Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Better way to trigger OnPropertyChanged

Tags:

c#

wpf

We have a WPF Project that follows the MVVM pattern.

In the View Model there is a lot of code that looks like this:

    private string m_Fieldname;
    public string Fieldname
    {
        get { return m_Fieldname; }
        set
        {
            m_Fieldname = value;
            OnPropertyChanged("Fieldname");
        }
    }

Is there a way to do this that would require less code?

Would be nice with something like this:

[NotifyWhenChanged]
public string Fieldname { get; set ; }
like image 238
Shiraz Bhaiji Avatar asked Aug 15 '11 10:08

Shiraz Bhaiji


4 Answers

You could have a look at PostSharp. They even have a sample at Data Binding. The code taken from there:

/// <summary>
/// Aspect that, when apply on a class, fully implements the interface 
/// <see cref="INotifyPropertyChanged"/> into that class, and overrides all properties to
/// that they raise the event <see cref="INotifyPropertyChanged.PropertyChanged"/>.
/// </summary>
[Serializable]
[IntroduceInterface( typeof(INotifyPropertyChanged), 
                     OverrideAction = InterfaceOverrideAction.Ignore )]
[MulticastAttributeUsage( MulticastTargets.Class, 
                          Inheritance = MulticastInheritance.Strict )]
public sealed class NotifyPropertyChangedAttribute : InstanceLevelAspect, 
                                                     INotifyPropertyChanged
{

    /// <summary>
    /// Field bound at runtime to a delegate of the method <c>OnPropertyChanged</c>.
    /// </summary>
    [ImportMember( "OnPropertyChanged", IsRequired = false)] 
    public Action<string> OnPropertyChangedMethod;

    /// <summary>
    /// Method introduced in the target type (unless it is already present);
    /// raises the <see cref="PropertyChanged"/> event.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    [IntroduceMember( Visibility = Visibility.Family, IsVirtual = true, 
                      OverrideAction = MemberOverrideAction.Ignore )]
    public void OnPropertyChanged( string propertyName )
    {
        if ( this.PropertyChanged != null )
        {
           this.PropertyChanged( this.Instance, 
                                  new PropertyChangedEventArgs( propertyName ) );
        }
    }

    /// <summary>
    /// Event introduced in the target type (unless it is already present);
    /// raised whenever a property has changed.
    /// </summary>
    [IntroduceMember( OverrideAction = MemberOverrideAction.Ignore )]
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Method intercepting any call to a property setter.
    /// </summary>
    /// <param name="args">Aspect arguments.</param>
    [OnLocationSetValueAdvice, 
     MulticastPointcut( Targets = MulticastTargets.Property, 
         Attributes = MulticastAttributes.Instance)]
    public void OnPropertySet( LocationInterceptionArgs args )
    {
        // Don't go further if the new value is equal to the old one.
        // (Possibly use object.Equals here).
        if ( args.Value == args.GetCurrentValue() ) return;

        // Actually sets the value.
        args.ProceedSetValue();

        // Invoke method OnPropertyChanged (our, the base one, or the overridden one).
        this.OnPropertyChangedMethod.Invoke( args.Location.Name );

    }
}

Usage is then as simple as this:

[NotifyPropertyChanged]
public class Shape
{
   public double X { get; set; }
   public double Y { get; set; }
}

Examples taken from PostSharp site and inserted for completing the answer

like image 185
Sascha Avatar answered Oct 17 '22 03:10

Sascha


It looks like as if the Framework 4.5 slightly simplifies this:

private string m_Fieldname;
public string Fieldname
{
    get { return m_Fieldname; }
    set
    {
        m_Fieldname = value;
        OnPropertyChanged();
    }
}

private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")
{
    // ... do stuff here ...
}

This doesn't quite automate things to the extent you're looking for, but using the CallerMemberNameAttribute makes passing the property name as a string unnecessary.

If you're working on Framework 4.0 with KB2468871 installed, you can install the Microsoft BCL Compatibility Pack via nuget, which also provides this attribute.

like image 23
takrl Avatar answered Oct 17 '22 01:10

takrl


Josh Smith has a good article on using DynamicObject to do this here

Basically it involves inheriting from DynamicObject and then hooking into TrySetMember. CLR 4.0 only, unfortunately, although it may also be possible using ContextBoundObject in earlier versions but that would probably hurt performance, being primarily suited for remoting\WCF.

like image 32
gͫrͣeͬeͨn Avatar answered Oct 17 '22 03:10

gͫrͣeͬeͨn


IMHO, the PostSharp approach, as in the accepted answer, is very nice and is of course the direct answer to the question asked.

However, for those who can't or won't use a tool like PostSharp to extend the C# language syntax, one can get most of the benefit of avoiding code repetition with a base class that implements INotifyPropertyChanged. There are many examples lying around, but none have so far been included in this useful and well-trafficked question, so here is the version I generally use:

/// <summary>
/// Base class for classes that need to implement <see cref="INotifyPropertyChanged"/>
/// </summary>
public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
    /// <summary>
    /// Raised when a property value changes
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Updates a field for a named property
    /// </summary>
    /// <typeparam name="T">The type of the field</typeparam>
    /// <param name="field">The field itself, passed by-reference</param>
    /// <param name="newValue">The new value for the field</param>
    /// <param name="onChangedCallback">A delegate to be called if the field value has changed. The old value of the field is passed to the delegate.</param>
    /// <param name="propertyName">The name of the associated property</param>
    protected void UpdatePropertyField<T>(ref T field, T newValue,
        Action<T> onChangedCallback = null,
        [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, newValue))
        {
            return;
        }

        T oldValue = field;

        field = newValue;
        onChangedCallback?.Invoke(oldValue);
        OnPropertyChanged(propertyName);
    }

    /// <summary>
    /// Raises the <see cref="PropertyChanged"/> event.
    /// </summary>
    /// <param name="propertyName">The name of the property that has been changed</param>
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Used, for example, like this:

private int _value;
public int Value
{
    get { return _value; }
    set { UpdatePropertyField(ref _value, value); }
}

Not quite as concise as being able to just apply a code attribute to an auto-implemented property as in the PostSharp approach, but still goes a long way to speeding the implementation of view models and other similar types.

The key features above that distinguish it from some other implementations:

  1. Equality is compared using EqualityComparer<T>.Default. This ensures that value types can be compared without being boxed (a common alternative would be object.Equals(object, object)). The IEqualityComparer<T> instance is cached, so after the first comparison for any given type T, it's very efficient.
  2. The OnPropertyChanged() method is virtual. This allows derived types to easily and efficiently handle property changed events in a centralized way, without having to subscribe to the PropertyChanged event itself (e.g. for multiple levels of inheritance) and also of course gives the derived type better control over how and when it handles the property changed event relative to raising the actual PropertyChanged event.
like image 20
Peter Duniho Avatar answered Oct 17 '22 03:10

Peter Duniho