Search code examples
c#mvvmavaloniauicommunity-toolkit-mvvm

MVVM Community Toolkit keeping two view synchronised with one model in an Avalonia application


I want to understand possible architectural level approaches that keep two views synchronised with a single model. Here I have a full implementation that uses MVVM Community Toolkit Messaging (see GitHub link below). What other ways of implementing this should be considered?

The setup

Imaging you have an application with two different view that need to synchronise the same model object between them. For example, let's say we have a plotting application. In the left is UI to allow the user to control the axis limits and on the right is a plot view that displays data.

Mock UI

When the user edits the text box the plot view will update. When the user pans the plot the text boxes update. So this is bidirectional.

Moreover, let's follow what happens when the user edits one of the settings text boxes.

  1. Edit Min text box with a new value
  2. View binding triggers the change handler on the Settings view-model.
  3. The change handler creates a new AxisLimits model object and updates itself with that value.
  4. The view-model then sends a MVVM Community Toolkit Message telling all recipients of the change.
  5. The main view-model handles the message and distributes the new model object to all view-models.

GitHub

Take a look at a fully working example on GitHub, https://github.com/danieljfarrell/Avalonia-MVVM-Messaging-Example

Update

https://github.com/danieljfarrell/Avalonia-MVVM-Messaging-Example/tree/nested_viewmodels is a branch in which a AxisLimits view-model is shared between the Settings and Plot view models. This eliminates the need for messages. AxisLimits represents all common data shared between the Settings and Plot view-models.


Solution

  • You say "two views" but in the example I only see two instances of the same view. It is perfectly acceptable to use the same view model instance for multiple instances of it's view.

    You may have heard the "rule" 1 View Model to 1 View, adhering to this will ensure a much easier time adapting the view and model to future needs by simply modifying the view model rather than a massive refactoring, but to accomplish this, it only needs to apply to the classes, not the created instances of those classes.

    Using the same instance of a view model for multiple view instances does require a "View Model First" approach. This means you create the view model instance before creating the view instances and assign it to the DataContext property of the views when you create them. Because the instances of the View are backed by the same view model instance they will always be in sync.

    The following does NOT violate the 1 View Model to 1 View rule as the two view instances are the same class:

    MyViewModel vm = new();
    MyView view1 = new()
    {
      DataContext = vm,
    }
    
    MyView view2 = new()
    {
      DataContext = vm,
    }
    

    However, in the event you do need two different views sharing the same model data, you will want two view models, in which case the view models will be sharing the same model (no rule against that!). In this case, rather than replacing the model instance as you do in your example, you might consider simply making your model send notifications when it is changed and keep it as a singleton. In this case you will have a single AxisLimits instance and if something changes, it fires an event notifying that it has changed rather than itself being replaced. Your view models would then simply register for this event. You can set up the event structure however you prefer, but there is also no reason why a model can't implement INotifyPropertyChanged if that's what you need.