假设我正在为汽车构建导航系统:
- 主窗口将包含一个屏幕、模式按钮和一个音量控制。
- 根据系统的模式,屏幕将显示音频、气候或导航面板。
- 在音频模式下,会有另一组模式按钮和一个面板,可以显示收音机、CD 或 MP3 控件。
过去,我对此类安排的策略是让我的视图模型遵循与视图完全相同的层次结构。所以:
- MainViewModel 将有一个 ScreenViewModel。
- ScreenViewModel 将具有 AudioViewModel、ClimateViewModel 和 NavigationViewModel。它还有一个 CurrentViewModel 属性,可以设置为音频、气候或导航视图模型,具体取决于系统模式。
- AudioViewModel 类似于 ScreenViewModel,为每个音频系统的模式(收音机、CD 和 MP3)保存视图模型以及用于存储当前模式的视图模型的属性。
用于将视图绑定到视图模型的 XAML 如下所示:
<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}" />
如果用户正在收听广播并决定在导航系统中输入目的地,他们将单击导航模式按钮。MainWindowViewModel 上会有一个命令将系统模式更改为“导航”并将 CurrentViewModel 设置为 NavigationViewModel。这将导致 NavigationView 被交换。非常干净的解决方案。
不幸的是,虽然以这种方式在执行模式下运行良好,但在尝试使用 Expression Blend 中的从属视图(例如 AudioPanel)时它会崩溃,因为父视图模型 (MainWindowViewModel) 不存在以提供 AudioViewModel。
似乎在 MVVM Light 和 Simple MVVM 等工具包中支持的解决方案是使用 ViewModelLocator,然后通过绑定到定位器上的正确属性让视图设置它自己的 DataContext。然后定位器提供视图模型的一个实例。
“ViewModelLocator 做事方式”解决了“可设计性”问题,但我不清楚如何表示层次关系并处理一个视图与另一个视图的交换。从概念上讲,让视图模型保存子视图模型对我来说更有意义。它正确地表示了视图的层次结构,视图的交换很容易,如果不再需要视图,则只需删除对父视图的引用,关联的视图模型及其所有下属都将被垃圾收集。
问题
构建 ViewModelLocator 以处理分层视图、基于系统模式交换视图和删除视图的最佳实践是什么?
具体来说:
- 您如何组织视图模型以便清楚地表示层次关系?
- 您如何处理将一个现有视图换成另一个视图(例如用导航面板替换音频面板)?
- 当不再需要关联的父视图时,如何确保释放父视图模型和子视图模型以进行垃圾回收?