MVVM (с WPF) - привязка нескольких видов к одной и той же модели ViewModel

Недавно я начал исследовать шаблон MVVM с WPF для предстоящего проекта. Я начал с статьи MSDN Джоша Смита. У меня есть вопрос (ну много, но пусть начнется с одного):

У меня есть IndividualViewModel, который предоставляет свойства модели. Мне нужно два представления "Добавить индивидуальное" и "Изменить индивидуальность", которые очень похожи, как вы можете себе представить. То, что я сделал в настоящее время, состоит в том, чтобы иметь 2 подкласса AddIndividualViewModel и EditIndividualViewModel, которые отображают команды Add и Edit соответственно. У меня также есть 2 аналогичных имени, которые привязаны к ним.

Теперь этот метод работает, и эти классы довольно малы в любом случае, но мне интересно, возможно ли, чтобы у меня была только одна модель представления, которая предоставляет обе команды. У меня было бы еще 2 вида, которые привязывались бы к этой же модели представления, отображая соответствующую команду в виде кнопки. Я не совсем уверен, как это сделать. В главном окне ресурсов у меня есть что-то вроде:

        <DataTemplate DataType="{x:Type ViewModels:AddIndividualViewModel}">
            <Views:AddIndividualView />
        </DataTemplate>

С помощью этого метода привязки вы можете иметь только привязку "один к одному", т.е. один и тот же вид всегда отображается для данной модели представления. Есть ли способ автоматически переключать представление в зависимости от свойства в модели представления (например, IndividualViewModel.Mode). Есть ли другой подход, который я должен рассмотреть?

Обратите внимание, что главное окно содержит коллекцию моделей просмотра и показывает каждый вкладку.

Спасибо!

Ответы

Ответ 1

Спасибо, что указал мне в правильном направлении! Я еще новичок в WPF и узнаю обо всех различных возможностях, включая методы привязки. В любом случае для всех, кого это интересует, вот решение, к которому я пришел для этого конкретного случая:

Я решил, что хочу, чтобы модели представлений разделялись в двух подклассах AddIndividualViewModel и EditIndividualViewModel, которые только выставляют команды, а не пытаются управлять состоянием в одном классе. Однако я хотел один вид, чтобы не дублировать XAML. В результате я использовал два DataTemplates и DataTemplateSelector для переключения кнопок действий в зависимости от модели представления:

        <DataTemplate x:Key="addTemplate">
            <Button Command="{Binding Path=AddCommand}">Add</Button>
        </DataTemplate>

        <DataTemplate x:Key="editTemplate">
            <Button Command="{Binding Path=UpdateCommand}">Update</Button>
        </DataTemplate>

        <TemplateSelectors:AddEditTemplateSelector
            AddTemplate="{StaticResource addTemplate}"
            EditTemplate="{StaticResource editTemplate}"
            x:Key="addEditTemplateSelector" />

и ведущий контента в нижней части формы:

        <ContentPresenter Content="{Binding}"
                          ContentTemplateSelector="{StaticResource addEditTemplateSelector}" />

Вот код для селектора шаблонов:

class AddEditTemplateSelector : DataTemplateSelector
{
    public DataTemplate AddTemplate { get; set; }
    public DataTemplate EditTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item is AddIndividualViewModel)
        {
            return AddTemplate;
        }
        else if (item is EditIndividualViewModel)
        {
            return EditTemplate;
        }

        return null;
    }
}

Это может быть или не быть, как реализовать последнюю вещь (учитывая требования), но хорошо видеть, что у меня есть такая возможность.

Ответ 2

Итак, вам нужно 2 разных представления, основанных на значении свойства. Следует рассмотреть рефакторинг вашего кода презентации, поэтому вместо значений свойства вы можете иметь реальные подклассы. Затем вы можете использовать 2 разных DataTemplate для каждого класса.

<DataTemplate DataType="{x:Type ViewModels:AddIndividualViewModel}">
  <Views:AddIndividualView />
</DataTemplate>

<DataTemplate DataType="{x:Type ViewModels:EditIndividualViewModel}">
  <Views:EditIndividualView />
</DataTemplate>

Если вы считаете, что это перебор, вы можете использовать триггер и обернуть ваши конкретные представления в ContentPresenter.

<DataTemplate x:Key="AddIndividualTemplate" DataType="{x:Type ViewModels:IndividualViewModel}">
  <Views:AddIndividualView />
</DataTemplate>

<DataTemplate x:Key="EditIndividualTemplate" DataType="{x:Type ViewModels:IndividualViewModel}">
  <Views:EditIndividualView />
</DataTemplate>

<DataTemplate DataType="{x:Type ViewModels:IndividualViewModel}">
  <ContentPresenter Content="{Binding}">
    <ContentPresenter.Style>
      <Style TargetType="ContentPresenter">
        <Setter Property="ContentTemplate" Value="{StaticResource AddIndividualTemplate}" />
        <Style.Triggers>
          <DataTrigger Binding="{Binding Mode}" Value="{x:Static ViewModels:IndividualMode.Edit}">
            <Setter Property="ContentTemplate" Value="{StaticResource EditIndividualTemplate}" />
          </DataTrigger>
        </Style.Triggers>
      </Style>
    </ContentPresenter.Style>
  </ContentPresenter>
</DataTemplate>

Ответ 3

Нет причин, по которым вы не сможете этого достичь. Одним из способов сделать это является предоставление некоторого флага в вашей модели представления, указывающего, находитесь ли вы в режиме добавления или в режиме редактирования и стилизуете свое представление на основе этого флага с помощью простых привязок, триггеров или селекторов шаблонов.

Для справки вы можете посмотреть Sacha Barber DataWrapper class, что часть его рамки Cinch (не относится непосредственно к вашему делу, но это хорошая отправная точка), который обертывает поля данных в модели представления таким образом, чтобы поддерживать флаг для переключения между режимом чтения (режим записи) и функцией чтения-записи (режим редактирования записи). Вы можете применить аналогичный подход, чтобы сделать различие между добавлением и редактированием.

В принципе, вместо наличия простых свойств в вашей модели представления, создайте экземпляр класса оболочки данных, который включает свойство Value и свойство IsAdding. По вашему мнению, вы можете использовать привязки, триггеры или селектор шаблонов для изменения шаблонов на основе этого свойства.

Ответ 4

Для этой задачи вам вообще не нужен какой-либо DataTemplateSelector.

  • Выведите как EditIndividualVM, так и AddINdividualVM из IndividualVM.
  • Путь Edit- и AddCommands к свойству setter в IndividualVM.
  • Установитель VM = новый AddIndividualVM или VM = новый EditIndividualVM в зависимости от того, какая кнопка нажата.
  • В xaml вы связываете в contentgrid с вашим свойством VM следующим образом: