Implementing INotifyPropertyChanged
In article, a web development guru walks us through how we can use INotifyPropertyChanged to craft code that adheres to the DRY concept of programming.
Join the DZone community and get the full member experience.
Join For FreeThis is a 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 a base class for multiple view models.
First, one's introduction to INotifyPropertyChanged comes usually through some existing view model. The interface is used to communicate to the view that some properties in the view model have changed. Take a look at thePropertyChanged
event and theNotifyPropertyChanged
method (this method is not part of the 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 the PropertyChanged
method, there are many implementations. Jeremy Bytes's blog has an excellent overview of the evolution of INotifyPropertyChanged for those who want to find out more. Here, I will stick with what I have in the sample model above.
NotifyPropertyChanged Method
I have the 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'll give a few words for explanation.
CallerMemberName attribute is for the compiler. When the propertyName
argument is not given to the method call, then the name of the calling method or property is set to propertyName
. These two calls to the 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 the change of three properties it's not too common. Very often, we need to fire just one change event and it goes for the current property that changed. This is why this shortcut is good: we don't have to write the property name as a string and our code works even when the property name is changed.
NotifyPropertyChangedBase
Repeating the INotifyPropertyChanged
event and method to fire this event to every single view model doesn't make much sense. We get a load of repeated code and if we want to change something we have to modify all copies of the repeated code. To avoid this, I wrote a 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 a PropertyChanged
event and NotifyPropertyChanged()
method in a 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 the important base classes and MVVM features out-of-box.
Wrapping Up
Implementing INotifyPropertyChanged
manually is not hard. Newer versions of C# provide us with the CallerMemberName
ttributea and calls the method to fire events for property changes that 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 extend it. We can also write the more general ViewModelBase
that offers more functionalities that view models may need.
Published at DZone with permission of Gunnar Peipman, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments