Building viewmodels using the Microsoft MVVM toolkit is straightforward. It’s descended from the old MVVM-Light library so if you’ve used that the transition is pretty simple. The main elements of the toolkit I use (there are others) are property setting and inter-model communication/messaging.
WPF has a powerful architecture for making sure a UI element and the code base it interacts with stay in sync. But by itself it requires a lot of boilerplate code…which doesn’t always seem “on point” with what you’re trying to do1 (e.g., expose a property for display and modification by a UI element). The MVVM architecture, fortunately, hides almost all of the details of using it. For defining properties your UI element will look to you follow a pattern like this (simplified for clarity):
public class RouteDisplayViewModel : ObservableRecipient, IRouteDisplayViewModel { private IAppConfig _appConfig; private int _routeWidth; public RouteDisplayViewModel( IAppConfig appConfig ) { _appConfig = appConfig; RouteWidth = 4; } public int RouteWidth { get => _routeWidth; set { SetProperty( ref _routeWidth, value ); _appConfig.RouteWidth = value; } } }
Every property your UI binds to generally uses the same pattern: call the protected SetProperty() method to assign new values to a field which holds the property’s value in the viewmodel, and do whatever is needed to update the (data) model layer to keep it in sync with the UI. Behind the scenes SetProperty ensures the UI is notified that the property it’s binding to was changed2.
In the above snippet there’s a one-to-one correspondance of properties between the data model and the viewmodel. But there doesn’t have to be. For example, the following snippet involves two related properties — the encrypted and plaintext versions of an API key — which need to be adjusted together (details omitted for clarity):
public string APIKey { get => _apiKey; set { SetProperty( ref _apiKey, value ); if( !_userConfig.APIKeys.TryGetValue( SelectedProcessorType, out var temp ) ) return; temp.Value = value; EncryptedAPIKey = temp.EncryptedValue; } } public string EncryptedAPIKey { get => _encyptedApiKey; private set => SetProperty(ref _encyptedApiKey, value ); }
Updating the APIKey property also updates the EncryptedAPIKey property (the latter never gets set from the UI so it can be read-only).
The MVVM framework is elegantly simple and powerful but it doesn’t read minds. Sometimes you have to remember how it works to deal with more complex situations. Consider this property:
public string OutputPath { get => _outputPath; private set { _appConfig.OutputFile.FilePath = value; if( !value.Equals( _appConfig.OutputFile.FilePath, StringComparison.OrdinalIgnoreCase ) ) { var mesg = "Unsupported export file type, changed to KML"; DisplayMessageAsync( mesg, "Warning" ); SetProperty( ref _outputPath, _appConfig.OutputFile.FilePath ); return; } SelectedExportType = _appConfig.OutputFile.Type; if( SelectedExportType != ExportType.Unknown ) { SetProperty( ref _outputPath, _appConfig.OutputFile.FilePath ); return; } var mesg2 = "Unsupported export file type, changing to KML"; DisplayMessageAsync( mesg2, "Warning" ); _appConfig.OutputFile.Type = ExportType.KML; SetProperty( ref _outputPath, _appConfig.OutputFile.FilePath ); } } public ExportType SelectedExportType { get => _exportType; set { SetProperty( ref _exportType, value ); _appConfig.ExportType = value; SetProperty( ref _outputPath, _appConfig.OutputFile.FilePath ); OnPropertyChanged( nameof(OutputPath) ); } }
There’s admittedly a fair bit of stuff going on here. But the issue I want to focus on involves lines 17 and 42. They set up a potential infinite loop because they tie the setters of the two properties together: changing the SelectedExportType (which changes the file extension of the output file) should change the OutputPath. But changing the OutputPath has to change the SelectedExportType because the export type is directly related to the file extension.
Or it would be an infinite recursion except for the fact lines 42 and 43 don’t just assign a value to OutputPath. Instead, they set the underlying field that holds the output path to a new value.
But that call to SetProperty() in line 42 won’t notify the UI that the output path needs to be updated. Because the way SetProperty() works is by using a hidden argument that gets filled automagically with the name of method (or property, in the case of a setter) from which it is being called. For the call on line 42 that would be SelectedExportType when we want it to be OutputPath. Line 43 solves this problem by explicitly raising the PropertyChanged event with the correct property name.
There are other ways of addressing this issue. But the point is, however you address it, you have to keep in mind what the MVVM toolkit is doing under the hood.