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. <TextBox x:Name="TextBoxUserName"
  40. Text="{Binding UserName}"
  41. Height="20"
  42. Width="150"
  43. Margin="0 10 0 0"
  44. Grid.Column="1"/>
  45. <DatePicker x:Name="DatePickerDOB"
  46. SelectedDate="{Binding DOB}"
  47. DisplayDateStart="1/1/1990"
  48. Width="150"
  49. Grid.Column="1"
  50. Grid.Row="1"/>
  51. <Rectangle
  52. Stroke="LightGray"
  53. StrokeThickness="1"
  54. Height="20"
  55. Width="150"
  56. Grid.Column="1"
  57. Grid.Row="2"/>
  58. <TextBlock x:Name="TextBlockAge"
  59. Text="{Binding Age}"
  60. Height="20"
  61. Width="150"
  62. Grid.Column="1"
  63. Grid.Row="2"/>
  64. <TextBox x:Name="TextBoxEmail"
  65. Text="{Binding EmailId}"
  66. Height="20"
  67. Width="150"
  68. Grid.Column="1"
  69. Grid.Row="3"/>
  70. <StackPanel x:Name="StackPanelButtons"
  71. Orientation="Horizontal"
  72. Grid.ColumnSpan="2"
  73. Grid.Row="4" >
  74. <Button x:Name="ButtonRegister"
  75. Height="20"
  76. Width="100"
  77. Content="Register"
  78. HorizontalAlignment="Center"
  79. Margin="20 10 0 0"
  80. Command="{Binding RegisterButtonClicked}"/>
  81. <Button x:Name="ButtonReset"
  82. Height="20"
  83. Width="100"
  84. Content="Reset"
  85. HorizontalAlignment="Center"
  86. Margin="20 10 0 0"
  87. Command="{Binding ResetButtonClicked}"/>
  88. </StackPanel>
  89. <TextBlock x:Name="TextBlockMessage"
  90. HorizontalAlignment="Center"
  91. Margin="20 8 0 0"
  92. Grid.Row="5"
  93. Grid.ColumnSpan="2">
  94. <TextBlock.Style>
  95. <Style TargetType="TextBlock">
  96. <Setter Property="Text"
  97. Value="Enter details to register!">
  98. </Setter>
  99. <Style.Triggers>
  100. <DataTrigger Binding="{Binding IsButtonClicked}" Value="True">
  101. <Setter Property="Text"
  102. Value="{Binding UserName, StringFormat='User: {0} is successfully registered!'}">
  103. </Setter>
  104. </DataTrigger>
  105. </Style.Triggers>
  106. </Style>
  107. </TextBlock.Style>
  108. </TextBlock>
  109. </Grid>
  110. </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. namespace A
  5. {
  6. class MainWindowViewModel : INotifyPropertyChanged
  7. {
  8. #region Properties
  9. private string _userName;
  10. public string UserName
  11. {
  12. get { return _userName; }
  13. set { _userName = value;
  14. RaisePropertyChange("UserName");
  15. }
  16. }
  17. private string _age;
  18. public string Age
  19. {
  20. get { return _age; }
  21. set { _age = value;
  22. RaisePropertyChange("Age");
  23. }
  24. }
  25. private string _emailId;
  26. public string EmailId
  27. {
  28. get { return _emailId; }
  29. set { _emailId = value;
  30. RaisePropertyChange("EmailId");
  31. }
  32. }
  33. private bool _isButtonClicked;
  34. public bool IsButtonClicked
  35. {
  36. get { return _isButtonClicked; }
  37. set { _isButtonClicked = value;
  38. RaisePropertyChange("IsButtonClicked");
  39. }
  40. }
  41. private DateTime _dob;
  42. public DateTime DOB
  43. {
  44. get { return _dob; }
  45. set { _dob = value;
  46. RaisePropertyChange("DOB");
  47. CalculateAge();
  48. }
  49. }
  50. #endregion
  51. #region ICommands
  52. public ICommand RegisterButtonClicked { get; set; }
  53. public ICommand ResetButtonClicked { get; set; }
  54. #endregion
  55. #region INotifyChangeProperty
  56. public event PropertyChangedEventHandler PropertyChanged;
  57. public void RaisePropertyChange(string propertyname)
  58. {
  59. if (PropertyChanged != null)
  60. {
  61. PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
  62. }
  63. }
  64. #endregion
  65. #region Constructor
  66. public MainWindowViewModel()
  67. {
  68. RegisterButtonClicked = new RelayCommand(RegisterUser, CanUserRegister);
  69. ResetButtonClicked = new RelayCommand(ResetPage, CanResetPage);
  70. }
  71. #endregion
  72. #region Event Methods
  73. private void RegisterUser(object value)
  74. {
  75. IsButtonClicked = true;
  76. }
  77. private bool CanUserRegister(object value)
  78. {
  79. if (string.IsNullOrEmpty(UserName))
  80. {
  81. return false;
  82. }
  83. else
  84. {
  85. return true;
  86. }
  87. }
  88. private void ResetPage(object value)
  89. {
  90. IsButtonClicked = false;
  91. UserName = Age = EmailId = "";
  92. }
  93. private bool CanResetPage(object value)
  94. {
  95. if (string.IsNullOrEmpty(UserName)
  96. || string.IsNullOrEmpty(EmailId))
  97. {
  98. return false;
  99. }
  100. else
  101. {
  102. return true;
  103. }
  104. }
  105. private void CalculateAge()
  106. {
  107. int Years = new DateTime(DateTime.Now.Subtract(DOB).Ticks).Year - 1;
  108. DateTime PastYearDate = DOB.AddYears(Years);
  109. Age = String.Format("{0}Years",Years);
  110. }
  111. #endregion
  112. }
  113. }
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;
  7. }
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. 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 {
  15. SetProperty(ref _userName, value);
  16. }
  17. }
  18. private string _age;
  19. public string Age
  20. {
  21. get { return _age; }
  22. set {
  23. SetProperty(ref _age, value);
  24. }
  25. }
  26. private string _emailId;
  27. public string EmailId
  28. {
  29. get { return _emailId; }
  30. set {
  31. SetProperty(ref _emailId, value);
  32. }
  33. }
  34. private bool _isButtonClicked;
  35. public bool IsButtonClicked
  36. {
  37. get { return _isButtonClicked; }
  38. set {
  39. SetProperty(ref _isButtonClicked, value);
  40. }
  41. }
  42. private DateTime _dob;
  43. public DateTime DOB
  44. {
  45. get { return _dob; }
  46. set {
  47. SetProperty(ref _dob, value);
  48. CalculateAge();
  49. }
  50. }
  51. #endregion
  52. #region ICommands
  53. public ICommand RegisterButtonClicked { get; set; }
  54. public ICommand ResetButtonClicked { get; set; }
  55. #endregion
  56. #region INotifyChangeProperty
  57. public event PropertyChangedEventHandler PropertyChanged;
  58. public void RaisePropertyChange(string propertyname)
  59. {
  60. if (PropertyChanged != null)
  61. {
  62. PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
  63. }
  64. }
  65. protected bool SetProperty<T>(ref T prop, T value, [CallerMemberName] string propertyName = null)
  66. {
  67. if (object.Equals(prop, value)) return false;
  68. prop = value;
  69. this.RaisePropertyChange(propertyName);
  70. return true;
  71. }
  72. #endregion
  73. #region Constructor
  74. public MainWindowViewModel()
  75. {
  76. RegisterButtonClicked = new RelayCommand(RegisterUser, CanUserRegister);
  77. ResetButtonClicked = new RelayCommand(ResetPage, CanResetPage);
  78. }
  79. #endregion
  80. #region Event Methods
  81. private void RegisterUser(object value)
  82. {
  83. _userName = "Alex";
  84. IsButtonClicked = true;
  85. }
  86. private bool CanUserRegister(object value)
  87. {
  88. if (string.IsNullOrEmpty(UserName))
  89. {
  90. return false;
  91. }
  92. else
  93. {
  94. return true;
  95. }
  96. }
  97. private void ResetPage(object value)
  98. {
  99. IsButtonClicked = false;
  100. UserName = Age = EmailId = "";
  101. }
  102. private bool CanResetPage(object value)
  103. {
  104. if (string.IsNullOrEmpty(UserName)
  105. || string.IsNullOrEmpty(EmailId))
  106. {
  107. return false;
  108. }
  109. else
  110. {
  111. return true;
  112. }
  113. }
  114. private void CalculateAge()
  115. {
  116. int Years = new DateTime(DateTime.Now.Subtract(DOB).Ticks).Year - 1;
  117. DateTime PastYearDate = DOB.AddYears(Years);
  118. Age = String.Format("{0}Years",Years);
  119. }
  120. #endregion
  121. }
  122. }
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.