我试图了解如何更新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。什么是正确的方法?
答案 0 :(得分:12)
我意识到这是一个老问题,但它是“链接属性的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视图模型都继承自):
public abstract class BaseViewModel : INotifyPropertyChanged
{
protected Dictionary<string, List<string>> DependencyMap;
protected BaseViewModel()
{
DependencyMap = new Dictionary<string, List<string>>();
foreach (var property in GetType().GetProperties())
{
var attributes = property.GetCustomAttributes<DependsOnPropertyAttribute>();
foreach (var dependsAttr in attributes)
{
if (dependsAttr == null)
continue;
var dependence = dependsAttr.Dependence;
if (!DependencyMap.ContainsKey(dependence))
DependencyMap.Add(dependence, new List<string>());
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; }
}
答案 1 :(得分:8)
指示条形图已更改的一种方法是在foo setter中添加对onPropertyChanged(this, "bar")
的调用。我知道,这很丑陋,但你有它。
如果foo是在祖先类中定义的,或者你没有访问setter的实现,我想你可以订阅PropertyChanged事件,这样当你看到“foo”更改时,你也可以触发一个“酒吧”更改通知。在您自己的对象实例上订阅事件同样很难看,但是可以完成工作。
答案 2 :(得分:8)
如果这是一个严重的问题(“严重”,我的意思是你有一个非平凡数量的从属只读属性),你可以创建一个属性依赖关系图,例如:
private static Dictionary<string, string[]> _DependencyMap =
new Dictionary<string, string[]>
{
{"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))
}
}
这与在OnPropertyChanged
setter中放置多个Foo
调用本身并没有多大区别,因为您必须为添加的每个新依赖属性更新依赖关系图。
但它确实可以随后实现PropertyChangeDependsOnAttribute
并使用反射来扫描类型并构建依赖关系图。这样你的财产看起来像:
[PropertyChangeDependsOn("Foo")]
public int Bar { get { return Foo * Foo; } }
答案 3 :(得分:6)
您可以直接致电
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");
}
}
答案 4 :(得分:3)
如果您仅将UI用于UI目的,则可以将其从模型中完全删除。您可以将UI元素绑定到foo属性,并使用自定义值转换器将结果从foo更改为foo * foo。
在WPF中,通常有很多方法可以完成同样的事情。很多时候,没有正确的方法,只是个人偏好。
答案 5 :(得分:0)
取决于计算的费用以及您期望使用它的频率,将其设置为私有集属性并在设置foo
时计算值可能是有益的,而不是动态计算调用bar
get时这基本上是一个缓存解决方案,然后您可以将属性更改通知作为bar
私有设置器的一部分。我通常更喜欢这种方法,主要是因为我使用AOP(通过Postsharp)来实现实际的INotifyPropertyChanged
样板。
-Dan
答案 6 :(得分:0)
我很确定它必须以声明的方式存在,但我第一次尝试解决这个问题却是失败的。 我想要完成的解决方案是使用Lambda Expressions来定义它。 由于可以解析表达式(??),因此应该可以解析表达式并附加到所有NotifyPropertyChanged事件,以获得有关依赖数据更改的通知。
在ContinousLinq中,这非常适合收藏。
private SelfUpdatingExpression<int> m_fooExp = new SelfUpdatingExpression<int>(this, ()=> Foo * Foo);
public int Foo
{
get
{
return m_fooExp.Value;
}
}
但遗憾的是我缺乏表达式和Linq中的基本知识:(