Implementing INotifyPropertyChanged
Short post about how to implement INotifyPropertyChanged without using any advanced tooling. I have some small UWP applications where I’m using MVVM to separate presentation and logic. Here is how I use INotifyPropertyChanged with base class for multiple view models.
First introduction to INotifyPropertyChanged comes usually through some existing view model. The interface is used to communicate to view that some properties in view model have changed. Take a look at PropertyChanged event and NotifyPropertyChanged method (method is not part of interface).
public class GalleryViewModel : INotifyPropertyChanged
{
public ObservableCollection<GalleryItem> Items { get; set; }
public GalleryViewModel()
{
Items = new ObservableCollection<GalleryItem>();
OpenImage = new RelayCommand<GalleryItem>(a => { SelectedItem = a; });
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private GalleryItem _selectedItem;
public GalleryItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NotifyPropertyChanged();
NotifyPropertyChanged("SelectedItemVisibility");
NotifyPropertyChanged("ListVisibility");
}
}
public Visibility SelectedItemVisibility
{
get
{
return _selectedItem == null ? Visibility.Collapsed : Visibility.Visible;
}
set { }
}
public Visibility ListVisibility
{
get
{
return _selectedItem != null ? Visibility.Collapsed : Visibility.Visible;
}
set { }
}
public async Task LoadData()
{
var service = new GalleryService();
var items = await service.LoadItems(null);
foreach(var item in items)
{
Items.Add(item);
}
}
public RelayCommand<GalleryItem> OpenImage { get; internal set; }
}
For PropertyChanged method there are many implementations. Jeremy Bytes blog has excellent overview of evolution of INotifyPropertyChanged for those who want to find out more. I will stick here with what I have in sample model above.
NotifyPropertyChanged method
I have shortest version of this method that I found. It works with newer .NET Frameworks.
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
For those who find it tricky or weird I give few words for explanation.
CallerMemberName attribute is for compiler. When propertyName argument is not given to method call then the name of calling method or property is used as propertyName. These two calls to NotifyPropertyChanged() method are equal:
public GalleryItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NotifyPropertyChanged();
NotifyPropertyChanged("SelectedItem");
}
}
But why this kind of shortcut? What it is good for? Although my sample model above fires change of three properties it’s not too common. Very often we need to fire just one change event and it goes for current property that changed. This is why this shortcut is good: we don’t have to write property name as string and our code works even when property name is changed.
NotifyPropertyChangedBase
Repeating INotifyPropertyChanged event and method to fire this event to every single view model doesn’t make much sense. We get load of repeated code and if we want to change something we have to modify all copies of repeated code. To avoid this I wrote NotifyPropertyChangedBase class.
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now we have PropertyChanged event and NotifyPropertyChanged() method in separate base class and we don’t have to duplicate the code to all view models. Using this base class I can write the view model above like shown here.
public class GalleryViewModel : NotifyPropertyChangedBase
{
public ObservableCollection<GalleryItem> Items { get; set; }
public GalleryViewModel()
{
Items = new ObservableCollection<GalleryItem>();
OpenImage = new RelayCommand<GalleryItem>(a => { SelectedItem = a; });
}
private GalleryItem _selectedItem;
public GalleryItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NotifyPropertyChanged();
NotifyPropertyChanged("SelectedItemVisibility");
NotifyPropertyChanged("ListVisibility");
}
}
public Visibility SelectedItemVisibility
{
get
{
return _selectedItem == null ? Visibility.Collapsed : Visibility.Visible;
}
set { }
}
public Visibility ListVisibility
{
get
{
return _selectedItem != null ? Visibility.Collapsed : Visibility.Visible;
}
set { }
}
public async Task LoadData()
{
var service = new GalleryService();
var items = await service.LoadItems(null);
foreach(var item in items)
{
Items.Add(item);
}
}
public RelayCommand<GalleryItem> OpenImage { get; internal set; }
}
From here we can go even further and use frameworks like Prism or MVVM Light Toolkit to have most of important base classes and MVVM features out-of-box.
Wrapping up
Implementing INotifyPropertyChanged manually is not hard. Newer versions of C# provide us with CallerMemberName attribute and calls to method to fire event for property changes get even smaller. Plus we don’t have to write property names as strings. To avoid repeating INotifyPropertyChanged code to all view models we created a simple base class and made our model to extend it. We can also write more general ViewModelBase that offers more functionalities that view models may need.
To avoid magic strings in the NotifyPropertyChanged method, you could use the nameof keyword:
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(SelectedItemVisibility));
NotifyPropertyChanged(nameof(ListVisibility));
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/nameof
I Would even go one step further and also use the nameof keyword for the other names.
This makes refactoring afterwards easier, because the names are now references to the actual property.
public GalleryItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(SelectedItemVisibility));
NotifyPropertyChanged(nameof(ListVisibility));
}
}
Or just use https://github.com/Fody/PropertyChanged