WPF INotifyPropertyChanged for linked read-only properties
I am trying to understand how to update the UI if I have a read-only property that is dependent on another property, so that changes to one property update both UI elements (in this case a textbox and a read-only textbox. For example:
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));
}
}
}
M开发者_Go百科y understanding is that bar will not automatically update the UI when foo is modified. Whats the correct way to do this?
I realize this is an old question, but it's the first Google result of "NotifyPropertyChanged of linked properties", so I think it's appropriate to add this answer so that there's some concrete code.
I used Robert Rossney's suggestion and created a custom attribute, then used it in a base view model's PropertyChanged event.
The attribute class:
[AttributeUsage(AttributeTargets.Property,AllowMultiple = true)]
public class DependsOnPropertyAttribute : Attribute
{
public readonly string Dependence;
public DependsOnPropertyAttribute(string otherProperty)
{
Dependence = otherProperty;
}
}
And in my base view model (which all other WPF view models inherit from):
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));
}
}
}
This now allows me to mark properties easily, like so:
public int NormalProperty
{
get {return _model.modelProperty; }
set
{
_model.modelProperty = value;
OnPropertyChanged();
}
}
[DependsOnProperty(nameof(NormalProperty))]
public int CalculatedProperty
{
get { return _model.modelProperty + 1; }
}
One way to indicate that bar has changed is to add a call to onPropertyChanged(this, "bar")
in the foo setter. Ugly as hell, I know, but there you have it.
If foo is defined in an ancestor class or you otherwise don't have access to the implementation of the setter, I suppose you could subscribe to the PropertyChanged event so that when you see a "foo" change, you can also fire a "bar" change notification. Subscribing to events on your own object instance is equally ugly, but will get the job done.
If this is a serious issue (by "serious", I mean you have a non-trivial number of dependent read-only properties), you can make a property dependency map, e.g.:
private static Dictionary<string, string[]> _DependencyMap =
new Dictionary<string, string[]>
{
{"Foo", new[] { "Bar", "Baz" } },
};
and then reference it in OnPropertyChanged:
PropertyChanged(this, new PropertyChangedEventArgs(propertyName))
if (_DependencyMap.ContainsKey(propertyName))
{
foreach (string p in _DependencyMap[propertyName])
{
PropertyChanged(this, new PropertyChangedEventArgs(p))
}
}
This isn't inherently a lot different from just putting multiple OnPropertyChanged
calls in the Foo
setter, since you have to update the dependency map for every new dependent property you add.
But it does make it possible to subsequently implement a PropertyChangeDependsOnAttribute
and use reflection to scan the type and build the dependency map. That way your property would look something like:
[PropertyChangeDependsOn("Foo")]
public int Bar { get { return Foo * Foo; } }
You could simply call
OnPropertyChanged(this, "bar");
from anywhere in this class...You cold even go like this:
public raz()
{
this.PropertyChanged += new PropertyChangedEventHandler(raz_PropertyChanged);
}
void raz_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "foo")
{
onPropertyChanged(this, "bar");
}
}
If you are only using bar for UI purposes, you could remove it from your model completely. You could bind the UI element to the foo property and use a custom value converter to change the result from foo into foo*foo.
In WPF there are often a lot of ways to accomplish the same thing. Often times, there isn't a correct way, just a personal preference.
Depending on the expense of the calculation and how frequently you expect it to be used, it may be beneficial to make it a private set property and calculate the value when foo
is set, rather than calculating on the fly when the bar
get is called. This is basically a caching solution and then you can do the property change notification as part of the bar
private setter. I generally prefer this approach, mainly because I use AOP (via Postsharp) to implement the actual INotifyPropertyChanged
boilerplate.
-Dan
I pretty sure it must be possible in a declarative way, but my first attempt to solve this issue have been a failure. The solution i want to accomplish is using Lambda Expressions to define this. Since Expressions can be parsed (??) it should be possible to parse the expressions and attach to all NotifyPropertyChanged events to get notified about depended data changes.
in ContinousLinq, this works excellent for collections.
private SelfUpdatingExpression<int> m_fooExp = new SelfUpdatingExpression<int>(this, ()=> Foo * Foo);
public int Foo
{
get
{
return m_fooExp.Value;
}
}
but unfortionatly i lack on fundamentional know how in Expressions and Linq :(
精彩评论