C# – Concrete examples of state sharing between multiple viewmodels (WPF MVVM)

cdata-bindingmvvmwpf

I have a WPF/Entity Framework (4.0) project with many objects. I'd like to build the application so that that I can have object selection state shared across viewmodels.

For Example: We have Cars, Drivers, Passengers, and Cargo classes. We also have UserControls for CarList, DriverList, etc. and editor windows for CarEditor, DriverEditor, etc. Furthermore, we have viewmodels for all of these (CarListViewModel, DriverListViewModel, CargoEditorViewModel, etc). This all composes a dockable interface where the user can have multiple object lists, editors, and viewers open.

What I want is a concrete code example of how to wireup multiple viewmodels so that selecting a car in the CarList will cause that car to go live in the CarEditorView, but also be selected in any other view for which the context is valid (such as a DriverByCarView- or just DriverList if there is a filter predicate).

There are a number of suggestions and discussions based on this question. The two methods that seem to dominate are:

  • 3018307: Discusses state sharing by mentioning a messaging subsystem
  • 1159035: Discusses state sharing by using an enclosing viewmodel

Is one of these approaches better than the other?

Does anyone have a concrete example of either/both of these methods in the form of a write-up or small code project?

I'm still learning WPF, so pointers to entry points for reading API fundamentals are appreciated, but looking at code examples is where I usually go.

Thanks


In case anyone is interested, here are some other similar discussions:

  • 3816961: Discusses returning multiple viewmodels depending on object type (i.e. a collection of arbitrary types adhering to a specific interface)
  • 1928130: Discusses whether it is a good idea to aggregate viewmodels as properties of other viewmodels (e.g. a MainWindow viewmodel composed of panel viewmodels)
  • 1120061: Essentially discusses whether to have use a viewmodel-per-model strategy or a viewmodel-per-view-element strategy.
  • 4244222: Discusses whether or not to nest the viewmodels when using a nested object hierarchy.
  • 4429708: Discusses sharing collections between viewmodels directly, but doesn't go into detail.
  • List item: Discusses managing multiple selections within a single viewmodel.

Best Answer

A typical way to achieve this is to use a messenger to publish a CarSelected message that details the selected car. Zero or more ViewModels can subscribe to the CarSelected message. ViewModels that are interested in the currently selected car can listen for the message and then act accordingly.

The messenger approach provides a clean decoupled design where publishers and subscribers have no dependencies on each other so can easily evolve independently - they just need to know about the car message. Messengers are an implementation of the mediator pattern.

In Prism the messenger is the EventAggregator and is used for publishing and subscribing to messages.

Update

Apart from the architectural advantages the EventAggregator brings, it also implements weak events to prevent memory leak issues with subscribers that do not explicitly unsubscribe.

Please see the following for EventAggregator documentation:

http://msdn.microsoft.com/en-us/library/ff649187.aspx

Prism:

http://compositewpf.codeplex.com/

Prism Example

public class ViewModel1
{
    private readonly IEventAggregator _eventService;
    private Car _selectedCar;

    public ViewModel1(IEventAggregator eventService)
    {
        _eventService = eventService;
    }

    //Databound property...
    public Car SelectedCar
    {
        set
        {
            _selectedCar = value;

            var msg = new CarSelectedMessage { Car = _selectedCar };

            _eventService.GetEvent<CarSelectedEvent>().Publish(msg);
        }
    }
}

public class ViewModel2
{
    public ViewModel2(IEventAggregator eventService)
    {
        eventService.GetEvent<CarSelectedEvent>().Subscribe(msg =>
        {
            //DoStuff with msg...
        });
    }
}

public class Car {}

public class CarMessage
{
    public Car Car { get; set; }
}

public class CarSelectedEvent : CompositePresentationEvent<CarMessage> {}