Reputation: 42364
Let's say I'm building a navigation system for a car:
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 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:
Upvotes: 1
Views: 438
Reputation: 42364
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/
Upvotes: 1
Reputation: 11598
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'.
Upvotes: 1