Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get rid of repetitive properties in WPF MVVM viewmodels

Tags:

c#

mvvm

wpf

prism

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");
        }
    }
}
like image 204
uncletall Avatar asked Oct 22 '13 01:10

uncletall


People also ask

What changes Inotify property?

The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed. For example, consider a Person object with a property called FirstName .

Should ViewModel contain business logic?

ViewModel contains the business logic, which manipulates the row data to show in the view. Any kind of function and methods should be in the view model.

What are attached properties in WPF?

An attached property is a Extensible Application Markup Language (XAML) concept. Attached properties enable extra property/value pairs to be set on any XAML element that derives from DependencyObject, even though the element doesn't define those extra properties in its object model.


2 Answers

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).

like image 140
Dan Puzey Avatar answered Oct 26 '22 09:10

Dan Puzey


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

like image 40
uncletall Avatar answered Oct 26 '22 08:10

uncletall