INotifyPropertyChanged Interface In MVVM

Introduction

 
Remember last time, when we figured out how to bind data with different modes here.
 
We used Prism's SetProperty method to update UI.
 
What exactly do I mean by updating UI?
 
We can bind properties of ViewModel with View, but is there any way to tell UI that bound properties have been modified so that the UI must update itself?
 
In simple words, we want to trigger UI when any bound value is modified.
 
To achieve this, WPF has introduced the INotifyPropertyChanged interface. It is a contract between view & viewmodel.
 
Let's see this in action.
 
Say we want to register a user & we want to calculate the user's age based on the date of birth entered by the user.
 
The screen will have 4 fields: User Name (TextBox), DOB (DateTimePicker), Age (TextBlock), EmailId (TextBox), Register plus Reset (Button) & response (TextBlock).
 
So the final window would look like this:
 
INotifyPropertyChanged Interface In MVVM
  1. <Window x:Class="A.MainWindow"    
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
  3.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"    
  4.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
  5.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    
  6.         mc:Ignorable="d"    
  7.         xmlns:ViewModel="clr-namespace:A"    
  8.         Title="MainWindow" Height="200" Width="320">    
  9.     <Window.Resources>    
  10.         <ViewModel:MainWindowViewModel x:Key="VM" ></ViewModel:MainWindowViewModel>    
  11.         <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>    
  12.     </Window.Resources>    
  13.     <Grid DataContext="{Binding Source={StaticResource VM}}"     
  14.         HorizontalAlignment="Center">    
  15.         <Grid.RowDefinitions>    
  16.             <RowDefinition Height="Auto"/>    
  17.             <RowDefinition Height="Auto"/>    
  18.             <RowDefinition Height="Auto"/>    
  19.             <RowDefinition Height="Auto"/>    
  20.             <RowDefinition Height="Auto"/>    
  21.             <RowDefinition Height="5*"/>    
  22.         </Grid.RowDefinitions>    
  23.         <Grid.ColumnDefinitions>    
  24.             <ColumnDefinition Width="Auto"/>    
  25.             <ColumnDefinition Width="Auto"/>    
  26.         </Grid.ColumnDefinitions>    
  27.         <Label x:Name="LabelUserName"            
  28.                Content="User Name:"        
  29.                Margin="0 10 0 0"/>    
  30.         <Label x:Name="LabelDOB"             
  31.                Content="DOB:"            
  32.                Grid.Row="1"/>    
  33.         <Label x:Name="LabelAge"             
  34.                Content="Age:"            
  35.                Grid.Row="2"/>    
  36.         <Label x:Name="LabelEmailId"             
  37.                Content="Email:"            
  38.                Grid.Row="3"/>    
  39.     
  40.         <TextBox x:Name="TextBoxUserName"          
  41.                  Text="{Binding UserName}"        
  42.                  Height="20"            
  43.                  Width="150"           
  44.                  Margin="0 10 0 0"         
  45.                  Grid.Column="1"/>    
  46.         <DatePicker x:Name="DatePickerDOB"     
  47.                     SelectedDate="{Binding DOB}"    
  48.                     DisplayDateStart="1/1/1990"    
  49.                     Width="150"    
  50.                     Grid.Column="1"    
  51.                     Grid.Row="1"/>    
  52.         <Rectangle       
  53.                  Stroke="LightGray"    
  54.                  StrokeThickness="1"    
  55.                  Height="20"            
  56.                  Width="150"    
  57.                  Grid.Column="1"            
  58.                  Grid.Row="2"/>    
  59.         <TextBlock  x:Name="TextBlockAge"    
  60.                  Text="{Binding Age}"     
  61.                  Height="20"            
  62.                  Width="150"            
  63.                  Grid.Column="1"            
  64.                  Grid.Row="2"/>    
  65.             
  66.         <TextBox x:Name="TextBoxEmail"     
  67.                  Text="{Binding EmailId}"     
  68.                  Height="20"            
  69.                  Width="150"            
  70.                  Grid.Column="1"            
  71.                  Grid.Row="3"/>    
  72.         <StackPanel x:Name="StackPanelButtons"     
  73.                     Orientation="Horizontal"     
  74.                     Grid.ColumnSpan="2"    
  75.                     Grid.Row="4" >    
  76.         <Button x:Name="ButtonRegister"            
  77.                 Height="20"            
  78.                 Width="100"            
  79.                 Content="Register"            
  80.                 HorizontalAlignment="Center"            
  81.                 Margin="20 10 0 0"          
  82.                 Command="{Binding RegisterButtonClicked}"/>    
  83.             
  84.         <Button x:Name="ButtonReset"            
  85.                 Height="20"            
  86.                 Width="100"            
  87.                 Content="Reset"            
  88.                 HorizontalAlignment="Center"            
  89.                 Margin="20 10 0 0"          
  90.                 Command="{Binding ResetButtonClicked}"/>    
  91.         </StackPanel>    
  92.         <TextBlock x:Name="TextBlockMessage"        
  93.                    HorizontalAlignment="Center"        
  94.                    Margin="20 8 0 0"          
  95.                    Grid.Row="5"        
  96.                    Grid.ColumnSpan="2">    
  97.             <TextBlock.Style>    
  98.                 <Style TargetType="TextBlock">    
  99.                     <Setter Property="Text"    
  100.                                     Value="Enter details to register!">    
  101.                     </Setter>    
  102.                     <Style.Triggers>    
  103.                         <DataTrigger Binding="{Binding IsButtonClicked}" Value="True">    
  104.                             <Setter Property="Text"    
  105.                                     Value="{Binding UserName, StringFormat='User: {0} is successfully registered!'}">    
  106.                             </Setter>    
  107.                         </DataTrigger>    
  108.                     </Style.Triggers>    
  109.                 </Style>    
  110.             </TextBlock.Style>    
  111.         </TextBlock>    
  112.     </Grid>    
  113. </Window>    
Now the ViewModel: MainWindowViewModel will consist of the following:
  • string UserName: User Name (TextBox)
  • DateTime DOB DOB (DateTimePicker)
  • string Age: Age (TextBlock)
  • string EmailId: EmailId (TextBox)
  • ICommand RegisterButtonClicked: Register (Button)
  • ICommand ResetButtonClicked: Reset (Button)
  • bool IsButtonClicked: What response to display in TextBlockMessage (TextBlock)
  • ViewModel will inherit INotifyPropertyChanged interface & we will override PropertyChangedEventHandler event
  • Function for calculating an age based on DOB:
Let's encapsulate all of the above:
  1. using System;  
  2. using System.ComponentModel;  
  3. using System.Windows.Input;  
  4.   
  5. namespace A  
  6. {  
  7.     class MainWindowViewModel : INotifyPropertyChanged  
  8.     {  
  9.         #region Properties  
  10.         private string _userName;  
  11.         public string UserName  
  12.         {  
  13.             get { return _userName; }  
  14.             set { _userName = value;  
  15.                 RaisePropertyChange("UserName");  
  16.             }  
  17.         }  
  18.   
  19.         private string _age;  
  20.         public string Age  
  21.         {  
  22.             get { return _age; }  
  23.             set {  _age = value;  
  24.                 RaisePropertyChange("Age");  
  25.             }  
  26.         }  
  27.   
  28.         private string _emailId;  
  29.         public string EmailId  
  30.         {  
  31.             get { return _emailId; }  
  32.             set {  _emailId = value;  
  33.                 RaisePropertyChange("EmailId");  
  34.             }  
  35.         }  
  36.   
  37.         private bool _isButtonClicked;  
  38.   
  39.         public bool IsButtonClicked  
  40.         {  
  41.             get { return _isButtonClicked; }  
  42.             set {  _isButtonClicked = value;  
  43.                 RaisePropertyChange("IsButtonClicked");  
  44.             }  
  45.         }  
  46.   
  47.         private DateTime _dob;  
  48.   
  49.         public DateTime DOB  
  50.         {  
  51.             get { return _dob; }  
  52.             set {  _dob = value;  
  53.                 RaisePropertyChange("DOB");  
  54.                 CalculateAge();  
  55.             }  
  56.         }  
  57.  
  58.         #endregion  
  59.  
  60.         #region ICommands  
  61.         public ICommand RegisterButtonClicked { getset; }  
  62.         public ICommand ResetButtonClicked { getset; }  
  63.         #endregion  
  64.  
  65.         #region INotifyChangeProperty  
  66.   
  67.         public event PropertyChangedEventHandler PropertyChanged;  
  68.         public void RaisePropertyChange(string propertyname)  
  69.         {  
  70.             if (PropertyChanged != null)  
  71.             {  
  72.                 PropertyChanged(thisnew PropertyChangedEventArgs(propertyname));  
  73.             }  
  74.         }  
  75.         #endregion  
  76.  
  77.         #region Constructor  
  78.         public MainWindowViewModel()  
  79.         {  
  80.             RegisterButtonClicked = new RelayCommand(RegisterUser, CanUserRegister);  
  81.             ResetButtonClicked = new RelayCommand(ResetPage, CanResetPage);  
  82.         }  
  83.  
  84.         #endregion  
  85.  
  86.         #region Event Methods  
  87.         private void RegisterUser(object value)  
  88.         {  
  89.             IsButtonClicked = true;  
  90.         }  
  91.   
  92.         private bool CanUserRegister(object value)  
  93.         {  
  94.              
  95.             if (string.IsNullOrEmpty(UserName))  
  96.             {  
  97.                 return false;  
  98.             }  
  99.             else  
  100.             {  
  101.                 return true;  
  102.             }  
  103.         }  
  104.   
  105.         private void ResetPage(object value)  
  106.         {  
  107.             IsButtonClicked = false;  
  108.             UserName = Age = EmailId = "";  
  109.         }  
  110.   
  111.         private bool CanResetPage(object value)  
  112.         {  
  113.             if (string.IsNullOrEmpty(UserName)  
  114.                 || string.IsNullOrEmpty(EmailId)) 
  115.             {  
  116.                 return false;  
  117.             }  
  118.             else  
  119.             {  
  120.                 return true;  
  121.             }  
  122.         }  
  123.   
  124.         private void CalculateAge()  
  125.         {  
  126.            int Years = new DateTime(DateTime.Now.Subtract(DOB).Ticks).Year - 1;  
  127.             DateTime PastYearDate = DOB.AddYears(Years);  
  128.             Age = String.Format("{0}Years",Years);  
  129.         }  
  130.         #endregion  
  131.   
  132.     }  

Now it's time for some action. Run this project and check out the behaviour:
 
INotifyPropertyChanged Interface In MVVM
 
There you go. As soon as DOB is modified, we call CalulateAge() method which sets the value of Age property thus Age property has been modified with new value in the background (in the C# class) now our PropertyChange event has been raised, which triggered UI and updated the value of Age)
 
Here is the syntax for calling a RaisePropertyChange:
  1. RaisePropertyChange("UserName");   
You may have wonder why we have to pass the Property name as a parameter. What if someone makes a mistake while typing a name? Then it will lead to a problem.
 
Let's add a new method to take care of this.
  1. protected bool SetProperty<T>(ref T prop, T value, [CallerMemberName] string propertyName = null)  
  2. {  
  3.     if (object.Equals(prop, value)) return false;  
  4.     prop = value;  
  5.     this.RaisePropertyChange(propertyName);  
  6.     return true;  

And how do we call this method?
  1. private string _userName;  
  2.       public string UserName  
  3.       {  
  4.           get { return _userName; }  
  5.           set {
  6.               SetProperty(ref _userName, value);  
  7.           }  
  8.       } 
See, whenever we bind the property in XAML, the value of the property gets assigned to UserName (the public property).
 
For example, when I enter "Rikam" in UserNameTextBox, the ViewModel's property UserName is updated with "Rikam" & _userName and the private variable will be null as per the above code.
 
Then SetProperty method will update _userName with "Rikam" and will raise the event with "UserName" property.
 
Now suppose I am entering "Rikam" in UI, but on the click of the Register button, it will change the property to "Alex".
  1. private void RegisterUser(object value)  
  2.        {  
  3.            UserName = "Alex";  
  4.            IsButtonClicked = true;  
  5.        } 
Then SetProperty will get these parameters:
  1. ref T prop = "Rikam", T value = "Alex"  
With respect to _userName = "Rikam" & Value(UserName) = "Alex".
 
Now let's see the final updated ViewModel:
  1. using System;  
  2. using System.ComponentModel;  
  3. using System.Runtime.CompilerServices;  
  4. using System.Windows.Input;  
  5.   
  6. namespace A  
  7. {  
  8.     class MainWindowViewModel : INotifyPropertyChanged  
  9.     {  
  10.         #region Properties  
  11.         private string _userName;  
  12.         public string UserName  
  13.         {  
  14.             get { return _userName; }  
  15.             set  {
  16.  SetProperty(ref _userName, value);  
  17.             }  
  18.         }  
  19.   
  20.         private string _age;  
  21.         public string Age  
  22.         {  
  23.             get { return _age; }  
  24.             set {   
  25.                 SetProperty(ref _age, value);  
  26.             }  
  27.         }  
  28.   
  29.         private string _emailId;  
  30.         public string EmailId  
  31.         {  
  32.             get { return _emailId; }  
  33.             set {   
  34.                 SetProperty(ref _emailId, value);  
  35.             }  
  36.         }  
  37.   
  38.         private bool _isButtonClicked;  
  39.   
  40.         public bool IsButtonClicked  
  41.         {  
  42.             get { return _isButtonClicked; }  
  43.             set {    
  44.                 SetProperty(ref _isButtonClicked, value);  
  45.             }  
  46.         }  
  47.   
  48.         private DateTime _dob;  
  49.   
  50.         public DateTime DOB  
  51.         {  
  52.             get { return _dob; }  
  53.             set {   
  54.                 SetProperty(ref _dob, value);  
  55.                 CalculateAge();  
  56.             }  
  57.         }  
  58.  
  59.         #endregion  
  60.  
  61.         #region ICommands  
  62.         public ICommand RegisterButtonClicked { getset; }  
  63.         public ICommand ResetButtonClicked { getset; }  
  64.         #endregion  
  65.  
  66.         #region INotifyChangeProperty  
  67.   
  68.         public event PropertyChangedEventHandler PropertyChanged;  
  69.         public void RaisePropertyChange(string propertyname)  
  70.         {  
  71.             if (PropertyChanged != null)  
  72.             {  
  73.                 PropertyChanged(thisnew PropertyChangedEventArgs(propertyname));  
  74.             }  
  75.         }  
  76.   
  77.         protected bool SetProperty<T>(ref T prop, T value, [CallerMemberName] string propertyName = null)  
  78.         {  
  79.             if (object.Equals(prop, value)) return false;  
  80.             prop = value;  
  81.             this.RaisePropertyChange(propertyName);  
  82.             return true;  
  83.         }  
  84.         #endregion  
  85.  
  86.         #region Constructor  
  87.         public MainWindowViewModel()  
  88.         {  
  89.             RegisterButtonClicked = new RelayCommand(RegisterUser, CanUserRegister);  
  90.             ResetButtonClicked = new RelayCommand(ResetPage, CanResetPage);  
  91.         }  
  92.  
  93.         #endregion  
  94.  
  95.         #region Event Methods  
  96.         private void RegisterUser(object value)  
  97.         {  
  98.             _userName = "Alex";  
  99.             IsButtonClicked = true;  
  100.         }  
  101.   
  102.         private bool CanUserRegister(object value)  
  103.         {  
  104.              
  105.             if (string.IsNullOrEmpty(UserName))  
  106.             {  
  107.                 return false;  
  108.             }  
  109.             else  
  110.             {  
  111.                 return true;  
  112.             }  
  113.         }  
  114.   
  115.         private void ResetPage(object value)  
  116.         {  
  117.             IsButtonClicked = false;  
  118.             UserName = Age = EmailId = "";  
  119.         }  
  120.   
  121.         private bool CanResetPage(object value)  
  122.         {  
  123.             if (string.IsNullOrEmpty(UserName)  
  124.                 || string.IsNullOrEmpty(EmailId))  
  125.             {  
  126.                 return false;  
  127.             }  
  128.             else  
  129.             {  
  130.                 return true;  
  131.             }  
  132.         }  
  133.   
  134.         private void CalculateAge()  
  135.         {  
  136.            int Years = new DateTime(DateTime.Now.Subtract(DOB).Ticks).Year - 1;  
  137.             DateTime PastYearDate = DOB.AddYears(Years);  
  138.             Age = String.Format("{0}Years",Years);  
  139.         }  
  140.         #endregion  
  141.   
  142.     }  

Classic!
 
There will be no change in the output.
 

Conclusion

 
In this article, we grasp knowledge on following things:
  • How to use the INotifyPropertyChanged interface with MVVM.
  • How to trigger UI with modified properties.
  • How to rectify error-prone code.
Thank you so much for being here, I wish you all the very best.
 
Keep coding.