Стратегия определения местоположения 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. Очень чистое решение.

К сожалению, хотя такой способ хорошо работает в режиме выполнения, он не работает при попытке работать с подчиненным представлением (скажем, AudioPanel) в Expression Blend, потому что модель родительского представления (MainWindowViewModel) не существует, чтобы предоставить AudioViewModel.

Решение, которое, кажется, поддерживается в наборах инструментов, таких как MVVM Light и Simple MVVM, состоит в том, чтобы вместо этого использовать ViewModelLocator, а затем установить представление в свой собственный DataContext, привязав к правильному свойству локатора. Затем локатор обслуживает экземпляр модели представления.

"Способ выполнения ViewModelLocator" решает проблему "проектируемости", но мне не ясно, как представлять иерархические отношения и обрабатывать переключение одного представления на другое. Концептуально, для меня просто имеет смысл иметь модель представления, содержащую модели дочернего представления. Он правильно отображает иерархию представлений, замена представлений является несложной задачей, и если представление больше не требуется, связанная модель представления и все ее подчиненные будут подвергаться сборке мусора путем простого удаления ссылки на родительский объект.

Вопрос

Какова наилучшая практика для создания ViewModelLocator для обработки иерархических представлений, обмена представлениями в зависимости от режима системы и удаления представлений?

В частности:

  • Как вы организуете модели представлений так, чтобы иерархические отношения были четко представлены?
  • Как вы справляетесь с заменой одного существующего вида на другой (скажем, заменой звуковой панели навигационной панелью)?
  • Как вы гарантируете, что модели родительского и дочернего представлений освобождаются для сборки мусора, когда связанное родительское представление больше не требуется?

2 ответа

Решение

Оказывается, в 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/

Кажется, что текущее представление в иерархии представлений является частью представления "состояние", поэтому оно будет иметь собственную сущность "модель" (viewmodel), которая управляет этими отношениями. Я бы не использовал для этого контейнер IoC, но я бы использовал его, чтобы зарегистрировать фабрику, которую "менеджер представлений" использует для создания "подвидов".

Другие вопросы по тегам