13

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));
    }
  }
}

My understanding is that bar will not automatically update the UI when foo is modified. Whats the correct way to do this?

tbischel
  • 6,337
  • 11
  • 51
  • 73

7 Answers7

15

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; }
}
Mage Xy
  • 1,803
  • 30
  • 36
  • What if I have the property AnyState in class A. Which is bascally a get from two other properties, each one in a separate class. prop A get { return B.IsRunning || C.IsRunning;} Class B clas a IsRunning property and C as well. – Luishg Feb 08 '18 at 16:26
  • @Luishg If the properties are in different classes, you will need a different solution. This attribute is only meant for calculated properties of a single class. – Mage Xy Feb 08 '18 at 16:36
10

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.

dthorpe
  • 35,318
  • 5
  • 75
  • 119
  • 3
    I don't understand why you think it is particularly ugly to fire onPropertyChanged(this, "bar")? I could see it if .bar was dependant on 3 properties and you had that call in all three setters... but then he could move the calculation out of the property-getter of .bar and into a calculation-method that calls the update method. – Goblin Aug 26 '10 at 19:16
  • 10
    It's ugly because you're placing logic required for proper behavior of property B in the setter of property A. There can be any number of properties that are dependent upon the value of A. Having to modify A to satisfy the needs of other properties that use A is nonobvious and nonscalable. – dthorpe Aug 26 '10 at 19:41
  • 1
    A cleaner solution would be to have some sort of way to indicate that B is dependent upon A and that when A changes B should signal a value change as well. The 2nd option above, to listen to one's own PropertyChanged event, is in this style, but listening to your own events is kinda kinky too. – dthorpe Aug 26 '10 at 19:45
  • Hehe - I think you have a lower threshold than me for what constitutes ugly code... :P I've learned to 'appreciate' UI-logic that isn't an event-soup... – Goblin Aug 26 '10 at 20:02
  • Quite possible. My background is in building APIs and app frameworks where everyone sees the code. We can get away with a lot of stuff in an app that would not be allowed in a framework or API. ;> – dthorpe Aug 26 '10 at 20:27
  • Yeah this was what I thought also a little bit after posting the question. I agree its not the most aesthetically pleasing – tbischel Aug 26 '10 at 23:23
  • 1
    I do not think `foo` should be responsible for rasing `PropertyChanged` for `bar`. This will work, but what if those properties were on another class? You would then have to implement it in another way. I have answered this question in another question, where the solution is the same even if the property were in the same class or in another class http://stackoverflow.com/questions/43653750/raising-propertychanged-for-a-dependent-property-when-a-prerequisite-property-in-another-class – Jogge Apr 28 '17 at 05:51
8

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; } }
Robert Rossney
  • 94,622
  • 24
  • 146
  • 218
7

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");
        }
    }
Tokk
  • 4,499
  • 2
  • 31
  • 47
  • 1
    This seems like a better scalable solution when I need to repeat for a bunch of properties. – tbischel Aug 26 '10 at 23:21
  • 1
    subscribing to PropertyChanged event and firing the bar change notification is exactly what PRISM does in their Commanding Quickstart. Their example is better- if ( propertyName == "Price" || propertyName == "Quantity" || propertyName == "Shipping" ) { this.NotifyPropertyChanged( "Total" ); } – RichardOD Jul 09 '11 at 11:42
3

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.

mdm20
  • 4,475
  • 2
  • 22
  • 24
  • in my case, the user needs to see it and the program needs to access it as well, but that does sound interesting. – tbischel Aug 26 '10 at 23:20
0

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

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
0

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 :(

Ram kiran Pachigolla
  • 20,897
  • 15
  • 57
  • 78
  • Can WPF bind directly against the self updating expression? -- If so, why not just return that type instead of the calculated int? – BrainSlugs83 Sep 05 '14 at 21:43