Best way to notify you when a property changes when a field depends on another

What is the best way in C # to notify a property changed in an element field without set , but get depends on other fields?

For instance:

 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 is the best way to raise PropertyChanged for "Field" when setting Item ? I wanted to call OnPropertyChanged("Field"); when setting up Item , but if I had many fields, the code would be quickly ugly and unbearable.

Edit:

I wonder if the function / method / attribute works:

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

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

He exists?

+7
source share
4 answers

One way is to simply call OnPropertyChanged several times:

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

However, this is not very convenient. Another option is to add the installer to the get-only property and set it from another 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 this for you.

I made a really basic example of what might look 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 is not very reliable (for example, you could create a circular relationship between properties and a modified property stuck in an infinite recursion environment). It can also be simplified with some .NET 4.5 and C # 6 features, but I will leave all this as an exercise for the reader. He probably also handles 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 ); } } 
+6
source

As far as I know, there is no built-in method for this. I usually do 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 do not have notification logic inside a property this way. In my opinion, this is more convenient, and it is clear what this method does.

In addition, I prefer to use this method, which I found somewhere in SO instead of the hard-coded name in the class (if the property name changes, it is interrupted).

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

where GetPropertyName :

 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 that, every time I change the property as a name, I will have to rename the property wherever I have GetPropertyName , instead of looking for the string values ​​of the string.

I'm also interested in the built-in way to make addiction, so I put my favorite in it :)

+2
source

Despite the fact that in this solution the event is still propagating from the setter (so that's not what it is all about), it provides a good, more manageable way of representing dependencies. Someone may find this helpful.

The solution is to create a custom wrapper to fire INotifyPropertyChanged events. Instead of calling OnPropertyChanged manually, we can define the following mathods (preferably inside the base class, which we will use 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 to set the value of this field without having to provide the name of the partisan call coming from.

We also need to define a class that we can use to define dependent properties (an 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 the object that is being modified, and allows you to extend the event to the following 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; } } 
+1
source

What should I do if I would like to get information about a changed property from the first database property that could not be used onpropertychanged?

0
source

All Articles