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



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

    主窗口将包含屏幕、模式按钮和音量控制。
  • 根据系统的模式,屏幕将显示音频,气候或导航面板。
  • 在音频模式下,会有另一组模式按钮和一个面板,可以显示收音机,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来处理分层视图、基于系统模式交换视图和删除视图的最佳实践是什么?

专:

  • 如何组织视图模型,使层次关系清晰地表示出来?
  • 你如何处理交换一个现有的视图为另一个(说取代音频面板与导航面板)?
  • 当不再需要关联的父视图时,您如何确保父视图和子视图模型被释放以进行垃圾收集?

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

事实证明,在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 -在visual studio - 2010 beta2/

最新更新