Working on MvvM ? You need this package.
By Mirek on (tags: Community Toolkit, mvvm, .net, WPF, categories: tools, code)If you develop a Windows Presentation Foundation (or UWP or MAUI) application with MvvM pattern and don’t use any framework for that like Prism, you better take a look at what .NET Community Toolkit has prepared for you.
UPDATE: 24.03.2023 CommunityToolkit.MvvM 8.1.0
.NET Community Toolkit is a set of tools and API’s that facilitate everyday .NET developers work. It consists of a bunch of NuGet packages tailored for specific need. Just naming few of them .NET MAUI Community Toolkit for MAUI app development, Windows Community Toolkit for UWP and WinUI 3, CommunityToolkit.Diagnostics, CommunityToolkit.HighPerformance and many more.
In this post we will lean over the CommunityToolkit.Mvvm package which is design to facilitate your work with binable XAML based applications no matter if you develop a WPF, UWP or even MAUI application.
INotifyPropertyChanged
If you are like me and don’t like WPF frameworks, you probably know that implementing a INotifyPropertyChanged interface requires bunch of boilerplate code. The model must implement the PropertyChanged event, then every bindable value you want to expose to the view must be implemented as full property with getter, setter, backing field and must call the PropertyChanged event. Even for simple model like below this is a lot of code.
public class PersonModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string firstName;
public string FirstName
{
get => firstName;
set
{
firstName = value;
NotifyPropertyChanged();
}
{
get => lastName;
set
{
lastName = value;
NotifyPropertyChanged();
}
}
public string FullName => $"{FirstName} {LastName}";
}
Above PersonModel class exposes only two bindable properties FirstName and LastName. Now let’s see how this can be simplified using ObservableObject from Microsoft.Toolkit.Mvvm package.
public partial class PersonModel : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
string firstName;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
string lastName;
public string FullName => $"{FirstName} {LastName}";
}
Where are all those properties and change notification stuff ? Note there are no more full properties implemented here, only the private string fields (firstName and lastName), hovewer the full properties are available and are used in FullName getter. How is it done? See the class is now marked as partial? That means there is somewhere the rest of the implementation. This is what the toolkit does for us. It not only implements the INotifyPropertyChanged interface, but it also generates the full properties with getters, setters and other stuff. It’s all done by the source generators and can be inspected in solution explorer in Visual Studio
How much less code to maintain. More details on ObservableObject can be found here.
RelayCommand
Implementing commanding in MvvM is also way easier and quicker with use of the toolkit package. Let’s see how you can implement the command in the model.
public partial class AboutViewModel : ObservableObject
{
[ObservableProperty]
int counter;
[RelayCommand]
void Increment()
{
Counter++;
}
}
Yes, that so simple ! You only need to implement your actual command implementation as single method. What we got generated by the toolkit? Let’s see (some diagnostics code were remove for better clarity)
public partial class AboutViewModel
{
private global::Microsoft.Toolkit.Mvvm.Input.RelayCommand? incrementCommand;
public global::Microsoft.Toolkit.Mvvm.Input.IRelayCommand IncrementCommand
=> incrementCommand ??= new global::Microsoft.Toolkit.Mvvm.Input.RelayCommand(new global::System.Action(Increment));
}
Need an asynchronous command?
public partial class AboutViewModel : ObservableObject
{
[ObservableProperty]
int counter;
[RelayCommand]
async Task IncrementAsync()
{
await Task.Delay(100);
Counter++;
}
}
No problem, let the toolkit serves you. Generated code below.
public partial class AboutViewModel
{
private global::Microsoft.Toolkit.Mvvm.Input.AsyncRelayCommand? incrementCommand;
public global::Microsoft.Toolkit.Mvvm.Input.IAsyncRelayCommand IncrementCommand
=> incrementCommand ??= new global::Microsoft.Toolkit.Mvvm.Input.AsyncRelayCommand(new global::System.Func(IncrementAsync));
}
And all of this goodies with no single reflection or anything else. All the extra code is generated on the fly while you’re authoring your classes. Moreover the generated code is optimised for better performance and lowest memory footprint.
Messenger
Another cool feature that is brought to your project by CommunityToolkit.Mvvm package is the Messenger. When implementing MvvM pattern you often need to keep view models decoupled, but still be able to communicate to each other and send some data around. That is usually accomplished by some sort of subscriber-publisher or events aggregator pattern. Here is how we can handle it with Messenger object and specifically with ObservableRecipient base class.
Let’s assume we have MainViewModel which represents the root of our application, the main window basically, and we also have one of embedded content view model AccountViewModel. Now we want the MainViewModel to be notified about the user change made in AccountViewModel. All we need to do is to derive the MainViewModel from ObservableRecipient and also implement IRecipient<UserUpdateMessage>
public partial class MainViewModel : ObservableRecipient, IRecipient<UserUpdateMessage>
{
[ObservableProperty]
Person user;
public void Receive(UserUpdateMessage message)
{
User = message.User;
}
}
where UserUpdateMessage class is simple message representation
public class UserUpdateMessage
{
public Person User { get; set; }
}
Now in the AccountViewModel we need to send the message
public partial class AccountViewModel : ObservableRecipient
{
[ObservableProperty]
Person user;
[RelayCommand]
void Save()
{
Messenger.Send(new UserUpdateMessage { User = User });
}
}
As you can see AccountViewModel also derives from ObservableRecipient object, which brings the Messanger instance into the context and makes it super easy to send and receive messages.
This is a default implementation of the Messenger from the CommunityToolkit.Mvvm package. Naturally you can override it and adjust the code to have more control over how the Messenger is used. For instance if you want it to be resolved from IoC. As a side note: there are two IMessenger implementations provided by the toolkit: StrongReferenceMessenger and WeakReferenceMessenger. The later is the default one.
One more important note. The Messenger uses the activation mechanism to limit the possibility of memory leakage. Every class that wants to receive or send messages must firstly be activated to do so. That practically means the internal implementation hooks up the receiver methods on activation and releases them on deactivation. While this is not that important when using WeakReferenceMessenger (which is used in default apprach), but should be considered critical when using StrongReferenceMessenger. However, in either case it is a good practice to unregister the messaging members whenever it makes sense to avoid unnecessary garbage collection and improve the overall performance of the application.
ObservableRecipient object exposes a public property IsActive which should be manually set to true whenever your object wants to start receiving messages and should be set to false whenever your object is no longer in use. Setting that property fires OnActivated and OnDeactivated protected methods accordingly. It’s totally up to you when you activate or disactivate your view model. I like the apprach that activates the view model whenever the connected window is activated and deactivate it whenewer the window is deactivated.
public partial class MainWindow : Window
{
public MainWindow(MainViewModel vm)
{
InitializeComponent();
DataContext = vm;
Vm = vm;
}
public MainViewModel Vm { get; }
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
Vm.IsActive = true;
}
protected override void OnDeactivated(EventArgs e)
{
base.OnDeactivated(e);
Vm.IsActive = false;
}
}
Another apprach is to activate the view model when its created and deactivate it when it’s destroyed. We can handle it in some base view model class
public abstract partial class BaseViewModel : ObservableRecipient, IDisposable
{
private bool disposed;
public BaseViewModel()
{
IsActive = true;
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
IsActive = false;
}
disposed = true;
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
Source code is available below.
Cheers!