C类实现了INotifyPropertyChanged。
假设C具有长度,宽度和面积属性,其中Area = Length * Width。 中的更改可能会导致区域更改。这三个都是绑定的,即UI期望所有三个都通知其值的变化。
当“长度”或“宽度”发生变化时,其设置者将调用NotifyPropertyChanged。
我该如何处理计算出的Area属性?目前我能想到的模式是在NotifyPropertyChanged中检测更改的属性是长度还是宽度,如果是这种情况,则启动Area的附加PropertyChanged通知。但是,这需要我在NotifyPropertyChanged内部维护依赖关系图,我认为这是一种反模式。
所以,我的问题是:我应该如何编写依赖于其他依赖项属性的依赖项属性?
编辑:此处的人员建议长度和宽度也为区域调用NotifyPropertyChanged。我再次认为这是一种反模式。一个属性(恕我直言)不应该知道谁依赖它,不应该NotifyPropertyChanged。只有财产应该知道它依赖谁。
答案 0 :(得分:3)
这个问题一直困扰着我,所以我重新打开它。
首先,我想为任何接受我个人“反模式”评论的人道歉。这里提供的解决方案确实是在WPF中完成的。然而,恕我直言,他们是不良做法造成的,缺乏框架。
我的主张是information hiding指南规定,当B依赖于A时,A不应该知道B.例如,当B 来自 A时,A不应该有代码说:“如果我的运行时类型确实是B,那么就这样做吧。”同时,当B 使用 A时,A不应该有代码说:“如果调用此方法的对象是B,那么......”
因此,如果属性B依赖于属性A,则A不应该是负责直接警告B的人。
相反,维护(正如我现在所做的)NotifyPropertyChanged中的依赖关系图也是一种反模式。该方法应该是轻量级的,并执行其命名状态,而不是维护属性之间的依赖关系。
所以,我认为所需的解决方案是通过aspect oriented programming:Peroperty B应该使用“I-depend-on(Property A)”属性,而某些代码重写器应该创建依赖图并透明地修改NotifyPropertyChanged
今天,我是一个从事单一产品工作的程序员,所以我再也无法证明这一点,但我认为这是正确的解决方案。
答案 1 :(得分:3)
这篇文章描述了如何创建一个自定义属性,该属性根据另一个属性自动调用PropertyChanged属性:http://www.redmountainsw.com/wordpress/2012/01/17/a-nicer-way-to-handle-dependent-values-on-propertychanged/
代码如下所示:
[DependsOn("A")]
[DependsOn("B")]
public int Total
{
get { return A + B; }
}
public int A
{
get { return m_A; }
set { m_A = value; RaisePropertyChanged("A"); }
}
public int B
{
get { return m_B: }
set { m_B = value; RaisePropertyChanged("B"); }
}
我自己没试过,但我喜欢这个想法
答案 2 :(得分:0)
当Length
或Width
属性发生更改时,您为PropertyChanged
激发Area
,以便为Length
触发它}或Width
。
这是一个基于支持字段的简单实现,以及用于触发OnPropertyChanged
事件的方法PropertyChanged
:
public Double Length {
get { return this.length; }
set {
this.length = value;
OnPropertyChanged("Length");
OnPropertyChanged("Area");
}
}
public Double Width {
get { return this.width; }
set {
this.width = value;
OnPropertyChanged("Width");
OnPropertyChanged("Area");
}
}
public Double Area {
get { return this.length*this.width; }
}
这样做肯定是不反模式。这正是这样做的模式。作为类的实现者,您知道在更改Length
时,Area
也会更改,并通过引发相应的事件对其进行编码。
答案 3 :(得分:0)
然后你应该在长度和宽度属性设置器中加注两次。一个用于实际属性,一个用于Area属性。
例如:
private int _width;
public int Width
{
get { return _width; }
set
{
if (_width == value) return;
_width = value;
NotifyPropertyChanged("Width");
NotifyPropertyChanged("Area");
}
}
这里的人建议长度和宽度也可以调用 区域的NotifyPropertyChanged。我再次认为这是一个 反模式。财产(恕我直言)不应该知道谁依赖 它,不应该NotifyPropertyChanged。只有财产应该 意识到它依赖于谁。
这不是反模式。实际上,你的数据封装在这个类中,所以这个类知道什么时候和什么改变了。你不应该知道这个类之外的区域取决于宽度和长度。因此,通知侦听器有关Area的最合理的位置是Width和Length setter。
财产(恕我直言)不应该知道谁依赖它,如 不应该NotifyPropertyChanged。
它不会破坏封装,因为您在同一个类中,在相同的数据结构中。
额外的信息是knockout.js(一个javascript mvvm库)有一个概念来访问这个问题:Computed Observables。所以我相信这绝对是可以接受的。
答案 4 :(得分:0)
以下是属性的可能实现:
public class DependentPropertiesAttribute : Attribute
{
private readonly string[] properties;
public DependentPropertiesAttribute(params string[] dp)
{
properties = dp;
}
public string[] Properties
{
get
{
return properties;
}
}
}
然后在Base View Model中,我们处理调用属性依赖的机制:
public class ViewModelBase : INotifyPropertyChanged
{
public ViewModelBase()
{
DetectPropertiesDependencies();
}
private readonly Dictionary<string, List<string>> _dependencies = new Dictionary<string, List<string>>();
private void DetectPropertiesDependencies()
{
var propertyInfoWithDependencies = GetType().GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(DependentPropertiesAttribute))).ToArray();
foreach (PropertyInfo propertyInfo in propertyInfoWithDependencies)
{
var ca = propertyInfo.GetCustomAttributes(false).OfType<DependentPropertiesAttribute>().Single();
if (ca.Properties != null)
{
foreach (string prop in ca.Properties)
{
if (!_dependencies.ContainsKey(prop))
{
_dependencies.Add(prop, new List<string>());
}
_dependencies[prop].Add(propertyInfo.Name);
}
}
}
}
protected void OnPropertyChanged(params Expression<Func<object>>[] expressions)
{
expressions.Select(expr => ReflectionHelper.GetPropertyName(expr)).ToList().ForEach(p => {
RaisePropertyChanged(p);
RaiseDependentProperties(p, new List<string>() { p });
});
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected void RaiseDependentProperties(string propertyName, List<string> calledProperties = null)
{
if (!_dependencies.Any() || !_dependencies.ContainsKey(propertyName))
return;
if (calledProperties == null)
calledProperties = new List<string>();
List<string> dependentProperties = _dependencies[propertyName];
foreach (var dependentProperty in dependentProperties)
{
if (!calledProperties.Contains(dependentProperty))
{
RaisePropertyChanged(dependentProperty);
RaiseDependentProperties(dependentProperty, calledProperties);
}
}
}
}
最后,我们在ViewModel
中定义依赖项[DependentProperties("Prop1", "Prop2")]
public bool SomeCalculatedProperty
{
get
{
return Prop1 + Prop2;
}
}