Reducing MVVM Light Viewmodel Code with Fody Property Dependencies and Custom Property Changed Methods

In other previous articles I’ve written about Fody and this article we’ll see how we can use the PropertyChanged.Fody addin with MVVM Light viewmodels.

Windows Store app screenshot

The preceding screenshot shows the app in action: a name TextBox is twoway data-bound to the viewmodel, when a different name is typed, the TextBlock shows the uppercase version.

The user can also type a color into the color box and a bit of reflection happens to convert this string to a color from the Colors class.

Windows Store app screenshot with different bg color

The following code shows the (non Fody-fied) viewmodel:

More...

SHARE:

Say Goodbye to Boring INotifyPropertyChanged Implementation in Universal Windows Apps with Fody

We need to implement INotifyPropertyChanged to allow bindings to be updated. For example in a simple viewmodel to add 2 numbers:

namespace UniFodyDemo
{    
    internal class CalculatorViewModel : INotifyPropertyChanged
    {
        private ICommand _addCommand;
        private string _firstNumber;
        private string _secondNumber;

        public CalculatorViewModel()
        {
            FirstNumber = "5";
            SecondNumber = "10";
            Result = "";

            AddCommand = new RelayCommand(Add);
        }


        // Use strings to simplify demo code (so no val converter)
        public string FirstNumber
        {
            get { return _firstNumber; }
            set
            {
                _firstNumber = value;
                OnPropertyChanged();
            }
        }

        public string SecondNumber
        {
            get { return _secondNumber; }
            set
            {
                _secondNumber = value;
                OnPropertyChanged();
            }
        }

        public string Result { get; set; }

        public ICommand AddCommand
        {
            get { return _addCommand; }
            private set
            {
                _addCommand = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void Add(object obj)
        {
            var a = int.Parse(FirstNumber);
            var b = int.Parse(SecondNumber);

            Result = (a + b).ToString();
        }

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

There’s more code here than we’d need if we didn’t have to implement INotifyPropertyChanged. Really the important things are the 3 properties that we bind to and the add command / method.

The XAML in both the Windows Store app and Windows Phone app looks like:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel Width="200">
        <TextBlock Text="{Binding FirstNumber, Mode=TwoWay}"></TextBlock>
        <TextBlock Text="{Binding SecondNumber, Mode=TwoWay}"></TextBlock>
        <Button Command="{Binding AddCommand}">Add</Button>
        <TextBlock Text="{Binding Result, Mode=TwoWay}"></TextBlock>
    </StackPanel>
</Grid>

Without implementing INotifyProperty changed, clicking the Add button won’t be of much use, we wont see the result.

The solution looks like the following, the shared project contains the viewmodel.

image

It would be great not have to write this same INotifyProperty stuff for everything we want to bind to/from. Using a NuGet package called PropertyChanged.Fody we can achieve this.

First this package needs to be installed into both the Windows Store and Windows Phone app projects. This is because the shared code in the viewmodel isn’t compiled in that shared project, rather the source code is compiled individually in both the Store/Phone projects.

Now the viewmodel can be simplified:

namespace UniFodyDemo
{
    [ImplementPropertyChanged]
    class CalculatorViewModel
    {
        public CalculatorViewModel()
        {
            FirstNumber = "5";
            SecondNumber = "10";
            Result = "";

            AddCommand = new RelayCommand(Add);
        }

        private void Add(object obj)
        {
            var a = int.Parse(FirstNumber);
            var b = int.Parse(SecondNumber);

            Result = (a + b).ToString();
        }
        

        // Use strings to simplify demo code (so no val converter)
        public string FirstNumber { get; set; }
        public string SecondNumber { get; set; }
        public string Result { get; set; }

        public ICommand AddCommand { get; private set; }
    }
}

Notice the [ImplementPropertyChanged] attribute which means INotifyPropertyChanged will be automatically implemented for us. Notice that it’s much easier to see what’s essential in the viewmodel without all the noise of INotifyPropertyChanged.

If we decompile the Windows store app we can see the implementation:

namespace UniFodyDemo
{
    internal class CalculatorViewModel : INotifyPropertyChanged
    {
        public ICommand AddCommand
        {
            get
            {
                return this.u003cAddCommandu003ek__BackingField;
            }
            private set
            {
                if (this.u003cAddCommandu003ek__BackingField == value)
                {
                    return;
                }
                this.u003cAddCommandu003ek__BackingField = value;
                this.OnPropertyChanged("AddCommand");
            }
        }

        public string FirstNumber
        {
            get
            {
                return this.u003cFirstNumberu003ek__BackingField;
            }
            set
            {
                if (String.Equals(this.u003cFirstNumberu003ek__BackingField, value, 4))
                {
                    return;
                }
                this.u003cFirstNumberu003ek__BackingField = value;
                this.OnPropertyChanged("FirstNumber");
            }
        }

        public string Result
        {
            get
            {
                return this.u003cResultu003ek__BackingField;
            }
            set
            {
                if (String.Equals(this.u003cResultu003ek__BackingField, value, 4))
                {
                    return;
                }
                this.u003cResultu003ek__BackingField = value;
                this.OnPropertyChanged("Result");
            }
        }

        public string SecondNumber
        {
            get
            {
                return this.u003cSecondNumberu003ek__BackingField;
            }
            set
            {
                if (String.Equals(this.u003cSecondNumberu003ek__BackingField, value, 4))
                {
                    return;
                }
                this.u003cSecondNumberu003ek__BackingField = value;
                this.OnPropertyChanged("SecondNumber");
            }
        }

        public CalculatorViewModel()
        {
            this.FirstNumber = "5";
            this.SecondNumber = "10";
            this.Result = "";
            this.AddCommand = new RelayCommand(new Action<object>(this, CalculatorViewModel.Add));
        }

        private void Add(object obj)
        {
            int num = Int32.Parse(this.FirstNumber);
            int num1 = Int32.Parse(this.SecondNumber);
            this.Result = (num + num1).ToString();
        }

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

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

So not only with Universal projects can we share code via the shared code project, adding Fody into the mix is even more awesome.

For a list of available addins or to contribute (or even create your own addin) check out the GitHub project site.

Also to learn more about Fody feel free to check out my Pluralsight course.

SHARE:

Write Less Repetitive Boilerplate Code with Fody

My newest Pluralsight course on Fody was just released.

Fody is a tool that frees us up from having to write repetitive boilerplate code. For example we can automate IDisposable implementation or automatically add Equals(), GetHashCode() and equality operators.

Fody is not one tool, but a collection of individual “addins” around the core Fody, with the core GitHub members: Simon Cropp, Rafał Jasica, and Cameron MacFarland.

Automatically Implementing Equality

As an example of what’s possible with Fody, we can auto-implement equality.

So first off we install the Equals Fody addin via NuGet:

PM> Install-Package Equals.Fody

Now we can add the [Equals] attribute to indicate that we want to auto-implement equality.

We can opt-out specific properties so they won’t be considered during equality by applying the [IgnoreDuringEquals] attribute.

We can also provide custom equality logic by creating a method and decorating it with the [CustomEqualsInternal] attribute. So for example if either altitude is –1 then consider altitudes equal.

The following code shows these attributes in action:

[Equals]
public class Location
{
    [IgnoreDuringEquals] 
    public int Altitude { get; set; }
    
    public int Lat { get; set; }
    public int Long { get; set; }

    [CustomEqualsInternal]
    private bool CustomEqualsLogic(Location other)
    {
        // this method is evaluated if the other auto-equal properties are equal

        if (this.Altitude == other.Altitude)
        {
            return true;
        }

        if (this.Altitude == -1 || other.Altitude == -1)
        {
            return true;
        }

        return false;
    } 
}

For a list of available addins or to contribute (or even create your own addin) check out the GitHub project site.

To learn more about how Fody works, how to use some of the available addins, and how to create your own check out my Pluralsight course.

SHARE: