Pages

Showing posts with label INotifyPropertyChanged. Show all posts
Showing posts with label INotifyPropertyChanged. Show all posts

Thursday, October 10, 2013

How to subscribe on property changes without events

In my previous post I already explained how to implement INotifyPropertyChanged and INotifyPropertyChanging properly. At least one proper way. If you missed it, you might want to read it before this post, because this one based heavily on it. Let’s assume we want to use it as base class for our ViewModels and Models as well. In our really simple example we have a model called Person. This class is inherited from our base ViewModel (implemented in my prev. post), named  ViewModelBase.

public class Person : ViewModelBase
{
   private string _name;
   public string Name
   {
     get { return _name; }
     set { this.SetAndRaisePropertyChanged(() => this.Name, ref _name, value); }
   }

   private DateTime _birthDate;
   public string BirthDate
   {
     get { return _birthDate; }
     set { this.SetAndRaisePropertyChanged(() => this.BirthDate, ref _birthDate, value); }
   }
}
  
And a 'manager' class, our MainViewModel, also inherited from ViewModelBase. Person is bound to UI, user can create a new one, modify and existing one, it doesn't matter. Anything happens with our class Person we want to receive a notification in our MainViewModel. We can extend our base class with an OnPropertyChanged protected method, like OnPropertyChanging, but those are protected and we are in another class. We can use the PropertyChanged event, but there are two issues with it: first it is a hard reference, we need to manage subscriptions (subscribe and unsubscribe), otherwise the garbage collector (GC) won't dispose our class. Second issue: we'll get every PropertyChanged event only with a property name. So we had to check every time, whether this property is our property using 'magic' string. Furthermore we always had to keep it in our heads, when we rename or refactor a property we also need to change those compare strings. So, might be working, but not a good idea. Something similar would be nice:

this.SubscribeOnPropertyChanged(() => this.Person, p => p.Name, OnNameChanged);

Ok, let's see how to achieve this. Create a new method in our ViewModelBase class. First a class selector (which is actually a property or field of our MainViewModel), then a property selector for the selected class, and an Action to execute in case of any changes. The trick is: we pick the class and store it in our external Action reference collection.

private readonly List<PropertySubscription> OnChangedActions = 
    new List<PropertySubscription>();
 
protected void SubscribeOnPropertyChanged<TClass, TProperty>(
    Expression<Func<TClass>> classSelector, 
    Expression<Func<TClass, TProperty>> propertySelector, Action onPropertyChanged)
   where TClass : ViewModelBase
{
   var propertyName = ExtractPropertyName(propertySelector.Body as MemberExpression);
   var targetClass = classSelector.Compile();

   targetClass().OnChangedActions.Add( 
       new PropertySubscription(propertyName, onPropertyChanged));
}

PropertySubscription is really just a metaclass to store external subscriptions. You can use a simply KeyValuePair<string, Action> instead. To avoid redundant code, ExtractPropertyName must be extracted into two methods. Simply create a new method with the same name and copy the last few lines from the original one into the new one:

private string ExtractPropertyName(MemberExpression me)
{
   if ((me != null) && !string.IsNullOrEmpty(me.Member.Name))
   {
      return me.Member.Name;
   }

   throw new ArgumentException("Property could not be found.");
}

Ok, so we store it, but it does not do anything. We need to execute those actions whenever a property change occurs. Create a method which filters our collection on property name and execute every subscription action. Don't forget to store actions into a local variable inside the iteration to avoid "Access to modified closure".

private void ExecuteExternalSubcriptions(string propertyName)
{
   var actions = this.OnChangedActions.Where(s => s.PropertyName == propertyName);

   foreach (var action in actions)
   {
      var local = action;
      if (local.OnChangedAction != null)
      {
         local.OnChangedAction();
      }
   }
}

There is only one step left, add this method to our RaisePropertyChanged method. The new one is something like:

private void RaisePropertyChanged(string propertyName)
{
   var handler = this.PropertyChanged;
   if (handler != null)
   {
      handler(this, new PropertyChangedEventArgs(propertyName));
   }

   this.ExecuteExternalSubcriptions(propertyName);
}

From now on, every time a property has been changed, we also check external subscriptions and if we found any, execute them after property changed event. Thanks to this little extension in the ViewModelBase we can easily execute custom actions outside our class without strong references. Since we reference in a (probably) short living class to a long living class GC can dispose our object or class without problem. Furthermore developing is also much better and easier, working with expressions does not let us to compile with typo and we can refactor or rename our properties without to worry about magic strings in our code.

I can't believe you are still reading :) To appreciate your patient I give you one more thing: subscriptions to any property change. As you can see, our previous method allows us to subscribe to one particular property change. But sometimes we need all of them. Subscribing to all of them would be silly, so let's just add one more method, without property selector.

private readonly object dummy = new object();
 
protected void SubscribeOnPropertyChanged<TClass>(
    Expression<Func<TClass>> classSelector, Action onPropertyChanged)
   where TClass : ViewModelBase
{
   this.SubscribeOnPropertyChanged(classSelector, x => x.dummy, onPropertyChanged);
}

But what is 'dummy and why? Like I said before, I really hate to build logic on strings, but to keep our code simply and effective we want to use the same collection. But our collection stores PropertySubscriptions, where property name is obligatory. Best way is to create a private field and use it as property in our subscription. What we need it filter out this property before we iterate through our actions. So, let's change our ExecuteExternalSubscriptions method:

private void ExecuteExternalSubscriptions(string propertyName)
{
   var actions = this.OnChangedActions.Where(
       s => s.PropertyName == propertyName || 
       s.PropertyName == this.ExtractPropertyName(() => this.dummy));

   foreach (var action in actions)
   {
      var local = action;
      if (local.OnChangedAction != null)
      {
         local.OnChangedAction();
      }
   }
}

And how it works? Here is a tiny code snippet:

public class MainWindowViewModel : ViewModelBase
{      
  private Person Person = new Person();

  public MainWindowViewModel()
  {
   // to subscribe on Person's name changes
   this.SubscribeOnPropertyChanged(() => this.Person, p => p.Name, OnNameChanged);

   // to subscribe on any property changes in Person clsas
   this.SubscribeOnPropertyChanged(() => this.Person, OnPersonChanged);
  }

  private void OnNameChanged()
  { }

  private void OnPersonChanged()
  { }
}



Monday, October 7, 2013

Extend the INotifyPropertyChanged implemenation with a shorter setter

When a WPF application is needed, than probably a noticeable percentage of the code is property declaration, due MVVM, at least in the UI / client layer or in the contracts. Event with the method mentioned in one of my previous post the implementation costs 6-12 lines depends on how you break lines. After some testing I found a better way, a more elegant way to set a property value. However the LOC won't be less and the execution time will be increased it is also an option.
Stick to my old ViewModelBase class (if you don't know what I mean, you definitely have to read my previous post) let's extend it with a read only field to cache class Fields and initialize it in the constructor. It is Lazy because you don't necessary need it, only if the new SetAndRaisePropertyChanged is gonna be used.

private readonly Lazy<IEnumerable<FieldInfo>> Fields;

protected ViewModelBase()
{
  this.Fields = new Lazy<IEnumerable<FieldInfo>>(() => 
      this.GetType().GetFields(
          System.Reflection.BindingFlags.NonPublic | 
          System.Reflection.BindingFlags.Public | 
          System.Reflection.BindingFlags.Instance));
}

My goal was to define a method with only one parameter (the value). So, what I need is to figure out the property and the backing field. Since I use .NET4 [CallerMethodName] is not an option. But the StrackFrame and StrackTrace are always there.

private string ConvertPropertyNameToBackingField(string propertyName)
{
   return "_" + propertyName;
}

protected bool SetAndRaisePropertyChanged<TProperty>(TProperty value)
{
   var method = new StackFrame(1).GetMethod();
   if (method != null && method.Name.StartsWith("set_"))
   { 
      var propertyName = method.Name.Substring(4);
      var field = this.Fields.Value.SingleOrDefault(f => 
         f.Name == this.ConvertPropertyNameToBackingField(propertyName));

      if (field != null)
      {
         var oldValue = field.GetValue(this);
         var result = oldValue == null || oldValue.Equals(value) == false;

         if (result)
         {
            this.OnPropertyChangingHandler(propertyName, oldValue, value);

            field.SetValue(this, value);

            this.RaisePropertyChanged(propertyName);                  
         }

      return result;
      }

      throw new ArgumentException("Could not find backing field: "
          this.ConvertPropertyNameToBackingField(propertyName) + 
          " in class " + this.GetType().Name);
   }

   throw new InvalidOperationException(@"Simplified SetAndRaisePropertyChanged method 
     cannot be used outside of the property setter. In this case consider 
     to use the another overload with three parameters.");
}

And the usage?

private string _MyProperty;
public string MyProperty
{
   get { return _MyProperty; }
   set { this.SetAndRaisePropertyChanged(value); }
}

Peace of cake, isn't it? As you can see, it only works if you call it from the setter and there is some kind of naming convention.

And what are the numbers?

The very first setter calls in the ViewModel do not show a big differences. In case of 3 parameters it takes around 0.0015 seconds, while this one takes 0.002. Real differences are during 2nd, 3rd, 4th etc. call. With property selector, it only costs 0.00008, but this one needs around 0.0007, so factor ten. But if you really need performance you should consider to use the string version (without property selector).

Wednesday, October 2, 2013

How to implement INotifyPropertyChanging

I think anyone, who at least once developed a WPF application, has also met the INotifyPropertyChanged interface. It is the key to build a program based on MVVM pattern, it helps to refresh binding and keep UI and code behind sync. Just like its name says, you receive a notification after a property's value has changed. But what if you need a notification before it changes? Let's assume we want to track user's changes and rollback if it is needed. In this case the INotifyPropertyChanging interface has to be implemented. Unfortunately, however this interface exists in .NET, nothing supports it. It means, you don't get any update or notify during property changing. But it doesn't have to mean, that you can't implement it yourself. So, let's see:

First, INotifyPropertyChanged.

protected void RaisePropertyChanged(string propertyName)
{
  var handler = this.PropertyChanged;
  if (handler != null)
  {
    handler(this, new PropertyChangedEventArgs(propertyName));
  }
}

I don't really like to operate with 'magic' strings, furthermore you don't get any compile error, but runtime error in case of typo, which is quite irritating. So, make it private and create another one with property selector using Expression.

protected void RaisePropertyChanged<TProperty>(Expression<Func<TProperty>> propertySelector)
{
  var property = this.ExtractPropertyName(propertySelector);
  this.RaisePropertyChanged(property);
}
 
private string ExtractPropertyName<TProperty>(Expression<Func<TProperty>> propertySelector)
{
  MemberExpression me = propertySelector.Body as MemberExpression;
  if (me == null)
  {
    UnaryExpression ue = propertySelector.Body as UnaryExpression;
    if (ue != null)
    {
       me = ue.Operand as MemberExpression;
    }
  }

  if ((me != null) && !string.IsNullOrEmpty(me.Member.Name))
  {
    return me.Member.Name;
  }

  throw new ArgumentException("Property could not be found.");
}

Alright. So far so good. But it would be better, if we hadn't write in every property setter a value compare, a backing field setter and a raise. We can use a SetAndRaisePropertyChanged method instead. It has three parameters: a property selector expression, a backing field reference and the value itself. If the value really changed, we set it and raise a property change.

protected bool SetAndRaisePropertyChanged<TProperty>(
    Expression<Func<TProperty>> propertySelector, 
    ref TProperty backingField, TProperty value)
{
  var result = backingField == null || backingField.Equals(value) == false;

  if (result)
  {
    var propertyName = this.ExtractPropertyName(propertySelector);          
    backingField = value;          
    this.RaisePropertyChanged(propertyName);
  }

  return result;
}

In this moment, it wouldn't be necessary to explicitly extract the property name, but it make sense later. So, we have a base class, which provides a SetAndRaisePropertyChanged method. With this method you can implement a bindable property like this:

private string _myProperty;
public string MyProperty
{
   get { return _myProperty; }
   set { this.SetAndRaisePropertyChanged(() => this.MyProperty, ref _myProperty, value); }
}

Ok, after this little detour let's start to implement the INotifyPropertyChanging interface. You can add to your base class or not, it's up to you, has no effect after all. Our goal is, to receive a notification before a property actually changes. Create an empty generic virtual method, it can be overwritten in the inherited class to handle property changing. As the method signature shows, you get the property name, the old value and the new value as well.

protected virtual void OnPropertyChanging<TProperty>(
    string propertyName, TProperty oldValue, TProperty newValue)
{ }
  
You cannot cancel or prevent the change, at least not at this point. I assume the base class also implements the INotifyPropertyChanging interface and you have a PropertyChanging event, too. In this case:

private void OnPropertyChangingHandler<TProperty>( 
    string propertyName, TProperty oldValue, TProperty newValue)
{
   var hander = this.PropertyChanging;
   if (hander != null)
   {
      hander(this, new PropertyChangingEventArgs(propertyName));
   }

   // call virtual method after event
   this.OnPropertyChanging(propertyName, oldValue, newValue);
}

If not, you can simply remove the event handler part from the method. To make it work, add this handler method to the SetAndRaisePropertyChanged, right before backing field setter line. As result the new content of your method looks like this:

protected bool SetAndRaisePropertyChanged<TProperty>(
    Expression<Func<TProperty>> propertySelector, 
    ref TProperty backingField, TProperty value)
{
   var result = backingField == null || backingField.Equals(value) == false;

   if (result)
   {
      var propertyName = this.ExtractPropertyName(propertySelector);
      // call changing method
      this.OnPropertyChangingHandler(propertyName, backingField, value);
      backingField = value;
      this.RaisePropertyChanged(propertyName);
   }

   return result;
}

And that's it. All you need to do is inherit your class from this base class, use SetAndRaisePropertyChanged instead of explicitly comparing and setting value and overwrite OnPropertyChanging method. Like I mentioned before, you can't prevent value changes. The problem is that if property is bound to a visual element (e.g TextBox), the user already made changes, you receive any notification first in code behind after that. So, if you want to prevent it, you actually need to set it back to it's old value, but sometimes it is so late. Other issue can be a complex control, like a TabControl. If you try to prevent tab changes you might face with some inconsistency. The same page is visible, but an other tab header is selected.