WPF. Должен ли пользовательский элемент управления иметь свой собственный ViewModel?
У меня есть окно, состоящее из нескольких пользовательских элементов управления, и задалось вопросом, имеет ли каждый пользовательский элемент управления свой собственный ViewModel, или если у окна в целом есть только один ViewModel?
Ответы
Ответ 1
Это не вопрос "да" или "нет". Это зависит от того, дает ли вам дополнительные модели обзора лучшую удобство и возможность проверки. Нет смысла добавлять модели заметок, если они ничего вам не принесут. Вам нужно будет оценить, стоит ли накладные расходы в вашем конкретном случае использования.
Ответ 2
Абсолютно, положительно
НЕТ
В ваших UserControls должно быть НЕ созданы ViewModels специально для них. Это, по сути, запах кода. Он не нарушает ваше приложение сразу, но это вызовет у вас боль при работе с ним.
Пользователь UserControl - это простой способ создать элемент управления с использованием. UserControls по-прежнему являются элементами управления и поэтому должны быть исключительно связаны с вопросами пользовательского интерфейса.
Когда вы создаете ViewModel для своего UserControl, вы либо размещаете бизнес-логику, либо логику интерфейса. Неправильно использовать ViewModels, чтобы содержать логику пользовательского интерфейса, поэтому, если это ваша цель, отбросьте свою виртуальную машину и поместите код в этот код управления. Если вы размещаете бизнес-логику в UserControl, скорее всего, вы используете ее для разделения частей своего приложения, а не для упрощения создания контроля. Элементы управления должны быть простыми и иметь одну цель, для которой они предназначены.
Когда вы создаете ViewModel для вашего UserControl, вы также нарушаете естественный поток данных через DataContext. Здесь вы испытаете большую боль. Чтобы продемонстрировать это, рассмотрим этот простой пример.
У нас есть ViewModel, который содержит людей, каждый из которых является экземпляром типа Person.
public class ViewModel
{
public IEnumerable<Person> People { get; private set; }
public ViewModel()
{
People = PeopleService.StaticDependenciesSuckToo.GetPeople();
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Чтобы показать список людей в нашем окне, тривиально.
<Window x:Class="YoureDoingItWrong.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:YoureDoingItWrong"
Title="Derp">
<Window.DataContext>
<l:ViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:Person}">
<l:PersonView />
</DataTemplate>
</Window.Resources>
<ListView ItemsSource="{Binding People}" />
</Window>
Список автоматически подбирает правильный шаблон для Лица и использует PersonView для отображения информации о пользователе пользователю.
Что такое PersonView? Это UserControl, который предназначен для отображения информации о человеке. Это элемент управления дисплеем для человека, аналогично тому, как TextBlock является элементом управления отображением текста. Он предназначен для привязки к Лику и, как таковой, работает плавно. Обратите внимание, что в окне выше показано, как ListView передает каждый экземпляр Person в PersonView, где он становится DataContext для этого поддерева визуального.
<UserControl x:Class="YoureDoingItWrong.PersonView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Label>Name</Label>
<TextBlock Text="{Binding Name}" />
<Label>Age</Label>
<TextBlock Text="{Binding Age}" />
</StackPanel>
</UserControl>
Чтобы это работало гладко, ViewModel UserControl должен быть экземпляром Type, для которого он предназначен. Когда вы нарушаете это, делая глупые вещи, такие как
public PersonView()
{
InitializeComponent();
this.DataContext = this; // omfg
}
или
public PersonView()
{
InitializeComponent();
this.DataContext = new PersonViewViewModel();
}
вы нарушили простоту модели. Обычно в этих случаях вы оказываетесь в отвратительных обходных решениях, наиболее распространенным из которых является создание свойства pseudo-DataContext для того, каким должен быть ваш DataContext. И теперь вы не можете привязать одного к другому, так что вы получите ужасные хаки, например
public partial class PersonView : UserControl
{
public PersonView()
{
InitializeComponent();
var vm = PersonViewViewModel();
// JUST KILL ME NOW, GET IT OVER WITH
vm.PropertyChanged = (o, e) =>
{
if(e.Name == "Age" && MyRealDataContext != null)
MyRealDataContext.Age = vm.PersonAge;
};
this.DataContext = vm;
}
public static readonly DependencyProperty MyRealDataContextProperty =
DependencyProperty.Register(
"MyRealDataContext",
typeof(Person),
typeof(PersonView),
new UIPropertyMetadata());
public Person MyRealDataContext
{
get { return (Person)GetValue(MyRealDataContextProperty); }
set { SetValue(MyRealDataContextProperty, value); }
}
}
Вы должны думать о UserControl как о более сложном контроле. Имеет ли TextBox собственный ViewModel? Нет. Вы привязываете свое свойство VM к свойству Text элемента управления, а элемент управления отображает текст в пользовательском интерфейсе.
MVVM не поддерживает "No Codebehind". Поместите логику пользовательского интерфейса для вашего пользовательского элемента управления в код. Если он настолько сложный, что вам нужна бизнес-логика внутри пользовательского элемента управления, это предполагает, что он слишком всеобъемлющий. Упрощать!
Подумайте о UserControls в MVVM, как это: для каждой модели у вас есть UserControl, и она предназначена для представления данных в этой модели пользователю. Вы можете использовать его в любом месте, где хотите показать пользователю эту модель. Нужна ли вам кнопка? Выделите свойство ICommand на свой UserControl и позвольте вашей бизнес-логике связываться с ним. Нужно ли вашей бизнес-логике знать, что происходит внутри? Добавьте маршрутизируемое событие.
Как правило, в WPF, если вы спрашиваете, почему ему больно что-то делать, это потому, что вы не должны этого делать.
Ответ 3
Я бы сказал, что каждый пользовательский элемент управления должен иметь свой собственный ViewModel, потому что это позволит вам повторно использовать пару ViewModel/UserControl в новых созвездиях в будущем.
Как я понимаю, ваше окно представляет собой составной элемент пользовательских элементов управления, поэтому вы всегда можете создать ViewModel, который объединяет все отдельные ViewModels для каждого из элементов управления пользователя. Это даст вам лучшее из обоих миров.
Ответ 4
[должен ли] каждый пользовательский элемент управления иметь свою собственную ViewModel или окно должно иметь только одну ViewModel?
К сожалению, ответ на этот вопрос, получивший наибольшее количество голосов, вводит в заблуждение и основан на комментариях, которыми я обменивался в других вопросах, что дает плохое руководство людям, пытающимся изучать WPF. Этот ответ отвечает:
Ваши UserControls не должны НЕ иметь ViewModels, разработанные специально для них.
Проблема в том, что не тот вопрос, который был задан.
Я бы согласился с общим мнением о том, что когда вы пишете UserControl
, публичный API элемента управления не должен включать в себя также создание типа модели представления, специально предназначенного для использования для этого элемента управления. Клиентский код должен иметь возможность использовать любую модель представления, которую он хочет.
Но это не исключает идеи о том, что "каждый пользовательский элемент управления может иметь свой собственный ViewMomdel". Есть как минимум два очевидных сценария, которые я могу придумать, где ответом будет "да, модель представления для каждого пользовательского элемента управления":
Пользовательский элемент управления является частью шаблона данных в представителе элементов (например, ItemsControl
). В этом случае модель представления будет соответствовать каждому отдельному элементу данных, и между объектом модели представления и пользовательским элементом управления, представляющим этот объект модели представления, будет взаимно-однозначное соответствие.
В этом сценарии объект модели представления "не предназначен специально для них" (поэтому нет противоречия с сомнительным ответом), но это, безусловно, тот случай, когда каждый пользовательский элемент управления имеет свою собственную модель представления (что делает ответ на актуальный вопрос "Да, у каждого пользовательского элемента управления может быть своя модель представления").
Реализация пользовательского элемента управления использует или даже требует структуры данных модели представления, специально разработанной для пользовательского элемента управления. Эта структура данных модели представления не будет представлена клиентскому коду; это деталь реализации, и как таковая она будет скрыта от клиентского кода с помощью пользовательского элемента управления. Но это, безусловно, по-прежнему будет структура данных модели представления, "разработанная специально для" этого пользовательского элемента управления.
Этот сценарий явно не является проблематичным, что прямо противоречит утверждению о том, что "Ваши UserControls должны НЕ иметь ViewModels, разработанные специально для них. "
Я не думаю, что когда-либо автор этого ответа намеревался исключить любой из этих сценариев. Но проблема в том, что люди, которые пытаются изучить WPF, могут не иметь достаточного контекста, чтобы распознать разницу, и, таким образом, могут неправильно обобщать в отношении пользовательских элементов управления и моделей просмотра на основе этого решительного, высоко оцененного и вводящего в заблуждение ответа.
Я надеюсь, что, представив эту альтернативную точку зрения в качестве пояснения и ответив на исходный вопрос в менее узком смысле, те, кто нашел этот вопрос, узнав больше о WPF, получат лучший контекст и лучшую идею и когда модель представления может быть реализована специально для пользовательского элемента управления, а когда - нет.
Ответ 5
Я предполагаю, что ваше приложение выполняет какую-то композицию вида, поэтому, если вы сделаете свой пользовательский элемент управления собственной моделью просмотра, у вас будет больше свободы для встраивания их в другие окна хоста без изменения модели глобального представления окна.
В качестве дополнительного бонуса ваше приложение будет более подходящим для перехода на более архитектурно-звуковую композиционную модель, как это предусмотрено рамками Prism или Caliburn, если возникнут требования приложения.