1

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

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

具体来说:

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

2 回答 2

1

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

于 2011-09-26T20:21:40.913 回答
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/

于 2011-09-27T20:33:14.343 回答