How to write a ViewModelBase in MVVM
Asked Answered
P

7

46

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

Papal answered 22/3, 2016 at 8:53 Comment(3)
In most cases it's nothing more just a base implementation of OnNotificationChanged or RaisePropertyChange method, but that should be easily found in dozens of the beginner and purist (w/o a MVVM framework) tutorials. For tutorial purposes it's good, if you want write a complex application you better use a grown up framework. INPC and ICommand are just a tiny part of MVVMVentriloquy
To elaborate on @Tesng's comment you can use PropertyChanged.Fody for implementing INotifyPropertyChanged part. This way the code would be much cleaner and you would not have to extend a class.Georgy
Thanks guys! appreciate it :)Papal
P
137

It's worth nothing to use MVVM frameworks if you don't know what's going on inside.

So let's go step by step and build your own ViewModelBase class.

  1. ViewModelBase is class common for all your viewmodels. Let's move all common logic to this class.

  2. Your ViewModels should implement INotifyPropertyChanged (do you understand why?)

     public abstract class ViewModelBase : INotifyPropertyChanged
     {
         public event PropertyChangedEventHandler PropertyChanged;
    
         protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
         {
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
     }
    

    the [CallerMemberName] attribute is not required, but it will allow you to write: OnPropertyChanged(); instead of OnPropertyChanged("SomeProperty");, so you will avoid string constant in your code. Example:

     public string FirstName
     {
         set
         {
             _firstName = value;
             OnPropertyChanged(); //instead of OnPropertyChanged("FirstName") or OnPropertyChanged(nameof(FirstName))
         }
         get{ return _firstName;}
     }
    

    Please note, that OnPropertyChanged(() => SomeProperty) is no more recommended, since we have nameof operator in C# 6.

  3. It's common practice to implement properties that calls PropertyChanged like this:

     public string FirstName
     {
         get { return _firstName; }
         set { SetProperty(ref _firstName, value); }
     }
    

    Let's define SetProperty in your viewmodelbase:

     protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
     {
         if (EqualityComparer<T>.Default.Equals(storage, value))
             return false;
         storage = value;
         this.OnPropertyChanged(propertyName);
         return true;
     }
    

    It simply fires PropertyChanged event when value of the property changes and returns true. It does not fire the event when the value has not changed and returns false. The basic idea is, that SetProperty method is virtual and you can extend it in more concrete class, e.g to trigger validation, or by calling PropertyChanging event.

This is pretty it. This is all your ViewModelBase should contain at this stage. The rest depends on your project. For example your app uses page base navigation and you have written your own NavigationService for handling navigation from ViewModel. So you can add NavigationService property to your ViewModelBase class, so you will have access to it from all your viewmodels, if you want.

In order to gain more reusability and keep SRP, I have class called BindableBase which is pretty much the implementation of INotifyPropertyChanged as we have done here. I reuse this class in every WPF/UWP/Silverligt/WindowsPhone solution because it's universal.

Then in each project I create custom ViewModelBase class derived from BindableBase:

public abstract ViewModelBase : BindableBase
{
    //project specific logic for all viewmodels. 
    //E.g in this project I want to use EventAggregator heavily:
    public virtual IEventAggregator () => ServiceLocator.GetInstance<IEventAggregator>()   
}

if I have app, that uses page based navigation I also specify base class for page viewmodels.

public abstract PageViewModelBase : ViewModelBase
{
    //for example all my pages have title:
    public string Title {get; private set;}
}

I could have another class for dialogs:

public abstract DialogViewModelBase : ViewModelBase
{
    private bool? _dialogResult;

    public event EventHandler Closing;

    public string Title {get; private set;}
    public ObservableCollection<DialogButton> DialogButtons { get; }

    public bool? DialogResult
    {
        get { return _dialogResult; }
        set { SetProperty(ref _dialogResult, value); }
    }

    public void Close()
    {
        Closing?.Invoke(this, EventArgs.Empty);
    }
}
Prostration answered 22/3, 2016 at 9:59 Comment(2)
man... thanks for sharing! It definitely gave me a better understanding of what this thing is all about haha... appreciate that :)Papal
Is it safe to say that where you wrote "BinableBase", you actually mean "BindableBase"? I would've edited the post myself, but since the typo is in the code, and since "you just never know", I figured I'd leave that to you.Pacifism
C
13

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.

Coel answered 17/12, 2018 at 18:50 Comment(0)
M
6

You have some nuget package to implement MVVM

  1. MVVM light
  2. MVVM Cross
  3. Prism

For me the easier for a beginner is MVVM light because it provide some code sample.

So the better is to install this nuget package, have a look about the generated code and back to us for more explanations if you need.

Masterful answered 22/3, 2016 at 8:56 Comment(4)
I was going to say something about NuGet but I saw your bit about "have a look about the generated code and back to us for more explanations if you need". :)Mcfadden
haha... icic... thx man! will try to look into that :) will definitely come back for more questions.Papal
Prism is not mvvm framework, forget about it. Prism.mvvm is and it is trully lightweight.Prostration
as of nuget Packages: I use PropertyChanged.Fody (uses Fody Weaver). No Subclassing of a base class needed, either you implement InotifyPropertichanged, or add an attribute to the class (AddnotifyPropertychangedinterface). Compiler genereates all the boilerplate needed, and it has some nice features, e.g. call Event on any other Prop in the class and u can use Autoproperties all along your Class.Annulment
F
2

In most MVVM frameworks, the base ViewModel classes actually contain very little code - usually just an implementation of INotifyPropertyChanged and some helper functions.

Take a look at the source code for MVVM Light's ViewModelBase and ObservableObject classes. ObservableObject is mostly the INotifyPropertyChanged implementation - using a lambda expression rather than "magic strings" for the property name. ViewModelBase extends ObservableObject and is mostly a utility method to determine if you're running inside the Visual Studio designer

Febri answered 22/3, 2016 at 10:9 Comment(0)
S
1

I like this BaseVewModel it gives a nice clean style to your view models. Check out the various 'before' and 'after' comparisons. Of course, nothing is mandatory - if you don't like a feature that the BaseViewModel provides then don't use it. Or modify it because you have the source code. In particular note that there are three different ways to implement properties with change notification - choose the level of sophistication that you understand/feel comfortable with.

Sciatica answered 22/3, 2016 at 10:31 Comment(2)
This post contains nothing that directly answers the question at hand. It should either be a comment, or you should make sure to include all salient details in the post itself, using the link only as a supplemental reference.Pacifism
Edit your answer pls. I really want to see that clean variant of BaseViewModel.Their
D
0

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;
}
Doorknob answered 13/5, 2021 at 12:12 Comment(0)
I
-1
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);
    }
}

}

Introgression answered 31/7, 2022 at 1:28 Comment(2)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Peeper
This answer does not provide any additional information to existing answers and it does not have any explanation.Prostration

© 2022 - 2024 — McMap. All rights reserved.