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?