WPF INotifyPropertyChanged用于链接的只读属性

我试图了解如何更新UI如果我有一个依赖于另一个属性的只读属性,以便更改一个属性更新两个UI元素(在这种情况下是文本框和只读文本框。例如:

public class raz : INotifyPropertyChanged { int _foo; public int foo { get { return _foo; } set { _foo = value; onPropertyChanged(this, "foo"); } } public int bar { get { return foo*foo; } } public raz() { } public event PropertyChangedEventHandler PropertyChanged; private void onPropertyChanged(object sender, string propertyName) { if(this.PropertyChanged != null) { PropertyChanged(sender, new PropertyChangedEventArgs(propertyName)); } } } 

我的理解是,当foo被修改时,bar不会自动更新UI。 这是正确的方法吗?

指示bar已更改的一种方法是在foo setter中添加对onPropertyChanged(this, "bar")的调用。 我知道,丑陋,但你有它。

如果foo是在祖先类中定义的,或者你没有访问setter的实现,我想你可以订阅PropertyChanged事件,这样当你看到“foo”更改时,你也可以触发一个“bar” “改变通知。 在您自己的对象实例上订阅事件同样很难看,但是会完成工作。

我意识到这是一个老问题,但它是“链接属性的NotifyPropertyChanged”的第一个Google结果,所以我认为添加这个答案是合适的,以便有一些具体的代码。

我使用了Robert Rossney的建议并创建了一个自定义属性,然后在基本视图模型的PropertyChanged事件中使用它。

属性类:

 [AttributeUsage(AttributeTargets.Property,AllowMultiple = true)] public class DependsOnPropertyAttribute : Attribute { public readonly string Dependence; public DependsOnPropertyAttribute(string otherProperty) { Dependence = otherProperty; } } 

在我的基本视图模型中(所有其他WPF视图模型都inheritance自):

 public abstract class BaseViewModel : INotifyPropertyChanged { protected Dictionary> DependencyMap; protected BaseViewModel() { DependencyMap = new Dictionary>(); foreach (var property in GetType().GetProperties()) { var attributes = property.GetCustomAttributes(); foreach (var dependsAttr in attributes) { if (dependsAttr == null) continue; var dependence = dependsAttr.Dependence; if (!DependencyMap.ContainsKey(dependence)) DependencyMap.Add(dependence, new List()); DependencyMap[dependence].Add(property.Name); } } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { var handler = PropertyChanged; if (handler == null) return; handler(this, new PropertyChangedEventArgs(propertyName)); if (!DependencyMap.ContainsKey(propertyName)) return; foreach (var dependentProperty in DependencyMap[propertyName]) { handler(this, new PropertyChangedEventArgs(dependentProperty)); } } } 

现在,我可以轻松地标记属性,如下所示:

 public int NormalProperty { get {return _model.modelProperty; } set { _model.modelProperty = value; OnPropertyChanged(); } } [DependsOnProperty(nameof(NormalProperty))] public int CalculatedProperty { get { return _model.modelProperty + 1; } } 

如果这是一个严重的问题(“严重”,我的意思是你有一个非平凡数量的依赖只读属性),你可以创建一个属性依赖关系图,例如:

 private static Dictionary _DependencyMap = new Dictionary { {"Foo", new[] { "Bar", "Baz" } }, }; 

然后在OnPropertyChanged中引用它:

 PropertyChanged(this, new PropertyChangedEventArgs(propertyName)) if (_DependencyMap.ContainsKey(propertyName)) { foreach (string p in _DependencyMap[propertyName]) { PropertyChanged(this, new PropertyChangedEventArgs(p)) } } 

这与在Foo setter中放置多个OnPropertyChanged调用本身并没有多大区别,因为您必须为添加的每个新依赖属性更新依赖关系映射。

但它确实可以随后实现PropertyChangeDependsOnAttribute并使用reflection来扫描类型并构建依赖关系图。 这样你的财产看起来像:

 [PropertyChangeDependsOn("Foo")] public int Bar { get { return Foo * Foo; } } 

你可以简单地打电话

 OnPropertyChanged(this, "bar"); 

从这堂课的任何地方……你甚至冷如此:

  public raz() { this.PropertyChanged += new PropertyChangedEventHandler(raz_PropertyChanged); } void raz_PropertyChanged(object sender, PropertyChangedEventArgs e) { if(e.PropertyName == "foo") { onPropertyChanged(this, "bar"); } } 

如果您仅将UI用于UI目的,则可以将其从模型中完全删除。 您可以将UI元素绑定到foo属性,并使用自定义值转换器将结果从foo更改为foo * foo。

在WPF中,通常有很多方法可以完成同样的事情。 通常情况下,没有正确的方法,只有个人偏好。

根据计算的费用以及您期望使用它的频率,将其设置为私有set属性并在设置foo时计算值可能是有益的,而不是在调用bar get时动态计算。 这基本上是一个缓存解决方案,然后您可以将属性更改通知作为bar私有设置器的一部分。 我通常更喜欢这种方法,主要是因为我使用AOP(通过Postsharp)来实现实际的INotifyPropertyChanged样板。

-担

我很确定它必须以声明的方式实现,但我第一次尝试解决这个问题却是失败的。 我想要完成的解决方案是使用Lambda Expressions来定义它。 由于可以解析表达式(??),因此应该可以解析表达式并附加到所有NotifyPropertyChanged事件,以获得有关依赖数据更改的通知。

在ContinousLinq中,这对于集合非常有用。

 private SelfUpdatingExpression m_fooExp = new SelfUpdatingExpression(this, ()=> Foo * Foo); public int Foo { get { return m_fooExp.Value; } } 

但不幸的是我缺乏基本知识如何在表达式和Linq 🙁