пятница, 4 марта 2011 г.

Вкратце об MVVM

О паттерне MVVM написано достаточно много статей тут и там, а еще здесь. Я постараюсь кратко на простом примере пояснить суть паттерна.

Чем же MVVM особенный?

MVVM очередной вариант классического паттерна MVC, создавался компанией Microsoft специально для WPF.  Про отличия MVC, MVP и MVVM можно почитать в этой статье. В MVVM приложение делится на три части: Model, View, View-Model.

Модель (Model) содержит в себе всю логику приложение, при чем модель ничего не знает о том как ее будут использовать. Здесь не должно быть ничего лишнего, особенно это касается событий вроде "Модель изменилась - пора обновить графический интерфейс". В этом случае модель получается до ужаса простой, так как программисту не надо заботиться о дизайнере и наворачивать ненужные конструкции. Более того, модель проще тестировать.

VM (View-Model) прослойка, перегоняющая функционал модели в удобный для представления вид. А так же VM должна оповещать представление о том, что модель изменилась.

Представление (View) - обычный GUI. Тут надо отметить, что представление ничего не знает ни о модели, ни о VM. То есть, представление никак не связано с VM напрямую. То есть получается следующая связь:

Такое возможно только при наличии дусторонних связок, которые бы неявным образом синхронизировали поля у представления и VM. В WPF вкачестве таких связок будут байндинги (binding).

Давайте теперь разберем патерн на простеньком примере.

Надуманный пример. 

Сделаем список заметок, который можно редактировать. Добавлять и удалять записи НЕЛЬЗЯ, для этого нужны коммады, о них я расскажу в одном из следующих постов. У заметки будет название (Title) и описание (Description). Итак идем попорядку


Модель


class NoteModel
{
public String Title { get; set; }
public String Description { get; set; }

public NoteModel(string title = "untitled", string description = null)
{
Title = title;
Description = description;
}
}


View-Model


Все VM являются наследниками INotifyPropertyChanged. Этот интерфейс необходит для оповещения презентера обо всех изменениях в моделе.


class NotePresenter : INotifyPropertyChanged
{
NoteModel _model;

public string Title
{
get
{
return _model.Title;
}
set
{
_model.Title = value;
OnPropertyChanged("Title");
}
}

public string Description
{
get
{
return _model.Description;
}
set
{
_model.Description = value;
OnPropertyChanged("Description");
}
}

public NotePresenter(NoteModel model)
{
if (model == null) throw new ArgumentNullException("model");
_model = model;
}

#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string fieldName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(fieldName));
}
}

#endregion
}


Презентер

Особой красоты наводить не буду


<Window x:Class="Testing_MVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Testing MVVM" Height="350" Width="525"
>
    <Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Notes}" x:Name="ListOfNotes">
<ListBox.ItemTemplate>
<DataTemplate>
<Border CornerRadius="5" BorderBrush="Blue" Margin="5">
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding Description}"/>
StackPanel>
Border>
DataTemplate>
ListBox.ItemTemplate>
ListBox>
<ContentControl Grid.Column="1" Content="{Binding ElementName=ListOfNotes, Path=SelectedValue}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" MinWidth="100"/>
Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Title" 
  VerticalAlignment="Center"/>
<TextBox Grid.Row="0" Grid.Column="1" 
Text="{Binding Title, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Description" 
  VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1" 
Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}"/>
Grid>
DataTemplate>
ContentControl.ContentTemplate>
ContentControl>
Grid>
Window>

И гланое .cs  файл привязанный к разметке


public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();

//по хорошему презентер должен создаваться и устанавливаться в другом месте
//или передаваться в конструктор
DataContext = new NotesListPresenter(
new NoteModel("Утро", "Еле встал"),
new NoteModel("Обед", "отлично поел"),
new NoteModel("Вечер", "Смотрел футбол"),
new NoteModel("Ночь", "Сладко-сладко поспал"));
}
}

Скриншот


В левой части мы выбираем запись, в правой редактируем, при этом поля изменяются одновременно.


Итог


В разметке xaml нет упонимания ни о моделе, ни о VM, а  главную VM мы подсовываем интерфейсу в качестве DataContext. Главное не запутаться в DataContext-ах. В целом мы добились нужной связи.
Исходники лежат здесь.

Комментариев нет:

Отправить комментарий