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

发布于 2024-12-06 18:15:22 字数 1877 浏览 6 评论 0原文

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

  • 主窗口将包含一个屏幕、模式按钮和音量控件。
  • 根据系统的模式,屏幕会显示音频、气候或导航面板。
  • 当处于音频模式时,会有另一组模式按钮和一个可以显示收音机、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。这将导致导航视图被交换。非常干净的解决方案。

不幸的是,虽然这种方式在执行模式下工作得很好,但当尝试在 Expression Blend 中使用从属视图(例如 AudioPanel)时,它会崩溃,因为父视图模型 (MainWindowViewModel) 不存在来提供 AudioViewModel。

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

“ViewModelLocator 的处理方式”解决了“可设计性”问题,但我不清楚如何表示层次关系并处理一个视图与另一个视图的交换。从概念上讲,让视图模型保存子视图模型对我来说更有意义。它正确地表示了视图的层次结构,视图的交换非常简单,如果不再需要某个视图,则只需删除对父级的引用,即可对关联的视图模型及其所有下级进行垃圾收集。

问题

构建 ViewModelLocator 来处理分层视图、基于系统模式的视图进出以及视图删除的最佳实践是什么?

具体来说:

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

七秒鱼° 2024-12-13 18:15:22

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

It seems like the current view in the hierarchy of views is part of the view 'state', so it would have a 'model' (viewmodel) entity of its own that manages this relationship. I would not use the IoC container for that, but I would use it to register a factory that the 'view manager' uses to create the 'sub-views'.

暮光沉寂 2024-12-13 18:15:22

事实证明,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/

It turns out, there's a XAML design attribute in Visual Studio/Blend that allows you to set the design-time DataContext of an element. This only applies during design time, so it should be possible to continue hooking up the DataContext using data templates (i.e., a ViewModelLocator or ViewManager may not be needed at all).

For example, say you have a view called AudioPanel and a view model called AudioViewModel.

You would just need to initialize some design-time data in 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();
        }
    }
}

...then in your view, you would just need to declare a d:DataContext attribute...

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

As long as you write a default constructor for each view model that comes into play during design time, it should be possible to view composite user interfaces in VS or Blend designers.

See this blog post for more details:
http://karlshifflett.wordpress.com/2009/10/28/ddesigninstance-ddesigndata-in-visual-studio-2010-beta2/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文