2

Class C implements INotifyPropertyChanged.

Assume the C has Length, Width and Area propreties, where Area = Length * Width. A change in either might cause a change in area. All three are bound, i.e. the UI expects all three to notify of changes in their values.

When either Length or Width change, their setters call NotifyPropertyChanged.

How should I treat the calculated Area property? Currently the pattern I can think of is detecting in NotifyPropertyChanged whether the changed property is either Length or Width and, if such is the case, initiate an addional PropertyChanged notification for Area. This, however, requires that I maintain inside NotifyPropertyChanged the dependencies graph, which I feel is an anti-pattern.

So, my question is: How should I code dependency properties that depend on other dependency properties?

edit: People here suggested that Length and Width also call NotifyPropertyChanged for Area. Again, I think this is an anti-pattern. A property (IMHO) shouldn't be aware of who depends on it, as shouldn't NotifyPropertyChanged. Only the property should be aware of who it depends on.

H.B.
  • 166,899
  • 29
  • 327
  • 400
Avi
  • 15,696
  • 9
  • 39
  • 54
  • 2
    Don't confuse [dependency properties](http://msdn.microsoft.com/en-us/library/ms752914.aspx) with properties of a class that implements INotifyPropertyChanged. It's not the same thing. – Clemens Feb 20 '12 at 13:16
  • 1
    If you really don't like it. Register your viewmodel to its own PropertyChanged event, listen for property changes of Width and Length, and then raise again a change for Area. But again, its perfectly valid to raise multiple properties. In fact, raising will never call the setter of a property, only the getter so it's safe. – dowhilefor Feb 20 '12 at 13:59
  • Duplicate of http://stackoverflow.com/questions/5440121/databinding-to-calculated-field – Chris Gessler Feb 26 '12 at 13:28
  • I agree with you, @Avi. `Length` and `Width` should not be responsible for raising `PropertyChanged` for `Area` - And if they were, how would you do this if those properties were in another class? I answered this question, in another question: http://stackoverflow.com/questions/43653750/raising-propertychanged-for-a-dependent-property-when-a-prerequisite-property-in-another-class – Jogge Apr 28 '17 at 05:32

5 Answers5

4

This issue kept on bugging me, so I re-opened it.

First, I'd like to appologize for anyone taking my "anti-pattern" comment personally. The solutions offered here were, indeed, how-it's-done in WPF. However, still, IMHO they're bad practices caused, deficiencies in ther framework.

My claim is that the information hiding guide dictates that when B depeneds on A, A should not be aware of B. For exmaple, when B derives from A, A should not have code saying: "If my runtime type is really a B, then do this and that". Simiarily, when B uses A, A should not have code saying: "If the object calling this method is a B, then ..."

So it follows that if property B depends on property A, A shouldn't be the one who's responsible to alert B directly.

Conversely, maintaining (as I currently do) the dependencies graph inside NotifyPropertyChanged is also an anti-pattern. That method should be lightweight and do what it name states, not maintain dependency relationships between properties.

So, I think the solution needed is through aspect oriented programming: Peroperty B should use an "I-depend-on(Property A)" attribute, and some code-rewriter should create the dependency graph and modify NotifyPropertyChanged transparently.

Today, I'm a single programmer working on a single product, so I can't justify dvelving with this any more, but this, I feel, is the correct solution.

Avi
  • 15,696
  • 9
  • 39
  • 54
  • 1
    no, you are wrong. information hiding just makes no sense inside the same data structure (the same class, without inheritance). furthermore, INotifyPropertyChanged is an interface with one event: PropertyChanged. whoever implements this interface that the only one who knows when to raise PropertyChanged. there is no need for AOP, that's a completely different topic. – Peter Porfy Feb 27 '12 at 15:19
3

Here is an article describing how to create a custom attribute that automatically calls PropertyChanged for properties depending on another property: http://www.redmountainsw.com/wordpress/2012/01/17/a-nicer-way-to-handle-dependent-values-on-propertychanged/

The code will look like this:

[DependsOn("A")]
[DependsOn("B")]
public int Total
{
  get { return A + B; }
}

public int A 
{
  get { return m_A; }
  set { m_A = value; RaisePropertyChanged("A"); }
}

public int B
{
  get { return m_B: }
  set { m_B = value; RaisePropertyChanged("B"); }
}

I haven't tried it myself but I like the idea

Björn
  • 3,098
  • 2
  • 26
  • 40
0

Here is a possible implementation of an attribute:

public class DependentPropertiesAttribute : Attribute
{
    private readonly string[] properties;

    public DependentPropertiesAttribute(params string[] dp)
    {
        properties = dp;
    }

    public string[] Properties
    {
        get
        {
            return properties;
        }
    }
}

Then in the Base View Model, we handle the mechanism of calling property dependencies:

public class ViewModelBase : INotifyPropertyChanged
{
    public ViewModelBase()
    {
        DetectPropertiesDependencies();
    }

    private readonly Dictionary<string, List<string>> _dependencies = new Dictionary<string, List<string>>();

    private void DetectPropertiesDependencies()
    {
        var propertyInfoWithDependencies = GetType().GetProperties().Where(
        prop => Attribute.IsDefined(prop, typeof(DependentPropertiesAttribute))).ToArray();

        foreach (PropertyInfo propertyInfo in propertyInfoWithDependencies)
        {
            var ca = propertyInfo.GetCustomAttributes(false).OfType<DependentPropertiesAttribute>().Single();
            if (ca.Properties != null)
            {
                foreach (string prop in ca.Properties)
                {
                    if (!_dependencies.ContainsKey(prop))
                    {
                        _dependencies.Add(prop, new List<string>());
                    }

                    _dependencies[prop].Add(propertyInfo.Name);
                }
            }
        }
    }

    protected void OnPropertyChanged(params Expression<Func<object>>[] expressions)
    {
        expressions.Select(expr => ReflectionHelper.GetPropertyName(expr)).ToList().ForEach(p => {
            RaisePropertyChanged(p);
            RaiseDependentProperties(p, new List<string>() { p });
        });

    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    protected virtual void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void RaiseDependentProperties(string propertyName, List<string> calledProperties = null)
    {
        if (!_dependencies.Any() || !_dependencies.ContainsKey(propertyName))
            return;

        if (calledProperties == null)
            calledProperties = new List<string>();

        List<string> dependentProperties = _dependencies[propertyName];

        foreach (var dependentProperty in dependentProperties)
        {
            if (!calledProperties.Contains(dependentProperty))
            {
                RaisePropertyChanged(dependentProperty);
                RaiseDependentProperties(dependentProperty, calledProperties);
            }
        }
    }
}

Finally we define dependencies in our ViewModel

[DependentProperties("Prop1", "Prop2")]
public bool SomeCalculatedProperty
{
    get
    {
        return Prop1 + Prop2;
    }
}
maxence51
  • 994
  • 1
  • 8
  • 20
0

When the Length or Width properties are changed you fire PropertyChanged for Area in addition to firing it for either Length or Width.

Here is a very simple implementation based on backing fields and the method OnPropertyChanged to fire the PropertyChanged event:

public Double Length {
  get { return this.length; }
  set {
    this.length = value;
    OnPropertyChanged("Length");
    OnPropertyChanged("Area");
  }
}

public Double Width {
  get { return this.width; }
  set {
    this.width = value;
    OnPropertyChanged("Width");
    OnPropertyChanged("Area");
  }
}

public Double Area {
  get { return this.length*this.width; }
}

Doing it like this is certainly not an anti-pattern. That is exactly the pattern for doing it. You as the implementer of the class knows that when Length is changed then Area is also changed and you encode it by raising the appropriate event.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • But what if `Width` and `Length` was in another class, how would you then raise `PropertyChanged` for `Area`? Check the answer for this question: http://stackoverflow.com/questions/43653750/raising-propertychanged-for-a-dependent-property-when-a-prerequisite-property-in-another-class – Jogge Apr 28 '17 at 05:26
  • Although this is how I do as well, this gets out of control quickly when you have a moderately sized class. You start hunting where you put your OnPropertyChanged() calls for the changed property. When you edit Property A, you shouldn't start looking for OnPropertyChanged("A") calls throughout a class file that consist of hundreds, sometimes thousands lines of codes. – Canol Gökel Dec 08 '17 at 21:06
0

Then you should raise twice, in Length and Width property setters. One for the actual property and one for the Area property.

for example:

private int _width;
public int Width
{
    get { return _width; }
    set
    {
        if (_width == value) return;
        _width = value;
        NotifyPropertyChanged("Width");
        NotifyPropertyChanged("Area");
    }
}

People here suggested that Length and Width also call NotifyPropertyChanged for Area. Again, I think this is an anti-pattern. A property (IMHO) shouldn't be aware of who depends on it, as shouldn't NotifyPropertyChanged. Only the property should be aware of who it depends on.

This is not an anti-pattern. Actually, your data encapsulated inside this class, so this class knows when and what changed. You shouldn't know outside of this class that Area depends on Width and Length. So the most logical place to notify listeners about Area is the Width and Length setter.

A property (IMHO) shouldn't be aware of who depends on it, as shouldn't NotifyPropertyChanged.

It does not break encapsulation, because you are in the same class, in the same data structure.

An extra information is that knockout.js (a javascript mvvm library) has a concept which accessing this problem: Computed Observables. So I believe this is absolutely acceptable.

Peter Porfy
  • 8,921
  • 3
  • 31
  • 41
  • In the Ember.js framework, computed properties represent this same functionality, but it only makes the computed property declare the depended-upon property names, not the other way around as implied in this solution. – Epirocks Oct 17 '17 at 00:13
  • While it might look similar, emberjs computed property is a different thing. With INotifyPropertyChanged and what you see here you don't even declare dependencies. `NotifyPropertyChanged` is not a dependency declaration like `function(){}.property('foo', 'bar')` , it just triggers an event. – Peter Porfy Oct 17 '17 at 15:39
  • That's the point though isn't it, that you're raising the event in a depended-upon property, implicitly declaring it. Doesn't seem right. What if the events you are raising depend on each other as well. If B depends on A and C depends on B and D on C. A property would call Notify for all, despite D only vaguely caring about A and possibly being semantically unrelated. E.g why should the Age field care about a customer-related Order created on 4th Dec... – Epirocks Oct 18 '17 at 16:32
  • I understand your reasoning and what say makes complete sense in general but what you need to understand is that `INotifyPropertyChanged` is a different thing than dependent properties. There is no automatic or declarative dependency tracking here so you need to do it manually. If you need to represent more complex dependencies possibly across boundaries then you probably need something else. `INotifyPropertyChanged` is just a dead simple interface and it has nothing to do with e.g. emberjs computed property concept. – Peter Porfy Oct 19 '17 at 06:48
  • "If you need to represent more complex dependencies possibly across boundaries then you probably need something else. " - This is the reason people are saying it feels wrong. It's not scalable. Who wouldn't want a scalable solution? Everything starts off simple, then needs to progress. – Epirocks Oct 19 '17 at 08:39
  • "Everything starts off simple, then needs to progress" - yes, and so for a simple thing like this (width+length+area) you should keep it simple (for example involving AOP here makes no sense). If you work with the `INotifyPropertyChanged` interface and you need something so simple as this it will be good. No need to overcomplicate it before you need so. What you say makes sense of course, I use emberjs every day. But what you say is also very generic and for different problems you need different solutions. It is a question about `INotifyPropertyChanged` and that's it. – Peter Porfy Oct 19 '17 at 14:06