Толстые модели, худые ViewModels и немые виды, лучший подход MVVM?
Благодаря щедрой помощи в этом вопросе я собрал следующую структуру MVVM, которая отображает изменения модели в реальном времени в XAML (текущая дата / время), очень приятно.
Крутое преимущество этой настройки заключается в том, что когда вы смотрите на свое представление в режиме разработки Visual Studio или Blend, вы видите, что время идет, что означает, что во время разработки у вас есть доступ к оперативным данным из вашей модели.
В процессе заставить это работать, я был удивлен, увидев, что большая часть основной массы переместилась из моей ViewModel в мою модель, включая реализацию INotifyPropertyChange. Другое изменение заключается в том, что я больше не привязываю свойства к ViewModel, а к методам.
Так что в настоящее время это мой любимый вкус MVVM:
Вид тупой:
- один ObjectDataProvider для каждого нужного вам объекта из вашей модели
- каждый ObjectDataProvider сопоставляется с методом в ViewModel (не является свойством)
- нет x: имя свойства в элементах XAML
ViewModel тощий:
- единственная вещь в вашей ViewModel - это методы, к которым привязывается ваше представление
Модель толстая:
- модель реализует INotifyPropertyChanged для каждого из своих свойств.
- для каждого метода в вашей модели представления (например, GetCurrentCustomer) в вашей модели есть соответствующий одноэлементный метод (например, GetCurrentCustomer).
- модель заботится о любой поточной функциональности в реальном времени, как в этом примере
Вопросы:
- Те из вас, кто внедрял MVVM в реальных сценариях, является ли эта базовая структура, на которой вы также остановились, и если нет, как ваша?
- Как бы вы расширили это, чтобы включить перенаправленные команды и перенаправленные события?
Следующий код будет работать, если вы просто скопируете XAML и код в новый проект WPF.
XAML:
<Window x:Class="TestBinding99382.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestBinding99382"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider
x:Key="DataSourceCustomer"
ObjectType="{x:Type local:ShowCustomerViewModel}"
MethodName="GetCurrentCustomer"/>
</Window.Resources>
<DockPanel DataContext="{StaticResource DataSourceCustomer}">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="{Binding Path=FirstName}"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding Path=LastName}"/>
</StackPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="{Binding Path=TimeOfMostRecentActivity}"/>
</StackPanel>
</DockPanel>
</Window>
Код позади:
using System.Windows;
using System.ComponentModel;
using System;
using System.Threading;
namespace TestBinding99382
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
//view model
public class ShowCustomerViewModel
{
public Customer GetCurrentCustomer() {
return Customer.GetCurrentCustomer();
}
}
//model
public class Customer : INotifyPropertyChanged
{
private string _firstName;
private string _lastName;
private DateTime _timeOfMostRecentActivity;
private static Customer _currentCustomer;
private Timer _timer;
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
this.RaisePropertyChanged("FirstName");
}
}
public string LastName
{
get
{
return _lastName;
}
set
{
_lastName = value;
this.RaisePropertyChanged("LastName");
}
}
public DateTime TimeOfMostRecentActivity
{
get
{
return _timeOfMostRecentActivity;
}
set
{
_timeOfMostRecentActivity = value;
this.RaisePropertyChanged("TimeOfMostRecentActivity");
}
}
public Customer()
{
_timer = new Timer(UpdateDateTime, null, 0, 1000);
}
private void UpdateDateTime(object state)
{
TimeOfMostRecentActivity = DateTime.Now;
}
public static Customer GetCurrentCustomer()
{
if (_currentCustomer == null)
{
_currentCustomer = new Customer
{ FirstName = "Jim"
, LastName = "Smith"
, TimeOfMostRecentActivity = DateTime.Now
};
}
return _currentCustomer;
}
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
2 ответа
Вот мое мнение, для чего это стоит:
Я не совсем согласен с подходом, который вы предлагаете (за исключением тупой точки зрения). В реальной жизни вам часто приходится использовать существующую модель: это может быть устаревший код, который у вас нет времени (или воли) для изменения, или даже библиотека, для которой у вас нет кода. По моему мнению, модель должна полностью не знать, как она будет отображаться, и ее легко использовать в приложении, не поддерживающем WPF. Таким образом, он не должен реализовывать какой-либо конкретный интерфейс, как INotifyPropertyChanged
из INotifyCollectionChanged
чтобы сделать его пригодным для использования в MVVM. Я думаю, что вся логика, связанная с пользовательским интерфейсом, должна находиться во ViewModel.
относительно RoutedEvents
а также RoutedCommands
они не очень подходят для использования с шаблоном MVVM. Я обычно стараюсь использовать как можно меньше RoutedEvents
как можно и нет RoutedCommands
совсем. Вместо этого мои ViewModels выставляют RelayCommand
свойства, которые я связываю с пользовательским интерфейсом в XAML (подробности об этой статье см. в статье Джоша Смита) RelayCommand
). Когда мне действительно нужно обрабатывать события для какого-то элемента управления, я использую прикрепленное поведение, чтобы сопоставить события командам ViewModel (взгляните на реализацию Марлона Греча)
Итак, в итоге:
- Тупой вид
- Большой и умный ViewModel
- Любая модель, которую вы хотите или должны использовать
Конечно, это только мой подход, и он может быть не лучшим, но я чувствую себя вполне комфортно с ним;)
Я согласен с Томасом. Мой совет всем, кто интересуется архитектурой WPF:
- Простые объекты POCO без INotifyPropertyChange, отслеживания состояния, BL и т. Д.
- Простые и маленькие ViewModels, которые уведомляют Представления точно в срок
- Простой многократно используемый пользовательский интерфейс с интеллектуальной системой навигации, которая позволяет избежать сложных иерархий данных и сложных базовых моделей представления
- MVVM с подходом View First для простоты зависимостей
- Асинхронные операции с задачами или Rx
- Простая тема
- Нет сложного надежного пользовательского интерфейса, сделайте его простым, просто воспользуйтесь преимуществами композиции и связывания пользовательского интерфейса WPF
- Не стесняйтесь использовать программный код для динамического создания контента (формы, списки и т. Д.) И сэкономить значительное время на декларативной конфигурации глаз (относится к большинству случаев) - и для меня это обязательно в 2015 году. Используйте методы расширения для создания Fluent API для этого.