Question

Let's say I'm building a navigation system for a car:

  • The main window would contain a screen, mode buttons, and a volume control.
  • Depending on the mode of the system, the screen would either show an audio, climate, or navigation panel.
  • When in audio mode, there would be another set of mode buttons and a panel that could either show radio, CD, or MP3 controls.

My strategy for arrangements like this in the past has been to have my view models follow the exact same hierarchy as the views. So:

  • The MainViewModel would have a ScreenViewModel.
  • The ScreenViewModel would have an AudioViewModel, ClimateViewModel, and NavigationViewModel. It would also have a CurrentViewModel property, which would be set to either the audio, climate or navigation view model, depending on the system mode.
  • The AudioViewModel would be similar to the ScreenViewModel, holding view models for each of the audio system's modes (radio, CD, and MP3) as well as a property for storing the view model for the current mode.

The XAML for binding the view to the view model would go something like this:

<Window.Resources>
    <DataTemplate DataType="{x:Type vm:AudioViewModel}">
        <view:AudioPanel />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:ClimateViewModel}">
        <view:ClimatePanel />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:NavigationViewModel}">
        <view:NavigationPanel />
    </DataTemplate>
</Window.Resources>

<ContentControl Content="{Binding CurrentViewModel}" />

If a user is listening to the radio and decides to enter a destination into the navigation system, they would click the Navigation mode button. There would be a command on MainWindowViewModel that changes the system mode to "Navigation" and sets the CurrentViewModel to the NavigationViewModel. This would cause the NavigationView to be swapped in. Very clean solution.

Unfortunately, while doing things this way works well in execution mode, it breaks down when trying to work with a subordinate view (say AudioPanel) in Expression Blend because the parent view model (MainWindowViewModel) doesn't exist to provide an AudioViewModel.

The solution that seems to be supported in toolkits such as MVVM Light and Simple MVVM is to use a ViewModelLocator instead, then have the view set it's own DataContext by binding to the correct property on the locator. The locator then serves up an instance of the view model.

The "ViewModelLocator way of doing things" solves the "designability" issue, but it's not clear to me how to represent hierarchical relationships and handle swapping of one view for another. Conceptually, it just makes more sense to me to have the view model hold the child view models. It represents the hierarchy of views correctly, swapping of views is a snap, and if a view is no longer needed, the associated view model and all its subordinates will be garbage collected simply by dropping the reference to the parent.

Question

What is the best practice for architecting a ViewModelLocator to handle hierarchical views, swapping of views in and out based on a system mode, and deletion of views?

Specifically:

  • How do you organize the views models so hierarchical relationships are clearly represented?
  • How do you handle swapping of one existing view out for another (say replacing the audio panel with the navigation panel)?
  • How do you ensure that parent and child view models get released for garbage collection when the associated parent view is no longer needed?
Was it helpful?

Solution 2

It turns out, there's a XAML design attribute in Visual Studio/Blend that allows you to set the design-time DataContext of an element. This only applies during design time, so it should be possible to continue hooking up the DataContext using data templates (i.e., a ViewModelLocator or ViewManager may not be needed at all).

For example, say you have a view called AudioPanel and a view model called AudioViewModel.

You would just need to initialize some design-time data in AudioViewModel...

public class AudioViewModel : ViewModelBase
{
    public int Volume { get; set; }
    public AudioMode Mode { get; set; }
    public ViewModelBase ModePanelViewModel { get; set; }

    public AudioViewModel()
    {
        if (IsInDesignMode)
        {
            Volume = 5;
            Mode = AudioMode.Radio;
            ModePanelViewModel = new RadioViewModel();
        }
    }
}

...then in your view, you would just need to declare a d:DataContext attribute...

<UserControl x:Class="NavSystem.Views.AudioPanel"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:vm="clr-namespace:NavSystem.ViewModels"
             mc:Ignorable="d"
             d:DataContext="{d:DesignInstance vm:AudioViewModel, IsDesignTimeCreatable=True}">

As long as you write a default constructor for each view model that comes into play during design time, it should be possible to view composite user interfaces in VS or Blend designers.

See this blog post for more details: http://karlshifflett.wordpress.com/2009/10/28/ddesigninstance-ddesigndata-in-visual-studio-2010-beta2/

OTHER TIPS

It seems like the current view in the hierarchy of views is part of the view 'state', so it would have a 'model' (viewmodel) entity of its own that manages this relationship. I would not use the IoC container for that, but I would use it to register a factory that the 'view manager' uses to create the 'sub-views'.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top