8

I am setting up a WPF application with a ViewModel that has lots of properties. These are all very repetitive and I am wondering if there is a way to get rid of this. This is what one property looks like, and I have about 8-10 of them.

public string Name
{
    get
    {
        return this.name;
    }

    set
    {
        if (this.name != value)
        {
            this.name = value;
            this.RaisePropertyChanged("Name");
        }
    }
}
Anatoliy Nikolaev
  • 22,370
  • 15
  • 69
  • 68
uncletall
  • 6,609
  • 1
  • 27
  • 52
  • 1
    possible duplicate of [Implementing INotifyPropertyChanged - does a better way exist?](http://stackoverflow.com/questions/1315621/implementing-inotifypropertychanged-does-a-better-way-exist) – McGarnagle Oct 22 '13 at 01:55
  • Thanks, interesting read.. – uncletall Oct 22 '13 at 02:03
  • Indeed ... I guess the complication comes with deciding what "equals" means. For myself, I think I'd rather live with the verbose setter (along with a VS code snippet to ease the pain of writing it out repeatedly). – McGarnagle Oct 22 '13 at 02:08
  • In working on an Answer for this question (that I abandoned), I came across UpdateControls, a NuGet package: http://updatecontrols.net/cs/index.shtml From the webpage, "Update Controls does not require that you implement INotifyPropertyChanged or declare a DependencyProperty. It connects controls directly to CLR properties." I will be investigating this package myself. Hopefully you will find this helpful. Also, I found out about UpdateControls here: http://blog.excastle.com/2012/12/16/stop-writing-inotifypropertychanged-start-using-updatecontrols/ – philologon Oct 22 '13 at 02:49

4 Answers4

5

My suggestion, if your requirements are straightforward, would be to go third party. This is a solved problem, thanks to some ingenious people...

The most bare-bones way you can write your code is to remove the INotifyPropertyChanged implementation entirely, and write your properties in the minimal way like this:

public string Name { get; set; }

Then add Fody.PropertyChanged to your project (it's on NuGet) and mark your class with the [ImplementPropertyChanged] attribute.

Fody will do some clever IL magic during compilation that will implement the interface and all of the boilerplate code magically - meaning your written code is as simple as can be, and your end result is exactly what you want.

Note that if you rely on the INotifyPropertyChanged interface elsewhere in your code (that is, if you manually attach to the event in code or similar), you may want to use Fody differently because the IDE won't realise you've got the interface implemented. Fortunately, Fody will also auto-implement in other scenarios too (e.g.: implement INotifyPropertyChanged in a class and Fody will, by default also implement event raising in your properties).

Dan Puzey
  • 33,626
  • 4
  • 73
  • 96
  • Thanks dan, also looks interesting. Seems there is many ways to solve this problem and just looking at a simple thing like this can keep me busy for a few days... Do you know if Fody also works when running builds outside Visual Studio? – uncletall Oct 23 '13 at 01:47
  • Fody certainly works on server and commandline builds too, if that's what you mean. (I believe it injects a post-build step into the project file that allows it to inject the handlers, though I don't have any code handy to confirm that.) – Dan Puzey Oct 23 '13 at 09:41
2

The mentioned thread contains indeed the answer but you need to do some digging. I will show the two best answers I found in there.

The first solution is to implement a ViewModelBase class that encapsulates the set method into a template method and uses lamda expressions to retrieve the Property name so refactoring does not break the property name string.

public class ViewModelBase: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        var body = selectorExpression.Body as MemberExpression;
        if (body == null)
            throw new ArgumentException("The body must be a member expression");
        OnPropertyChanged(body.Member.Name);
    }

    protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(selectorExpression);
        return true;
    }
}

Usage:

class ViewModel : DataBase
{
    private String _prop1;
    public String Prop1
    {
        get { return _prop1; }
        set
        {
            SetField(ref _prop1, value, () => Prop1);
        }
    }
}

The second solution uses a Dictionary to store the properties in the base class. This way we do not need to pass in the old value as it is kept in the base class and we do not need to create member fields to hold the values for the properties. I like this solution the best:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    private readonly Dictionary<string, object> _propertyValueStorage;

    #region Constructor

    protected ViewModelBase()
    {
        this._propertyValueStorage = new Dictionary<string, object>();
    }

    #endregion

    protected void SetValue<T>(Expression<Func<T>> property, T value)
    {
        var lambdaExpression = property as LambdaExpression;

        if (lambdaExpression == null)
        {
            throw new ArgumentException("Invalid lambda expression", "Lambda expression return value can't be null");
        }

        var propertyName = this.getPropertyName(lambdaExpression);
        var storedValue = this.getValue<T>(propertyName);

        if (object.Equals(storedValue, value)) return;

        this._propertyValueStorage[propertyName] = value;
        this.OnPropertyChanged(propertyName);
    }

    protected T GetValue<T>(Expression<Func<T>> property)
    {
        var lambdaExpression = property as LambdaExpression;

        if (lambdaExpression == null)
        {
            throw new ArgumentException("Invalid lambda expression", "Lambda expression return value can't be null");
        }

        var propertyName = this.getPropertyName(lambdaExpression);
        return getValue<T>(propertyName);
    }

    private T getValue<T>(string propertyName)
    {
        object value;
        if (_propertyValueStorage.TryGetValue(propertyName, out value))
        {
            return (T)value;
        }
        return default(T);

    }

    private string getPropertyName(LambdaExpression lambdaExpression)
    {
        MemberExpression memberExpression;

        if (lambdaExpression.Body is UnaryExpression)
        {
            var unaryExpression = lambdaExpression.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambdaExpression.Body as MemberExpression;
        }

        return memberExpression.Member.Name;
    }

    #region < INotifyPropertyChanged > Members

    public event PropertyChangedEventHandler PropertyChanged;

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

    #endregion

}

Usage would be:

public class ViewModel : ViewModelBase
{
    public String Prop1
    {
        get { return GetValue(() => Prop1); }
        set { SetValue(() => Prop1, value); }
    }
    public bool Bool1
    {
        get { return GetValue(() => Bool1); }
        set { SetValue(() => Bool1, value); }
    }

Solution 1 is based on https://stackoverflow.com/a/1316566/2259878 and https://stackoverflow.com/a/1316566/2259878

Solution 2 is based on http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

Community
  • 1
  • 1
uncletall
  • 6,609
  • 1
  • 27
  • 52
  • Worth noting that you can use the .NET4.5 [CallerMemberName](http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute.aspx) attribute instead of resolving the property name from the expression. – Dan Puzey Oct 22 '13 at 13:47
  • You are right, but I am still on 4.0. But just that might be worth getting the upgrade to VS2012/13 pushed through – uncletall Oct 23 '13 at 00:45
  • You can use CallMemeberName with .net4 project in case you will reference Microsoft.BCL.Portability and install KB2468871 – Lonli-Lokli Oct 23 '13 at 13:40
  • Thanks for that, will checkout the KB2468871 – uncletall Oct 24 '13 at 01:39
  • Seems a little bit to complex to get everybody and build servers to use this. – uncletall Oct 24 '13 at 02:50
  • Using that second one. While it feels slightly bad having a list of objects and having properties act only as keys, you're safe from duplicates and a huge amount of boilerplate. – Magus May 01 '14 at 16:30
0

It depends on the requirement, if all the properties are used for same purpose means like name1, name2, name3.....name10, like listing names of 10 people, then put in a another class and bind a collection of the class type to Items-control in your xaml. or simply bind a ObservableCollection of string

But if each of properties has its own purpose then it can not avoid, because Properties are nothing but variables to hold different values. Each property will have it's own intention and operation on each of them will vary in the view model, depending on logic

Kumareshan
  • 1,311
  • 8
  • 8
0

My solution is near to uncletall's but with some changes for usage

    private static readonly Properties<MainWindowViewModel> _properties = new Properties<MainWindowViewModel>();

    public static Property TextProperty = _properties.Create(_ => _.Text);
    private string _text;
    public string Text
    {
        get { return _text; }
        set { SetProperty(ref _text, value, TextProperty); }
    }

XAML:

<Label Grid.Row="1" Content="{Model:PropertyBinding  {x:Static Model:MainWindowViewModel.TextProperty}}" Width="200"/>

Benefit of this sample is compile-time check for changes. Full sample link

Lonli-Lokli
  • 3,357
  • 1
  • 24
  • 40
  • Thanks for the working sample project. But I don't quite understand the compile-time check benefits that you mention, is there any benefits of your solution over mine? But as a disadvantage I see the additional two statements for creating the property and defining the field. – uncletall Oct 23 '13 at 02:02
  • I see a greate benefit in xaml - any property name changes will result in changing xaml as well without run-time errors. Yes, defining _props is required. But anyway code stays readable and understandable. – Lonli-Lokli Oct 23 '13 at 07:38
  • Also, if you want to make a 'magic' then you need to implement your custom solution. It's not easy. We are using our custom Property notifiers in production code, but I cannot share information with you. You have all possible easy-implementing ways as answers to support auto-notify. – Lonli-Lokli Oct 23 '13 at 13:37