MVVM – Model – View – ViewModel – паттерн организации PL (presentation layer – уровень представления).
Паттерн MVVM применяется при создании приложений с помощью WPF и Silverlight. Этот паттерн был придуман архитектором этих самых WPF и Silverlight - John Gossman (его блог). Паттерн MVVM применяется в Expression Blend.
Идеологически MVVM похож на Presentation Model описанный небезызвестным Фаулером, но MVVM сильно опирается на возможности WPF.
Основная особенность MVVM заключается в том, что все поведение выносится из представления (view) в модель представления (view model). Связывание представления и модели представления осуществляется декларативными байндингами в XAML разметке. Это позволяет тестировать все детали интерфейса не используя сложных инструментальных средств.
Я сначала хотел кратко описать применение MVVM и Unity для построения PL, но понял что одного поста для описания возможностей MVVM очень мало.
В WPF для передачи данных между объектами и визуальными элементами используются байндинги (binding – привязка) в простонародии биндинги. Передача может быть как однонаправленная, так и двунаправленная. Работают байндинги с помощью зависимых свойств (DependencyProperty) или интерфейса INotifyPropertyChanged. Передача управляющих воздействий от визуальных элементов осуществляется с помощью команд, реализующих интерфейс ICommand.
Для начала надоевший уже пример SayHello.
Как всегда используется супер-сложный класс бизнес логики:
public interface ISayHelloService
{
string SayHello(string name);
}
public class SayHelloSerivce : ISayHelloService
{
public string SayHello(string name)
{
return "Привет, " + name;
}
}
Теперь определение класса команды, которая состоит из пары делегатов
public class DelegateCommand : ICommand
{
Func<object, bool> _canExecute;
Action<object> _execute;
//Конструктор
public DelegateCommand(Func<object, bool> canExecute, Action<object> execute)
{
this._canExecute = canExecute;
this._execute = execute;
}
//Проверка доступности команды
public bool CanExecute(object parameter)
{
return this._canExecute(parameter);
}
//Выполнение команды
public void Execute(object parameter)
{
this._execute(parameter);
}
//Служебное событие
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested += value; }
}
}
Теперь напишем нашу модель представления.
public class ViewModel: INotifyPropertyChanged
{
//Имя
public string Name { get; set; }
//Текст приветствия
public string HelloText { get; set; }
//Команда
public ICommand SayHelloCommand
{
get
{
return _sayHelloCommand;
}
}
ISayHelloService _service;
ICommand _sayHelloCommand;
//Конструктор
public ViewModel(ISayHelloService service)
{
this._service = service;
//Создаем команду
this._sayHelloCommand = new DelegateCommand(
o => CanExecuteHello(),
o => ExecuteHello());
}
private void ExecuteHello()
{
this.HelloText = _service.SayHello(this.Name);
OnPropertyChanged("HelloText");
}
private bool CanExecuteHello()
{
return !string.IsNullOrEmpty(this.Name);
}
//Для поддержка байндинга
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this,
new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Получилось слегка многословно по причине того, что пример искусственный.
Дело за разметкой:
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Введите имя"/>
<TextBox Text="{Binding Name}" Grid.Column="1"/>
</Grid>
<TextBox Text="{Binding HelloText}"/>
<Button Content="Сказать привет" Command="{Binding SayHelloCommand}"/>
</StackPanel>
И немного изменим констрктор View:
public Window1(ViewModel model)
{
InitializeComponent();
DataContext = model;
}
В App.xaml уберем атрибут StartupUri, и добавим обработчик события Startup, в котором напишем следующий код:
var container = new UnityContainer();
container
.RegisterType<ViewModel>()
.RegisterType<ISayHelloService, SayHelloSerivce>();
var window = container.Resolve<Window1>();
window.Show();
Можно нажать F5 и смотреть что получилось.
Теперь воспользуемся фичами WPF.
Изменим код ViewModel.
public class ViewModel : INotifyPropertyChanged
{
//Имя
public string Name
{
get { return this._name; }
set
{
this._name = value;
OnPropertyChanged("Name");
OnPropertyChanged("HelloText");
}
}
//Текст приветствия
public string HelloText
{
get
{
return _service.SayHello(this.Name);
}
}
string _name;
ISayHelloService _service;
//Конструктор
public ViewModel(ISayHelloService service)
{
this._service = service;
}
//Для поддержка байндинга
#region INotifyPropertyChanged Members
//Без изменений
#endregion
}
В разметке View уберем кнопку и поставим Mode=OneWay для байндинга второго текстбокса.
Кроме этого слега изменим App.xml.cs
var container = new UnityContainer();
container
.RegisterType<ViewModel>(new ContainerControlledLifetimeManager())
.RegisterType<ISayHelloService, SayHelloSerivce>();
container.Resolve<Window1>().Show();
container.Resolve<Window1>().Show();
Два созданных окна будут разделять одну ViewModel и при вводе имени в одном из окон результат будет отображаться во всех.
8 коммент.:
Ручная реализация INotifyPropertyChanged - это самый злостный антипаттерн, который только можно придумать. Никогда, никогда так не делайте в рабочем коде!
Да и ручная реализация ICommand тоже.
Нужно качать MVVM Toolkit, там все это уже есть.
А как будет выглядеть ViewModel если комманд не 1-2, а десяток ?
Так и будет - 10 свойств с командами и 10-20 методов для обработки (сам код и опционльно проверка доступности).
Это полюбому меньше, чем делать аналогичное через обработчики событий.
Я бы рекоммендовал посмотреть на PRISM (http://www.codeplex.com/CompositeWPF) от Patterns and Practices команды, если еще не смотрели, все выглядит неплохо, архитектурный подход правильный, все слабосвязано, построено на Unity.
Только там не MVVM.
Согласен, там не конкретно MVVM, Prism не навязывает определенный UI паттерн, там можно и MVVM, и Passive View и Supervising Presenter. Для MVVM там реализация DelegateCommand и CompositeCommand, которая конечно особой сверхценности не несет, но для MVVM :)
Отправить комментарий