views:

88

answers:

4

Say I have the following class:

public MainFormViewModel
{
    public String StatusText {get; set;}
}

What is the easiest smallest way to get my changes to StatusText to reflect to any controls that bind to it?

Obviously I need to use INotifyPropertyChanged, but is there a cool way to do it that does not clutter up my code? need lots of files? etc?

Note: If this is a dupe then I am sorry. I searched and could not find any thing but using T4 code Generation which does not sound easy (to setup at least).

+2  A: 

Unfortunately C# doesn't offer an easy mechanism to do that automatically... It has been suggested to create a new syntax like this :

public observable int Foo { get; set; }

But I doubt it will ever be included in the language...

A possible solution would to use an AOP framework like Postsharp, that way you just need to decorate your properties with an attribute:

public MainFormViewModel : INotifyPropertyChanged
{
    [NotifyPropertyChanged]
    public String StatusText {get; set;}
}

(haven't tried, but I'm pretty sure Postsharp allows you to do that kind of thing...)


UPDATE: OK, I managed to make it work. Note that it's a very crude implementation, using reflection on a private field to retrieve the delegate... It could certainly be improved, but I'll leave it to you ;)

[Serializable]
public class NotifyPropertyChangedAttribute : LocationInterceptionAspect
{
    public override void OnSetValue(LocationInterceptionArgs args)
    {
        object oldValue = args.GetCurrentValue();
        object newValue = args.Value;
        base.OnSetValue(args);
        if (args.Instance is INotifyPropertyChanged)
        {
            if (!Equals(oldValue, newValue))
            {
                RaisePropertyChanged(args.Instance, args.LocationName);
            }
        }
    }

    private void RaisePropertyChanged(object instance, string propertyName)
    {
        PropertyChangedEventHandler handler = GetPropertyChangedHandler(instance);
        if (handler != null)
            handler(instance, new PropertyChangedEventArgs(propertyName));
    }

    private PropertyChangedEventHandler GetPropertyChangedHandler(object instance)
    {
        Type type = instance.GetType().GetEvent("PropertyChanged").DeclaringType;
        FieldInfo propertyChanged = type.GetField("PropertyChanged",
                                                  BindingFlags.Instance | BindingFlags.NonPublic);
        if (propertyChanged != null)
            return propertyChanged.GetValue(instance) as PropertyChangedEventHandler;

        return null;
    }
}

Note that your class still need to implement the INotifyPropertyChanged interface. You just don't have to explicitly raise the event in your property setters.

Thomas Levesque
Are you sure that is the link to Postsharp. Doesn't seem like they offer any tools for C#.
Vaccano
Oops, sorry, bad link... I fixed it
Thomas Levesque
This looks cool, but it must be in the Commercial version (or it is harder than it looks). I could not get it to work with the Community Edition.
Vaccano
I updated my answer with a working example. It works for .NET 4.0, not sure about other versions...
Thomas Levesque
+1  A: 

Ive always liked this method

private string m_myString;
public string MyString
{
    get { return m_myString; }
    set 
    {
        if (m_myString != value)
        {
             m_myString = value;
             NotifyPropertyChanged("MyString");
        }
    }
}


private void NotifyPropertyChanged(string property)
{
    if (PropertyChanged != null)
         PropertyChanged(this, new PropertyChangedEventArgs(property));
}

or for less code bloat

set 
{
    m_myString = value;
    NotifyPropertyChanged("MyString");
}
Val
Val. In a multithreaded application you should take a local copy of PropertyChanged before calling it. The reason being the code "if (PropertyChanged != null)" may pass bu by the time u call PropertyChanged it could be null. Extreme edge case i know but can be very hard to debug.
Simon
I hadn't considered that scenario. Thanks for the heads up.
Val
Simon has a good point, but in general property change notifications are only allowed on the UI thread. If the thread that is modifying the `PropertyChanged` event is *not* the same thread that is raising the event, you probably have a bug in your code.
Gabe
+1  A: 

Have a go of this http://code.google.com/p/notifypropertyweaver/

All you need to do is implement INotifyPropertyChanged

So your code will look like

public MainFormViewModel : INotifyPropertyChanged
{
    public String StatusText {get; set;}

    #region INotifyPropertyChanged Implementation
}

The build task will compile this (you never see the below code)

public MainFormViewModel : INotifyPropertyChanged
{
    public String StatusText {get; set;}
    private string statusText;

    public string StatusText 
    {
       get { return statusText; }
       set
       {
           if (value!= statusText)
           {
               statusText = value;
               OnPropertyChanged("StatusText");
           }
       }
    }

    #region INotifyPropertyChanged Implementation
}
Simon
A: 

I have a base class called "Model". It exposes a protected object called DataPoints, which is essentially a dictionary.

C#

public String StatusText {
    get { 
        return (string)DataPoints["StatusText"]; 
    } 
    set { 
        DataPoints["StatusText"] = value; 
    }
}

VB

public Property StatusText as String 
    get 
        return DataPoints!StatusText 
    end get 
    set
        DataPoints!StatusText = value
    end set
end property

When you set a value in the DataPoints dictionary it does the following:

  1. Checks to make sure the value actually changed.
  2. Saves the new value
  3. Sets the IsDirty property to true.
  4. Raises the Property Changed event for the named property as well as the IsDirty and IsValid properties.

Since it is a dictionary, it also makes loading objects from a database or XML file really easy.

Now you may think reading and writing to dictionary is expensive, but I've been doing a lot of performance testing and I haven't found any noticable impact from this in my WPF applications.

Jonathan Allen