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 🙁