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.