当视图是分层的并且需要交换进出时定位ViewModel的策略

时间:2011-09-26 20:12:40

标签: wpf silverlight mvvm mvvm-light simple-mvvm

假设我正在为汽车构建导航系统:

  • 主窗口将包含屏幕,模式按钮和音量控制。
  • 根据系统的模式,屏幕将显示音频,气候或导航面板。
  • 在音频模式下,会有另一组模式按钮和一个可以显示收音机,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上会有一个命令,它将系统模式更改为“Navigation”,并将CurrentViewModel设置为NavigationViewModel。这将导致NavigationView被交换。非常干净的解决方案。

不幸的是,虽然这样做的方式在执行模式下运行良好,但在尝试使用Expression Blend中的从属视图(比如AudioPanel)时会崩溃,因为父视图模型(MainWindowViewModel)不存在以提供AudioViewModel

MVVM Light和Simple MVVM等工具包中似乎支持的解决方案是使用ViewModelLocator,然后让视图通过绑定到定位器上的正确属性来设置它自己的DataContext。然后定位器提供视图模型的实例。

“ViewModelLocator处理方式”解决了“可设计性”问题,但我不清楚如何表示层次关系并处理一个视图到另一个视图的交换。从概念上讲,让视图模型保持子视图模型对我来说更有意义。它正确地表示了视图的层次结构,视图交换是一个快照,如果不再需要视图,关联的视图模型及其所有下属将通过删除对父项的引用来进行垃圾收集。

问题

构建ViewModelLocator以处理分层视图,根据系统模式交换视图和删除视图的最佳实践是什么?

具体做法是:

  • 如何组织视图模型,以便清晰地表示层次关系?
  • 如何处理一个现有视图换另一个视图(比如用导航面板替换音频面板)?
  • 当不再需要关联的父视图时,如何确保释放父级和子视图模型以进行垃圾回收?

2 个答案:

答案 0 :(得分:1)

看起来视图层次结构中的当前视图是视图“状态”的一部分,因此它将拥有自己的“模型”(viewmodel)实体来管理这种关系。我不会使用IoC容器,但我会用它来注册'视图管理器'用来创建'子视图'的工厂。

答案 1 :(得分:1)

事实证明,Visual Studio / Blend中有一个XAML设计属性,允许您设置元素的设计时DataContext。这仅适用于设计时,因此应该可以继续使用数据模板连接DataContext(即根本不需要ViewModelLocator或ViewManager)。

例如,假设您有一个名为AudioPanel的视图和一个名为AudioViewModel的视图模型。

您只需要在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();
        }
    }
}

...然后在您看来,您只需要声明d:DataContext属性...

<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}">

只要为在设计时发挥作用的每个视图模型编写默认构造函数,就应该可以在VS或Blend设计器中查看复合用户界面。

有关详细信息,请参阅此博文: http://karlshifflett.wordpress.com/2009/10/28/ddesigninstance-ddesigndata-in-visual-studio-2010-beta2/