Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 ?

like image 959
A.Pissicat Avatar asked Aug 11 '16 12:08

A.Pissicat


People also ask

How do you implement property change?

To implement INotifyPropertyChanged you need to declare the PropertyChanged event and create the OnPropertyChanged method. Then for each property you want change notifications for, you call OnPropertyChanged whenever the property is updated.

How will an object be notified if the property bound to it has been changed?

The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed. For example, consider a Person object with a property called FirstName .

What is OnPropertyChanged C#?

INotifyPropertyChanged is an interface member in System. ComponentModel Namespace. This interface is used to notify the Control that property value has changed. Sourcecode.zip.

What is property changed?

The PropertyChanged event can indicate all properties on the object have changed by using either null or String. Empty as the property name in the PropertyChangedEventArgs.


3 Answers

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 );
    }
}
like image 167
Kyle Avatar answered Oct 19 '22 06:10

Kyle


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

like image 39
Etienne Faucher Avatar answered Oct 19 '22 08:10

Etienne Faucher


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;
    }
}
like image 38
Tomasz Avatar answered Oct 19 '22 07:10

Tomasz