Design Pattern (2), MVVM

Introduction

 
After writing Design Pattern, MVC, my next design pattern article had to be MVVM for sure.  Especially, I used this pattern and tried to train my team to use this pattern several years ago. However, I wonder, for this important, popular, and valuable pattern, why Microsoft does not make a physical pattern like MVC (for MVC, we have MS Visual Studio Web MVC module for more than 10 years, actually from 2008).  On the other hand, if we search online, we can get a lot of papers to introduce and discuss MVVM pattern.
 
I realize the MVVM pattern still lives while Microsoft does not have a formal physical pattern (although there was one call MVVM Light ToolKit for WPF and Silverlight for a while) must be because this pattern is quite complex with more variations, therefore it is hard to make a formal pattern.
 
That is true, when I tried to discuss MVVM pattern, it seems to me that the first thing I need to do is to define the pattern, ..., so, this article will be starting by discussing the definition of MVVM, and then giving a simplest sample to demo MVVM.
 
The discussions in this article will be as following,
  • A: History
  • B: What is MVVM Pattern:
  • C: Make a Project Demo
    • C-0: Make a WPF app
    • C-1: Make a Separation between Model and View
    • C-2: Make Synchronization between ViewModel and View
    • C-3: Use ICommand from View to Call back
  • D: What is MVVM Pattern again:
    • Physical module to Conceptual module

A: History

 
Model–view–viewmodel (MVVM) is a software architectural pattern that facilitates the separation of the development of the graphical user interface (the view) from the development of the business logic or back-end logic (the model) so that the view is not dependent on any specific model platform.
 
The view model of MVVM is a value converter, meaning the view model is responsible for exposing (converting) the data objects from the model in such a way that objects are easily managed and presented. In this respect, the view model is more model than view, and handles most if not all of the view's display logic. The view model may implement a mediator pattern, organizing access to the back-end logic around the set of use cases supported by the view.
 
MVVM was invented by Microsoft architects Ken Cooper and Ted Peters specifically to simplify event-driven programming of user interfaces. The pattern was incorporated into WPF and Silverlight. John Gossman, one of Microsoft's WPF and Silverlight architects, announced MVVM on his blog in 2005.[4]
 
Model–view–viewmodel is also referred to as model–view–binder, especially in implementations not involving the .NET platform.
 

B: What is MVVM

 
 
MVVM is a variation of MVC module, we can say MVVM module is derived from MVC in concept.  The difference is that MVVM used most likely for WPF or Silverlight apps without an endpoint for controller access, but with strong data binding between the View and middle tier, the "Controller".  So, this introduces the ViewModel to replace the position of Controller of MVC module.
 
MVVM stands for Model, View, and ViewModel. MVVM separates an application into three components - Model, View, and ViewModel, associated with One Operation: Data Binding that have two features: Synchronization and Command Calling Back.
 
Three Components of MVVM: the following understanding it from my previous article: Design Pattern (1), MVC
  • Model: What --- Data
  • View: What Look like --- Format (with data)
  • ViewModel: How --- operation through Data Binding
One Operation of MVVM with Two Features,
  • Data Binding: Between View and ViewModel with two features:
    • Synchronization: View and ViewModel (ViewModel and Model are always synchronized)
    • Command: Calling back from View to ViewModel 
Conceptual Module
 
MVVM.jpg
Note:
For the conceptual model above, someone argued that the MVVM model is a front end model, it should not directly talk to Database as the graph shown above. That is true, the MVVM model and the MVC model are all front end model for sure. MVC is a new replacement of the old ASP.NET web application, while MVVM is a new version of Windows Forms, and on the top of WPF. At this point, if we say the MVVM model is a data holder in this model, and it links to Data Source, provided such as by Web API or some other middle tier. I have modified the photo a little bit below to make the point clear:
 
 
We will be based on the MVVM conceptual module to build out physical module.
 

C: Make a MVVM WPF Demo

 
From online, a lot of articles claim that they made a simplest sample of MVVM pattern, which suggests how difficult to make a simple model for MVVM.  In this article, we want to introduce the concept of the MVVM, so we want the simplest model.  Instead of making one, we use one from MVVM for Beginners (thanks to the author). With some explanations, we try to make the context clue very clear to demo the MVVM concept.
  • Step 1: Create a View
  • Step 2: Create a Model
  • Step 3: Create a Binder
  • Step 4: Create a DataContext
  • Step 5: Create a ViewModel
  • Step 6: Create a Synchronization
  • Step 7: Create a Command
C-0: Create a WPF app
 
We use the current version of Visual Studio 2019 16.9.4 and NET Framework 4.8 to build the app:
  • Start Visual Studio and select Create a new project.
  • In the Create a new project dialog,
    • on the left side panel select Windows,
    • on the right Select WPF Application (.NET Framework)
    • Named as SamplesMVVM  > OK.
Step 1: Create a View
 
Open MainWidow.xaml and add replace the <Grid> tag with:
  1. <Grid>  
  2.     <Grid.ColumnDefinitions>  
  3.         <ColumnDefinition Width="150"/>  
  4.         <ColumnDefinition Width="5"/>  
  5.         <ColumnDefinition Width="*"/>  
  6.     </Grid.ColumnDefinitions>  
  7.   
  8.     <DockPanel>  
  9.         <TextBlock Text="Added Names"  
  10.         DockPanel.Dock="Top" Margin="5,3"/>  
  11.         <ListBox></ListBox>  
  12.     </DockPanel>  
  13.   
  14.     <GridSplitter Grid.Column="1"  
  15.     VerticalAlignment="Stretch" Width="5"  
  16.      Background="Gray" HorizontalAlignment="Left" />  
  17.   
  18.     <Grid Grid.Column="2">  
  19.         <Grid.ColumnDefinitions>  
  20.             <ColumnDefinition Width="Auto"/>  
  21.             <ColumnDefinition Width="*"/>  
  22.         </Grid.ColumnDefinitions>  
  23.         <Grid.RowDefinitions>  
  24.             <RowDefinition Height="Auto"/>  
  25.             <RowDefinition Height="Auto"/>  
  26.             <RowDefinition Height="Auto"/>  
  27.         </Grid.RowDefinitions>  
  28.   
  29.         <TextBlock Grid.Row="0" Text="Name" Margin="5,3"/>  
  30.         <TextBox Grid.Row="0" Grid.Column="1" Margin="5,3"/>  
  31.   
  32.         <TextBlock Grid.Row="1" Text="Your name is:" Margin="5,3"/>  
  33.         <TextBlock Grid.Row="1" Grid.Column="1" Margin="5,3"/>  
  34.   
  35.         <Button Grid.Row="2" Grid.ColumnSpan="2" HorizontalAlignment="Left"  
  36.          Content="Add Me" Margin="5,3" MinWidth="75" />  
  37.   
  38.     </Grid>  
  39.   
  40. </Grid> 
Run the app (F5), you should see the following,
 
Image 1
 
When one clicks the button or edits the text box, nothing happens! Let's fix that in the following steps.
 
C-1: Make a Separation between Model and View
 
Step 2: Create a Model
 
Right Click project > Add > Class, Choose Class Name as Model.cs
  1. public class Model  
  2. {  
  3.     public string CurrentName { get; set; }  
  4.   
  5.     public ObservableCollection<string> AddedNames { get; } = new ObservableCollection<string>();  
Step 3: Create a Binder
 
We modify MainWidow.xaml file to bind the list box, TextBox and BlockBox to the Model Data: 
  1. <Grid>    
  2.     <Grid.ColumnDefinitions>    
  3.         <ColumnDefinition Width="150"/>    
  4.         <ColumnDefinition Width="5"/>    
  5.         <ColumnDefinition Width="*"/>    
  6.     </Grid.ColumnDefinitions>    
  7.     
  8.     <DockPanel>    
  9.         <TextBlock Text="Added Names" DockPanel.Dock="Top" Margin="5,3"/>   
  10.         <!--<ListBox></ListBox>-->  
  11.         <ListBox ItemsSource="{Binding AddedNames}"></ListBox>    
  12.     </DockPanel>    
  13.     
  14.     <GridSplitter Grid.Column="1"    
  15.             VerticalAlignment="Stretch" Width="5"    
  16.              Background="Gray" HorizontalAlignment="Left" />    
  17.     
  18.     <Grid Grid.Column="2">    
  19.         <Grid.ColumnDefinitions>    
  20.             <ColumnDefinition Width="Auto"/>    
  21.             <ColumnDefinition Width="*"/>    
  22.         </Grid.ColumnDefinitions>    
  23.         <Grid.RowDefinitions>    
  24.             <RowDefinition Height="Auto"/>    
  25.             <RowDefinition Height="Auto"/>    
  26.             <RowDefinition Height="Auto"/>    
  27.         </Grid.RowDefinitions>    
  28.     
  29.         <!--<TextBlock Grid.Row="0" Text="Name" Margin="5,3"/>    
  30.         <TextBox Grid.Row="0" Grid.Column="1" Margin="5,3"/>    
  31.     
  32.         <TextBlock Grid.Row="1" Text="Your name is:" Margin="5,3"/>    
  33.         <TextBlock Grid.Row="1" Grid.Column="1" Margin="5,3"/>-->    
  34.     
  35.         <TextBlock Grid.Row="0" Text="Name" Margin="5,3"/>    
  36.         <TextBox Grid.Row="0" Grid.Column="1"     
  37.          Text="{Binding CurrentName,     
  38.          UpdateSourceTrigger=PropertyChanged}" Margin="5,3"/>    
  39.     
  40.         <TextBlock Grid.Row="1" Text="Your name is:" Margin="5,3"/>    
  41.         <TextBlock Grid.Row="1" Grid.Column="1"     
  42.         Text="{Binding CurrentName}" Margin="5,3"/>    
  43.     
  44.         <Button Grid.Row="2" Grid.ColumnSpan="2" HorizontalAlignment="Left"    
  45.                 Content="Add Me" Margin="5,3" MinWidth="75" />     
  46.     </Grid>     
  47. </Grid>   
Step 4: Create a DataContext
 
Now, we have to associate this model with the view, we can simply bind them together through the DataContext property that is a special property that will flow through all elements of the visual tree. We set it on the MainWindow constructor, it will be available to all controls. 
  1. public partial class MainWindow : Window  
  2. {  
  3.     public MainWindow()  
  4.     {  
  5.         InitializeComponent();  
  6.   
  7.         this.DataContext = new Model();  
  8.         //this.DataContext = new ViewModel(new Model());  
  9.     }  
Now, run the app, if we type a name in the name box, such like Henry, we will see the name in the BlockBox below,
 
 
Here, there is a problem that we bind the front end, the View, directly to the back end data, the Model, by MVC principle, the View and Model should be separated by a middle operational tier, the Controller, otherwise, the operational work will either be mixed in Model, which will break the rule that Model only holds the data and its operation, i.e., business rule; or be mixed in the View, which will also break the SoC rule.
 
Let us fix it by making a middle operational tier, ViewModel:
 
Step 5: Create a ViewModel
 
Now, we create a middle operational tier, ViewModel. Now, it will only include the proxy of the model, while later on it will include all operational action (not business related) in it, such as Synchronization by INotifyPropertyChanged interface or Command Calling by ICommand:
  1. public class ViewModel
  2. {  
  3.     private Model _model;  
  4.     public ViewModel(Model model)  
  5.     {  
  6.         _model = model;  
  7.     }  
  8.   
  9.     public string CurrentName  
  10.     {  
  11.         get { return _model.CurrentName; }  
  12.         set  
  13.         {  
  14.             if (value == _model.CurrentName)  
  15.                 return;  
  16.             _model.CurrentName = value;  
  17.         }  
  18.     }  
  19.   
  20.     public ObservableCollection<string> AddedNames   
  21.     {  
  22.         get { return _model.AddedNames; }   
  23.     }   
  24. }
Step 4-1: Modify DataContext to ViewModel
 
We re-bind the View to the ViewModel, instead of Model directly:
  1. public partial class MainWindow : Window  
  2. {  
  3.     public MainWindow()  
  4.     {  
  5.         InitializeComponent();  
  6.   
  7.         //this.DataContext = new Model();  
  8.         this.DataContext = new ViewModel(new Model());  
  9.   
  10.     }  
Now, the result will the same as previously, however, the concept is totally different that we introduce a middle tier ViewModel, and Model and View are separated.
 
C-2: Make Synchronization between ViewModel and View
 
From Step 1, we have successfully separated View and Model to each other.  At the same time, when we run the app, we found out the input name synchronously appears on the BlockBox below the Text Input Box.  However, this synchronous action might not happen for all controls on the View, for example, the Add Me button is not affected. We need to implement the INotifyPropertyChanged or/and INotifyCollectionChanged interfaces to make them happen.
 
Note
For more info or test case, please see Explain INotifyPropertyChanged In WPF - MVVM --- c-sharpcorner.
 
Step 6: Make a Synchronization
 
Let ViewModel derived from INotifyPropertyChanged interface to enforce the notification to the View when ViewModel has any changes.
  1. public class ViewModel : INotifyPropertyChanged  
  2. {  
  3.     private Model _model;  
  4.     public ViewModel(Model model)  
  5.     {   
  6.         _model = model;  
  7.     }  
  8.   
  9.     public string CurrentName  
  10.     {  
  11.         get { return _model.CurrentName; }  
  12.         set  
  13.         {  
  14.             if (value == _model.CurrentName)  
  15.                 return;  
  16.             _model.CurrentName = value;  
  17.             OnPropertyChanged();  
  18.         }  
  19.     }  
  20.   
  21.     public ObservableCollection<string> AddedNames   
  22.     {  
  23.         get { return _model.AddedNames; }   
  24.     }  
  25.   
  26.     public event PropertyChangedEventHandler PropertyChanged;  
  27.   
  28.     void OnPropertyChanged([CallerMemberName] string propertyName = null)  
  29.     {  
  30.         PropertyChanged?.Invoke(thisnew PropertyChangedEventArgs(propertyName));  
  31.     }  
C-3: Make Command from View to Call back
 
Step 7: Make a Command:
 
Button and other actionable items (such as MenuItem) work through an interface named ICommand. One glaring omission of WPF is that it doesn't provide an out of the box, simple, model friendly ICommand implementation. One could refer here to the seminal RelayCommand by Josh Smith... but since it's a very simple interface and I don't want to use any third party, we are just going to implement it inline.... Add this to the Model class: 
  1. public ViewModel(Model model)  
  2. {  
  3.     AddCommand = new AddNameCommand(this);  
  4. }  
  5.   
  6. class AddNameCommand : ICommand  
  7. {  
  8.     ViewModel parent;  
  9.   
  10.     public AddNameCommand(ViewModel parent)  
  11.     {  
  12.         this.parent = parent;  
  13.         parent.PropertyChanged += delegate { CanExecuteChanged?.Invoke(this, EventArgs.Empty); };  
  14.     }  
  15.   
  16.     public event EventHandler CanExecuteChanged;  
  17.   
  18.     public bool CanExecute(object parameter) { return !string.IsNullOrEmpty(parent.CurrentName); }  
  19.   
  20.     public void Execute(object parameter)  
  21.     {  
  22.         parent.AddedNames.Add(parent.CurrentName); ;  
  23.         parent.CurrentName = null;  
  24.     }  
  25. }  
  26.   
  27. public ICommand AddCommand { get; private set; } 
The final code for ViewModel will be like this
 
  1. using System;    
  2. using System.Collections.ObjectModel;    
  3. using System.ComponentModel;    
  4. using System.Runtime.CompilerServices;    
  5. using System.Windows.Input;   
  6.     
  7. namespace SimplestMVVM    
  8. {    
  9.     public class ViewModel : INotifyPropertyChanged    
  10.     {    
  11.         private Model _model;    
  12.         public ViewModel(Model model)    
  13.         {    
  14.             AddCommand = new AddNameCommand(this);    
  15.             _model = model;    
  16.         }    
  17.     
  18.         public string CurrentName    
  19.         {    
  20.             get { return _model.CurrentName; }    
  21.             set    
  22.             {    
  23.                 if (value == _model.CurrentName)    
  24.                     return;    
  25.                 _model.CurrentName = value;    
  26.                 OnPropertyChanged();    
  27.             }    
  28.         }    
  29.     
  30.         public ObservableCollection<string> AddedNames     
  31.         {    
  32.             get { return _model.AddedNames; }     
  33.         }    
  34.     
  35.         public event PropertyChangedEventHandler PropertyChanged;    
  36.     
  37.         void OnPropertyChanged([CallerMemberName] string propertyName = null)    
  38.         {    
  39.             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));    
  40.         }    
  41.     
  42.         class AddNameCommand : ICommand    
  43.         {    
  44.             ViewModel parent;    
  45.     
  46.             public AddNameCommand(ViewModel parent)    
  47.             {    
  48.                 this.parent = parent;    
  49.                 parent.PropertyChanged += delegate { CanExecuteChanged?.Invoke(this, EventArgs.Empty); };    
  50.             }    
  51.     
  52.             public event EventHandler CanExecuteChanged;    
  53.     
  54.             public bool CanExecute(object parameter) { return !string.IsNullOrEmpty(parent.CurrentName); }    
  55.     
  56.             public void Execute(object parameter)    
  57.             {    
  58.                 parent.AddedNames.Add(parent.CurrentName); ;    
  59.                 parent.CurrentName = null;    
  60.             }    
  61.         }    
  62.     
  63.         public ICommand AddCommand { get; private set; }    
  64.     }    
  65. }    
Run the app: when with no name typed in, the Add Me button is gray out,
 
 
 
while when a name typed in, the Add Me button is on, Click the button, the item will be added into the List Box on the left panel:
 
 

D: What is MVVM again

 
Accordingly, the physical module we built, we do not agree the following concept module of MVVM:
 
 
Out modifications are,
  • ViewModel and Model are always synchronized;
  • Notification to client (View) happens only in the middle tier, ViewModel, instead of in Model.
 

Summary

 
This article discussed MVVM design pattern,
 
Three Components of MVVM:
  • Model: What --- Data
  • View: What Look like --- Format (with data)
  • ViewModel: How --- operation through Data Binding
One Operation of MVVM with Two Features:
  • Data Binding: Between View and ViewModel with two features:
    • Synchronization: View and ViewModel (ViewModel and Model are always synchronized)
    • Command: Calling back from View to ViewModel
Reference
 
Bind and trigger the binding,
INotifyPropertyChanged or INotifyCollectionChanged Interfaces, and samples:
The samples for implementations of MVVM:


Similar Articles