计算属性的 MVVM 更新
本文关键字:更新 MVVM 属性 计算 | 更新日期: 2023-09-27 18:36:56
我只是在学习 MVVM,我正在尝试如何显示由于计算值的更改而对计算属性的更改。到目前为止,我看到的所有解决方案都严重违反了封装,我想知道是否有更好的方法。
假设我需要显示的一件事是复杂的税收计算的结果。计算(可能还有它的依赖项)会不时更改,因此我想严格封装它。
这里最常提供的解决方案似乎是获取税收值所依赖的所有属性,以便在模型视图中为属性本身以及依赖于它的每个属性调用 PropertyChanged。这意味着每个属性都需要知道使用或可能使用它的所有内容。当我的税收规则发生变化,使计算依赖于以前不依赖的东西时,我将需要接触进入我的计算的所有新属性(可能在其他类中,可能不受我的控制),以使它们调用 PropertyChanged 作为税收值。这完全破坏了封装的任何希望。
我能想到的最佳解决方案是使执行计算的类接收 PropertyChanged 事件,并在计算中的任何更改时为税值引发一个新的 PropertyChanged 事件。这至少保留了类级别的封装,但它仍然违反了方法封装:类不应该知道方法是如何工作的。
所以,我的问题是,有没有更好的方法(如果有,它是什么)?还是表示封装 (MVVM) 阻止了业务逻辑的封装?我是否面临非此即彼的选择?
这里最常提供的解决方案似乎是获得所有 税值所依赖的属性调用属性更改于 属性本身和每个属性的模型视图 取决于它。
否 除非显示支持属性,否则不需要其自己的更改通知。但是,每个属性都需要直接在其二传手中调用税值的OnPropertyChanged("TaxValue")
;或间接按照以下示例。这样,UI 就会更新,因为支持属性已更改。
话虽如此,让我们考虑一个例子。一种方法是创建一个将进行值计算的方法。当设置了最终值(下面的税值)时,它将调用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;
/// <summary>Raises the PropertyChanged event.</summary>
/// <param name="propertyName">The name of the property that has changed, only needed
/// if called from a different source.</param>
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
它非常简单,就是这样做的:传播依赖属性的通知,而不会污染触发器属性设置器。
原始示例:
public string Name
{
get { return Property.Get(string.Empty); }
set { Property.Set(value); }
}
public string Greeting => Property.Calculated(() => "Hello, " + Name + "!");
它的大小非常强大:想想视图模型属性的类似 Excel 的公式引擎。
我在域和视图模型类的几个项目中都使用了它,它帮助我消除了大部分命令式控制流(错误的主要来源),并使代码更具声明性和清晰度。
最好的一点是,依赖属性可以属于不同的视图模型,并且依赖关系图在运行时可能会发生巨大变化,并且它仍然可以正常工作。
这里最常提供的解决方案似乎是获取税收值所依赖的所有属性,以便在模型视图中为属性本身以及依赖于它的每个属性调用 PropertyChanged。
是的,但仅适用于该对象:每个属性都应在资源库中触发自己的属性更改事件。此外,资源库应以某种方式触发依赖于自身该值的属性。您不应尝试主动触发其他对象的更新:它们应侦听此对象PropertyChanged
。
我能想到的最佳解决方案是使执行计算的类接收 PropertyChanged 事件,并在计算中的任何更改时为税值引发一个新的 PropertyChanged 事件。这至少保留了类级别的封装,但它仍然违反了方法封装:类不应该知道方法是如何工作的。
这确实是标准方法。每个类都负责监视它所依赖的属性,并触发它所依赖的属性的属性更改事件。
可能有一些框架可以帮助您做到这一点,但知道应该发生什么是值得的。
有一个名为 Fody/PropertyChanged 的插件,它在编译时工作以自动实现PropertyChanged
。它将自动查看同一类中的哪些属性使用了您的属性,并在一个复杂的税收计算发生变化时引发所有适当的PropertyChanged
事件。
您可以使用 ILSpy 反编译已编译的代码,以查看它执行了哪些操作,并验证它是否引发了所有适当的事件。
我能想到的最好的解决方案是使执行 计算接收属性更改事件,并引发新的 属性更改了税值的事件,当任何更改时 进入计算。这至少保留了封装 类级别,但它仍然违反了方法封装:类不应该知道方法是如何工作的。
我认为您正在将术语"封装"扩展到对语法的争论。这里没有问题,例如:
private int _methodXCalls;
public void MethodX() {
Console.WriteLine("MethodX called {0} times", ++_methodXCalls);
}
该字段仅在 MethodX 中相关,但仅仅因为声明在语法上不在 MethodX
中并不意味着它破坏了方法封装。
同样,在类初始化中为每个属性设置事件处理程序也没有问题。只要它在初始化时只出现一次,并且不需要任何其他东西来"知道"这些特定的处理程序已被添加,您的属性在逻辑上仍然是自包含的。您可能可以以某种方式在属性上使用属性,例如 [DependsOn(property1, property2)]
,但这实际上只是一个代码可读性问题。