Best way to notify property change when field is depending on another
Asked Answered
G

3

11

What is the best way in c# to notify property changed on an item's field without set but get depends on other fields ?

For example :

public class Example : INotifyPropertyChanged
{
    private MyClass _item;
    public event PropertyChangedEventHandler PropertyChanged;

    public MyClass Item
    {
        get
        {
            return _item;
        }
        protected set
        {
            _item = value;
            OnPropertyChanged("Item");
        }
    }

    public object Field
    {
        get
        {
            return _item.Field;
        }
    }
#if !C#6
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
#else
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        // => can be called in a set like this: 
        // public MyClass Item { set { _item = value; OnPropertyChanged();} }
        // OnPropertyChanged will be raised for "Item"
    }
#endif
}

What the best way to rise a PropertyChanged for "Field" when setting Item ? I wanted to callOnPropertyChanged("Field"); when setting Item but if I had many fields the code will quickly be ugly and unmaintainable.

Edit:

I wonder if there is a function/method/attribute working like this :

[DependOn(Item)]
public object Field
{
    get
    {
        return _item.Field;
    }
}

=> When Item changes, all the depending fields will notify the property changed.

Does it exist ?

Goofy answered 11/8, 2016 at 12:25 Comment(2)
you could implement INotify for MyClassHydropic
Field in MyClass doesn't change, that the instance _item who is replaced by another. In my case, MyClass contains a xml text. I do a webRequest obtain a new instance of MyClass and I save it on _item. _item.Field can't notify property changed, because Field is only a get who parse the xml file.Goofy
M
10

One way is to just call OnPropertyChanged multiple times:

public MyClass Item
{
    get
    {
        return _item;
    }
    protected set
    {
        _item = value;
        OnPropertyChanged("Item");
        OnPropertyChanged("Field");
    }
}

This isn't very maintainable, however. Another option is to add a setter to your get-only property and set it from the other property:

public MyClass Item
{
    get
    {
        return _item;
    }
    protected set
    {
        _item = value;
        OnPropertyChanged("Item");
        Field = _item.Field;
    }
}

public object Field
{
    get
    {
        return _field;
    }
    private set
    {
        _field = value;
        OnPropertyChanged("Field");
    }
}

There is no built-in mechanism for using attributes to indicate this relationship between properties, however it would be possible to create a helper class that could do it for you.

I've made a really basic example of what that might look like here:

[AttributeUsage( AttributeTargets.Property )]
public class DepondsOnAttribute : Attribute
{
    public DepondsOnAttribute( string name )
    {
        Name = name;
    }

    public string Name { get; }
}

public class PropertyChangedNotifier<T> : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public PropertyChangedNotifier( T owner )
    {
        mOwner = owner;
    }

    public void OnPropertyChanged( string propertyName )
    {
        var handler = PropertyChanged;
        if( handler != null ) handler( mOwner, new PropertyChangedEventArgs( propertyName ) );

        List<string> dependents;
        if( smPropertyDependencies.TryGetValue( propertyName, out dependents ) )
        {
            foreach( var dependent in dependents ) OnPropertyChanged( dependent );
        }
    }

    static PropertyChangedNotifier()
    {
        foreach( var property in typeof( T ).GetProperties() )
        {
            var dependsOn = property.GetCustomAttributes( true )
                                    .OfType<DepondsOnAttribute>()
                                    .Select( attribute => attribute.Name );

            foreach( var dependency in dependsOn )
            {
                List<string> list;
                if( !smPropertyDependencies.TryGetValue( dependency, out list ) )
                {
                    list = new List<string>();
                    smPropertyDependencies.Add( dependency, list );
                }

                if (property.Name == dependency)
                    throw new ApplicationException(String.Format("Property {0} of {1} cannot depends of itself", dependency, typeof(T).ToString()));

                list.Add( property.Name );
            }
        }
    }

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

    private readonly T mOwner;
}

This isn't terribly robust (for example you could create a circular dependency between properties and the property changed would get stuck in an infinite recursion situation). It can also be made simpler using some .NET 4.5 and C#6 features, but I'll leave all that as an exercise for the reader. It probably also doesn't handle inheritance very well.

To use this class:

public class Example : INotifyPropertyChanged
{
    private MyClass _item;
    private PropertyChangedNotifier<Example> _notifier;

    public Example()
    {
        _notifier = new PropertyChangedNotifier<Example>( this );
    }

    public event PropertyChangedEventHandler PropertyChanged
    {
        add { _notifier.PropertyChanged += value; }
        remove { _notifier.PropertyChanged -= value; }
    }

    public MyClass Item
    {
        get
        {
            return _item;
        }
        protected set
        {
            _item = value;
            OnPropertyChanged("Item");
        }
    }

    [DependsOn( "Item" )]
    public object Field
    {
        get
        {
            return _item.Field;
        }
    }
    protected void OnPropertyChanged(string propertyName)
    {
        _notifier.OnPropertyChanged( propertyName );
    }
}
Morula answered 11/8, 2016 at 14:8 Comment(5)
That's what I actually do. The problem is that my class is not fixed yet, I have to create fields and remove fields for test. It a long way which can be misleading. If field Item "ignore" fields that depend on it, I can focus only on news fields. More maintainable and can avoid errorsGoofy
I developed a similar attribute (didn't see your edit ^^) but your PropertyChangedNotifier looks better than my code. I'll use your example, thank you. I'll just add the test if dependent == propertyNameGoofy
For completeness, in your PropertyChangedNotifier, expand the example usage where a property depends on multiple other properties (stacking [DependsOn( "Item" )]).Sigman
Edit, stacking does not work, gives compiler error about duplicate attributes... You forgot to add AllowMultiple = true, like so [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]Sigman
The attribute solution is actually a nice one. But what if Item were in another class? Then you cannot use this solution. I have answered this question in another question, where the solution is the same, even if the property were in another class: #43654250Harter
L
2

As far as I know, there is not built in method for that. I usually do like this:

public class Foo : INotifyPropertyChanged
{
    private Bar _bar1;

    public Bar Item
    {
        get { return _bar1; }
        set 
        { 
             SetField(ref _bar1, value); 
             ItemChanged();
        }
    }

    public string MyString
    {
        get { return _bar1.Item; }
    }

    private void ItemChanged()
    {
        OnPropertyChanged("MyString");
    }
}

public class Bar
{
    public string Item { get; set; }
}

You don't have the notifying logic inside of the property this way. It is more maintainable in my opinion this way and it is clear what the method does.

Also, I prefer to use this method I found somewhere on SO instead of the hardcoded name in the class (if the name of the property changes, it breaks).

OnPropertyChanged("MyString"); becomes OnPropertyChanged(GetPropertyName(() => MyString));

where GetPropertyName is:

public static string GetPropertyName<T>(Expression<Func<T>> propertyLambda)
{
    if (propertyLambda == null) throw new ArgumentNullException("propertyLambda");

    var me = propertyLambda.Body as MemberExpression;

    if (me == null)
    {
        throw new ArgumentException("You must pass a lambda of the form: '() => Class.Property' or '() => object.Property'");
    }

    return me.Member.Name;
}

After this, every time I change the property as the name, I will have to rename the property everywhere I have GetPropertyName instead of searching for the hardcoded string values.

I'm also curious about a built-in way to do the dependency, so I'm putting a favorite in there :)

Lashelllasher answered 11/8, 2016 at 14:17 Comment(0)
S
2

Even though in this solution the event is still propagated from the setter (so not exactly what the question is about), it provides a nice, more manageable way for representing dependencies. Someone might find it useful.

The solution is to create a custom wrapper for triggering INotifyPropertyChanged events. Instead of calling OnPropertyChanged manually we can define following mathods (preferably inside a base class that we will reuse later):

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    internal void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected ViewModelPropertyChange SetPropertyValue<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
    {
        property = value;
        OnPropertyChanged(propertyName);

        return new ViewModelPropertyChange(this);
    }
}

This class provides us with a way of setting a value of a given field without a need for providing the name of a proparty the call comes from.

We also have to define a class that will enable use to define dependent properties (instance of this class is returned from SetPropertyValue mathod).

public class ViewModelPropertyChange
{
    private readonly ViewModelBase _viewModel;

    public ViewModelPropertyChange(ViewModelBase viewModel)
    {
        _viewModel = viewModel;
    }

    public ViewModelPropertyChange WithDependent(string name)
    {
        _viewModel.OnPropertyChanged(name);

        return this;
    }
}

It simply stores a reference to an object that is being changed and makes it possible to propagate an event to next properties.

With this we can create a class derived from ViewModelBase like this:

class OurViewModel : ViewModelBase
{
    private int _partOne;
    public int PartOne
    {
        get => _partOne;
        set => SetPropertyValue(ref _partOne, value)
            .WithDependent(nameof(Total));
    }

    private int _partTwo;
    public int PartTwo
    {
        get => _partTwo;
        set => SetPropertyValue(ref _partTwo, value)
            .WithDependent(nameof(Total))
            .WithDependent(nameof(PartTwoPlus2));
    }

    public int Total {
        get => PartOne + PartTwo;
    }

    public int PartTwoPlus2 {
        get => PartTwo + 2;
    }
}
Springfield answered 21/3, 2018 at 11:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.