How to write a ViewModelBase in MVVM

Viewed 49273

I'm pretty new in WPF programming environment. I'm trying to write a program out using MVVM design pattern.

I've did some studies and read up some articles related to it and many of a time I came across this thing called

ViewModelBase

I know what it is.. But may I know specifically where should I begin with to be able to write out my own ViewModelBase? Like... Really understanding what's happening without getting too complicated. Thank you :)

7 Answers

The below class can be used as a ViewModelBase in WPF projects:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    /// <summary>
    /// Multicast event for property change notifications.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Checks if a property already matches the desired value.  Sets the property and
    /// notifies listeners only when necessary.
    /// </summary>
    /// <typeparam name="T">Type of the property.</typeparam>
    /// <param name="storage">Reference to a property with both getter and setter.</param>
    /// <param name="value">Desired value for the property.</param>
    /// <param name="propertyName">Name of the property used to notify listeners.This
    /// value is optional and can be provided automatically when invoked from compilers that
    /// support CallerMemberName.</param>
    /// <returns>True if the value was changed, false if the existing value matched the
    /// desired value.</returns>
    protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(storage, value)) return false;
        storage = value;
        // Log.DebugFormat("{0}.{1} = {2}", this.GetType().Name, propertyName, storage);
        this.OnPropertyChanged(propertyName);
        return true;
    }

    /// <summary>
    /// Notifies listeners that a property value has changed.
    /// </summary>
    /// <param name="propertyName">Name of the property used to notify listeners.  This
    /// value is optional and can be provided automatically when invoked from compilers
    /// that support <see cref="CallerMemberNameAttribute"/>.</param>
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
    }
}

And an example of ViewModel class is:

public class MyViewModel : ViewModelBase
{
    private int myProperty;
    public int MyProperty
    {
        get { return myProperty; }
        set { SetProperty(ref myProperty, value); }
    }
}

For ease of writing, below snippet can be used:

<?xml version="1.0" encoding="utf-8"?>  
<CodeSnippets  
    xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">  
    <CodeSnippet Format="1.0.0">  
        <Header>  
            <Title>OnPropertyChanged</Title>  
        </Header>      
      <Snippet>
          <SnippetTypes>
            <SnippetType>SurroundsWith</SnippetType>
            <SnippetType>Expansion</SnippetType>
          </SnippetTypes>
          <Declarations>
            <Literal>
              <ID>TYPE</ID>
              <ToolTip>Property type</ToolTip>
              <Default>int</Default>
            </Literal>
            <Literal>
              <ID>NAME1</ID>
              <ToolTip>Property name</ToolTip>
              <Default>MyProperty</Default>
            </Literal>
          </Declarations>
            <Code Language="CSharp">  
                <![CDATA[private $TYPE$ _$NAME1$;
public $TYPE$ $NAME1$
{
    get => _$NAME1$; 
    set => SetProperty(ref _$NAME1$, value);    
}]]>  
            </Code>  
        </Snippet>  
    </CodeSnippet>  
</CodeSnippets>  

The full code could be downloaded from here.

To revisit this answer today, I'd like to offer additional productivity improvements when writing MVVM code for Visual Studio.

The Intellisense in Visual Studio can automatically create the SetProperty boilerplate method. To do so, I set the ViewModel in the XAML of my Window (see below). Then, whenever I reference a {Binding Path=NewProperty}, Right Click and Select Quick Actions and Refactoring... (or via Ctrl .). If the SetProperty method isn't made, it will automatically be created for you within your ViewModel class. Further, it'll generate the property and field required for the Binding.

<Window x:Class="My.Project.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:My.Project"
        mc:Ignorable="d"
        xmlns:viewmodel="clr-namespace:My.Project.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodel:MainWindowViewModel}"

        Title="MainWindow" Height="360" Width="1000">
...
</Window>

However, this method has drawbacks

  1. The INotifyPropertyChanged is not implemented and the OnPropertyChanged method is not implemented (if you need it)
  2. This would need to be done in every ViewModel
  3. This is specific for Visual Studio

Benefits:

  1. Once the SetProperty method is defined in the project, using the Quick Actions and Refactoring... option will only generate the necessary property and field for you. This also works if you inherit from a ViewModelBase.

Here is the SetProperty method as generated by Visual Studio.

protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
    if (object.Equals(storage, value)) return false;
    storage = value;
    // Log.DebugFormat("{0}.{1} = {2}", this.GetType().Name, propertyName, storage);
    this.OnPropertyChanged(propertyName);
    return true;
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using System.Runtime.CompilerServices;
using System.ComponentModel;
namespace CollectionViewMVVM.ViewModel
{
public class BaseViewModel : INotifyPropertyChanged
{
    /*Referencia: https://www.youtube.com/watch?v=PXEt1esjnZ0&ab_channel=Codigo369*/
    public INavigation Navigation;
    public event PropertyChangedEventHandler PropertyChanged;
    public virtual void OnpropertyChanged([CallerMemberName] string nombre = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nombre));
    }

    private ImageSource foto;
    public ImageSource Foto
    {
        get { return foto; }
        set
        {
            foto = value;
            OnpropertyChanged();
        }
    }
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    public async Task DisplayAlert(string title, string message, string cancel)
    {
        await Application.Current.MainPage.DisplayAlert(title, message, cancel);
    }

    public async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
    {
        return await Application.Current.MainPage.DisplayAlert(title, message, accept, cancel);
    }

    protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if(EqualityComparer<T>.Default.Equals(field, value))
        {
            return false;
        }
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
    /*Ejemplo para declarar entrys*/
    private string _title;
    public string Title
    {
        get { return _title; }
        set
        {
            SetProperty(ref _title, value);
        }
    }
    /*Para tipos bool*/
    private bool _isBusy;
    public bool IsBusy
    {
        get { return _isBusy; }
        set
        {

            SetProperty(ref _isBusy, value);
        }
    }

    protected void SetValue<T>(ref T backingFielded, T value, [CallerMemberName] string propertyName = null)
    {
        if(EqualityComparer<T>.Default.Equals(backingFielded, value))
        {
            return;
        }
        backingFielded = value;
        OnPropertyChanged(propertyName);
    }
}

}

Related