WPF - должен ли пользовательский элемент управления иметь собственную ViewModel? - PullRequest
23 голосов
/ 21 декабря 2009

У меня есть окно, состоящее из нескольких пользовательских элементов управления, и мне было интересно, имеет ли каждый пользовательский элемент управления свою собственную ViewModel или у всего окна должно быть только одно ViewModel?

Ответы [ 4 ]

32 голосов
/ 02 марта 2015

Абсолютно, положительно

NO

Ваши UserControls должны НЕ иметь ViewModels, разработанные специально для них. Это, по сути, запах кода. Это не сломает ваше приложение сразу, но при работе с ним вы почувствуете боль.

UserControl - это просто простой способ создания элемента управления с использованием композиции. UserControls по-прежнему являются элементами управления, и поэтому должны заниматься исключительно вопросами пользовательского интерфейса.

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

Когда вы создаете ViewModel для вашего UserControl, вы также нарушаете естественный поток данных через DataContext. Это где вы будете испытывать больше всего боли. Чтобы продемонстрировать, рассмотрим этот простой пример.

У нас есть ViewModel, которая содержит People, каждый из которых является экземпляром типа 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>

Список автоматически выбирает правильный шаблон элемента для Person и использует 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 должен быть экземпляром типа, для которого он предназначен . Когда ты нарушаешь это, делая глупости вроде

public PersonView()
{
    InitializeComponent();
    this.DataContext = this; // omfg
}

или

public PersonView()
{
    InitializeComponent();
    this.DataContext = new PersonViewViewModel();
}

вы нарушили простоту модели. Обычно в этих случаях вы получаете отвратительные обходные пути, наиболее распространенным из которых является создание свойства псевдо-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 своя собственная модель представления? Нет. Вы связываете свойство своей виртуальной машины со свойством Text элемента управления, и элемент управления отображает ваш текст в своем пользовательском интерфейсе.

MVVM не означает «Нет кода». Поместите свою логику пользовательского интерфейса для пользовательского контроля в коде позади. Если это настолько сложно, что вам нужна бизнес-логика внутри пользовательского элемента управления, это говорит о том, что она слишком всеобъемлющая. Упростить!

Думайте о UserControls в MVVM следующим образом: для каждой модели у вас есть UserControl, и он предназначен для представления данных в этой модели пользователю. Вы можете использовать его где угодно, чтобы показать пользователю эту модель. Нужна ли кнопка? Предоставьте свойство ICommand в вашем UserControl и позвольте вашей бизнес-логике связываться с ним. Ваша бизнес-логика должна знать, что происходит внутри? Добавьте перенаправленное событие.

Обычно в WPF вы спрашиваете, почему так больно делать что-то, потому что вы не должны этого делать.

11 голосов
/ 21 декабря 2009

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

6 голосов
/ 21 декабря 2009

Я бы сказал, что каждый пользовательский элемент управления должен иметь свою собственную ViewModel, потому что это позволит вам в будущем повторно использовать пару ViewModel / UserControl в новых созвездиях.

Насколько я понимаю, ваше окно является Композитом пользовательских элементов управления, поэтому вы всегда можете создать ViewModel, которая объединяет все отдельные ViewModel для каждого из пользовательских элементов управления. Это даст вам лучшее из обоих миров.

1 голос
/ 22 декабря 2009

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

В качестве дополнительного бонуса ваше приложение будет более подходящим для перехода к более архитектурно-обоснованной модели композиции, чем та, которая предоставляется средами Prism или Caliburn, если возникнут требования к приложению.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...