MVVM更新计算的属性

我只是在学习MVVM,而我正在努力研究如何根据计算出来的值的变化来显示对计算属性的更改。 到目前为止,我见过的所有解决方案都严重违反了封装,我想知道是否有更好的解决方案。

假设我需要显示的一件事是复杂税收计算的结果。 计算(可能还有它的依赖关系)会不时改变,所以我想保持严格的封装。

这里最经常提供的解决方案似乎是获取税值所依赖的所有属性,以便在ModelView中为属性本身以及依赖于它的每个属性调用PropertyChanged。 这意味着每个属性都需要知道使用或可能使用它的所有内容。 当我的税收规则以一种使计算依赖于以前不依赖的事物的方式改变时,我将需要触及所有进入我的计算的新属性(可能在其他类中,可能不在我的控制之下),让他们为税值调用PropertyChanged。 这完全破坏了封装的任何希望。

我能想到的最好的解决方案是让进行计算的类接收 PropertyChanged事件,并在进入计算的任何更改时为税值引发新的PropertyChanged事件。 这至少保留了级别的封装,但它仍然违反了方法封装:类不应该知道方法如何工作。

所以,我的问题是,有更好的方法(如果是的话,它是什么)? 或者表示封装(MVVM)是否阻止了业务逻辑的封装? 我是否面临过任何选择?

这里最经常提供的解决方案似乎是获取税值所依赖的所有属性,以便在ModelView中为属性本身以及依赖于它的每个属性调用PropertyChanged。 ….

是的,但仅适用于该对象:每个属性都应在setter中触发自己的属性更改事件。 此外,setter应该以某种方式触发依赖于该值的属性。 您不应该尝试主动触发其他对象的更新:它们应该监听此对象PropertyChanged

我能想到的最好的解决方案是让进行计算的类接收PropertyChanged事件,并在进入计算的任何更改时为税值引发新的PropertyChanged事件。 这至少保留了类级别的封装,但它仍然违反了方法封装:类不应该知道方法如何工作。

这确实是标准方式。 每个类都有责任监视它所依赖的属性,并触发属性的属性更改事件。

可能有一些框架可以帮助你做到这一点,但值得知道应该发生什么。

这里最经常提供的解决方案似乎是获取税值所依赖的所有属性,以便在ModelView中为属性本身以及依赖于它的每个属性调用PropertyChanged。

除非显示支持属性,否则支持属性不需要自己的更改通知。 但是每个属性都需要直接在其setter中调用税值的OnPropertyChanged("TaxValue") ; 或根据以下示例间接地。 这样,UI就会更新,因为支持属性已更改。

话虽如此,让我们考虑一个例子。 一种方法是创建一个可以进行值计算的方法。 设置最终值(TaxValue如下)时,它将调用OnNotifyPropertyChange 。 该操作将告知用户TaxValue对整个世界的改变; 无论什么价值触发它(扣除|费率|收入):

 public class MainpageVM : INotifyPropertyChanged { public decimal TaxValue { get { return _taxValue; } set { _taxValue = value; OnPropertyChanged(); } // Using .Net 4.5 caller member method. } public decimal Deduction { get { return _deduction; } set { _deduction = value; FigureTax(); } } public decimal Rate { get { return _rate; } set { _rate = value; FigureTax(); } } public decimal Income { get { return _income; } set { _income = value; FigureTax(); } } // Something has changed figure the tax and update the user. private void FigureTax() { TaxValue = (Income - Deduction) * Rate; } #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; /// Raises the PropertyChanged event. /// The name of the property that has changed, only needed /// if called from a different source. protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } #endif } 

编辑

要在.Net 4中使用CallerMemberName(和其他项目),请安装Nuget包:

Microsoft.BCL 。

或者如果不使用标准的OnPropetyChanged("TaxValue")

查看Stephen Cleary的计算属性: https : //github.com/StephenCleary/CalculatedProperties

它非常简单并且就是这样:传播依赖属性的通知而不会污染触发器属性setter。

原始例子:

 public string Name { get { return Property.Get(string.Empty); } set { Property.Set(value); } } public string Greeting => Property.Calculated(() => "Hello, " + Name + "!"); 

它的大小非常强大:为View Model属性设想类似Excel的公式引擎。

我在域和视图模型类的几个项目中使用它,它帮助我消除了大多数命令式控制流(错误的主要来源),并使代码更具说明性和清晰性。

关于它的最好的事情是依赖属性可以属于不同的视图模型,并且依赖图在运行时可以发生显着变化,它仍然可以正常工作。

有一个名为Fody / PropertyChanged的插件可以在编译时自动实现PropertyChanged 。 它将自动查看同一类中的哪些属性使用您的属性,并在一个复杂的税收计算更改时引发所有相应的PropertyChanged事件。

您可以使用ILSpy对已编译的代码进行反编译,以查看它的作用并validation它是否引发了所有适当的事件。

我能想到的最好的解决方案是让进行计算的类接收 PropertyChanged事件,并在进入计算的任何更改时为税值引发新的PropertyChanged事件。 这至少保留了级别的封装,但它仍然违反了方法封装:类不应该知道方法如何工作。

我认为你正在将术语“封装”扩展到对语法的争论。 这里没有问题,例如:

 private int _methodXCalls; public void MethodX() { Console.WriteLine("MethodX called {0} times", ++_methodXCalls); } 

该字段仅在MethodX中相关,但仅仅因为声明在语法内部不是MethodX并不意味着它会破坏方法封装。

同样,在类初始化中为每个属性设置事件处理程序也没有问题。 只要它只在初始化时出现一次,并且不需要“知道”那些特定的处理程序被添加,你的属性仍然在逻辑上是自包含的。 您可能以某种方式使用属性上的属性,例如[DependsOn(property1, property2)] ,但这实际上只是代码可读性问题。