Цель лабораторной работы: показать реализацию
На самом деле MVVM – это просто паттерн, и для его применения не нужны никакие тулкиты. Все тулкиты – это просто хэлперы, которые облегчают Вам жизнь, избавляя от рутины. Согласно Википедии сейчас существует более 14 различных MVVM toolkit’ов.
В лекции №13 вполне детально был рассмотрен паттерн MVVM, поэтому описание использования MVVM toolkit’а в приведенном ниже учебном элементе будет предельно краткое и схематичное.
Большинство MVVM Toolkit’ов включают шаблон для Visual Studio и (или) Expression Blend:
(рис 14.1) Шаблон MVVM toolkit’а для Expression Blend
Если мы реализуем паттерн MVVM средствами Model-View-ViewModel Toolkit 0.1, то данный тулкит сразу создает следующую структуру файлов в проекте:
(рис 14.2) Структура файлов в проекте, созданном при помощи MVVM toolkit’а
Возьмем какую-нибудь каноничную задачу. Например, отображение списка книг читального зала. У книги есть:
Пусть к нам ходят читатели и то и дело берут книги почитать или возвращают их обратно. Требуется в любой момент знать, сколько экземпляров той или иной книги у нас осталось, и можем ли мы ее выдать.
В рассматриваемой задаче модель будет состоять из одного простого класса: Book.cs
class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Count { get; set; }
public Book(string title, string author, int count)
{
this.Title = title;
this.Author = author;
this.Count = count;
}
}
Напишем ViewModel для нашей модели: Book.cs
class BookViewModel: ViewModelBase
{
public Book Book;
public BookViewModel(Book book)
{
this.Book = book;
}
public string Title
{
get { return Book.Title; }
set
{
Book.Title = value;
OnPropertyChanged("Title");
}
}
public string Author
{
get { return Book.Author; }
set
{
Book.Author = value;
OnPropertyChanged("Author");
}
}
public int Count
{
get { return Book.Count; }
set
{
Book.Count = value;
OnPropertyChanged("Count");
}
}
}
BookViewModel унаследован от класса ViewModelBase, который предоставляет нам MVVM Toolkit. ViewModelBase в свою очередь, реализует интерфейс INotifyPropertyChanged и содержит функцию OnPropertyChanged. Все это нужно для того, чтобы всегда можно было вызвать событие "изменилось такое-то поле". Как видно в коде, при любом изменении поля мы такое событие вызываем и передаем в качестве параметра его название. Потом на форме биндинг может это событие обработать и, как следствие, интерфейс и ViewModel всегда будут друг с другом синхронизированы.
Помимо BookViewModel у нас есть еще класс MainViewModel, уже сгенерированный и даже связанный с формой. Добавим в него поле:
ObservableCollection<BookViewModel> BooksList { get; set; }
Также слегка изменим конструктор:
public MainViewModel(List<Book> books)
{
BooksList = new ObservableCollection<BookViewModel>
(books.Select(b => new BookViewModel(b)));
}
Это и есть наше окно, либо User Control. У любого FrameworkElement-а есть поле DataContext. DataContext может быть любым объектом, иметь какие угодно поля, а его главная задача — являться источником данных для Databinding-а. Форма у нас всего одна, DataContext для нее заполняется в методе OnStartup, что в App.xaml.cs. Немного модифицируем то, что сделал нам MVVM Toolkit, получится следующее: App.xaml.cs
private void OnStartup(object sender, StartupEventArgs e)
{
List<Book> books = new List<Book>()
{
new Book("Колобок", null, 3),
new Book("CLR via C#", "Джеффри Рихтер", 1),
new Book("Война и мир", "Л.Н. Толстой", 2)
};
MainView view = new MainView(); // создали View
MainViewModel viewModel =
new ViewModels.MainViewModel(books); // Создали ViewModel
view.DataContext = viewModel; // положили ViewModel во View в качестве
// DataContext
view.Show();
}
Осталось написать XAML-код формы. MainView.xaml
<Window x:Class="SampleMVVM.Views.MainView"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:c="clr-namespace:SampleMVVM.Commands"
Title="Main Window" Height="400" Width="800">
<ListView ItemsSource="{Binding BooksList}">
<ListView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" BorderThickness="1" Margin="10">
<StackPanel Margin="10">
<TextBlock Text="{Binding Title}" FontWeight="Bold"/>
<TextBlock Text="{Binding Author}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Осталось:" />
<TextBlock Text="{Binding Count}"
FontWeight="Bold" Margin="10,0"/>
<TextBlock Text="шт" />
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Window>
Обратите внимание на конструкцию Binding в разметке формы. Именно таким образом можно привязывать поля объекта, находящегося в DataContext-е, к атрибутам контролов. Мы не написали ни строчки кода, но тем не менее при запуске получим следующее:
(рис 14.3) Результат проекта созданного при помощи MVVM toolkit’а
Сделаем так, что для выделенной в списке книги будет открываться редактор. Изменим XAML-разметку формы: MainView.xaml
<Window x:Class="SampleMVVM.Views.MainView"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:c="clr-namespace:SampleMVVM.Commands"
Title="Main Window" Height="400" Width="350">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding BooksList}"
IsSynchronizedWithCurrentItem="True">
<ListView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" BorderThickness="1"
Margin="10">
<StackPanel Margin="10">
<TextBlock Text="{Binding Title}"
FontWeight="Bold"/>
<TextBlock Text="{Binding Author}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Осталось:" />
<TextBlock Text="{Binding Count}"
FontWeight="Bold" Margin="10,0"/>
<TextBlock Text="шт" />
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ContentControl Grid.Column="1" Content="{Binding BooksList}">
<ContentControl.ContentTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" BorderThickness="1"
Margin="10">
<StackPanel Margin="10">
<TextBlock Text="Название:"/>
<TextBox Text="{Binding Title,
UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,10"/>
<TextBlock Text="Автор:"/>
<TextBox Text="{Binding Author,
UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,10"/>
</StackPanel>
</Border>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</Window>
Стоит обратить внимание на конструкцию UpdateSourceTrigger=PropertyChanged в строке биндинга. Это значит, что любое изменение, производимое в данном поле, будет немедленно отражаться на источнике. Это легко увидеть:
(рис 14.4) Результат конструкции UpdateSourceTrigger=PropertyChanged
Если этого не написать, источник будет обновляться только по окончании редактирования (т.е. когда контрол будет терять фокус). Это может привести к следующей ошибке интерфейса: когда жмешь "Сохранить", сохраняется все, кроме только что измененного поля.
Добавим в приложение функциональности. Пусть некие читатели берут книги и возвращают. Соответственно, сделаем две кнопки — "Выдать" и "Забрать", меняющие количество имеющихся в наличии книг. Если книг не осталось (Count = 0), кнопка "Выдать" должна дизаблиться.
В MVVM не пишутся обработчики событий. Функции, которые нужно выполнять контролам, пишутся во ViewModel и биндятся к контролам точно так же, как поля. Только используется механизм команд.
Команда должна представлять из себя экземпляр класса, реализующего интерфейс ICommand. К счастью, MVVM Toolkit снова нам помог и сгенерил целых два таких класса - DelegateCommand для реализации команды без параметров и DelegateCommand<T> - для реализации команды с параметром типа T.
Мы параметры передавать не будем. Код во ViewModel будет таков: BookViewModel.cs
#region Забрать
private DelegateCommand getItemCommand;
public ICommand GetItemCommand
{
get
{
if (getItemCommand == null)
{
getItemCommand = new DelegateCommand(GetItem);
}
return getItemCommand;
}
}
private void GetItem()
{
Count++;
}
#endregion
#region Выдать
private DelegateCommand giveItemCommand;
public ICommand GiveItemCommand
{
get
{
if (giveItemCommand == null)
{
giveItemCommand = new DelegateCommand(GiveItem, CanGiveItem);
}
return giveItemCommand;
}
}
private void GiveItem()
{
Count--;
}
private bool CanGiveItem()
{
return Count > 0;
}
#endregion
Обратите внимание, что этот код добавляется в BookViewModel, а не в MainViewModel. Дело в том, что мы будем добавлять кнопки в ContentControl, DataContext-ом которого является именно BookViewModel.
С первой командой все ясно. Создали команду, и назначили ей в качестве действия метод GetItem, который и будет вызываться при ее активации. Со второй немного интереснее, но тоже просто. Помимо того, что она выполняет некоторое действие, она еще и может проверять с помощью метода CanGiveItem(), может она выполняться или нет.
В XAML-разметку нашей формы добавим следующее
<ContentControl Grid.Column="1" Content="{Binding BooksList}">
<ContentControl.ContentTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" BorderThickness="1" Margin="10">
<StackPanel Margin="10">
<TextBlock Text="Название:"/>
<TextBox Text="{Binding Title,
UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,10"/>
<TextBlock Text="Автор:"/>
<TextBox Text="{Binding Author,
UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,10"/>
<StackPanel Orientation="Horizontal">
<Button Content="Выдать" Command="{Binding
GiveItemCommand}" Margin="10,0" />
<Button Content="Забрать" Command="{Binding
GetItemCommand}" Margin="10,0" />
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Вот и все. Мы получили требуемую функциональность. Количество экземпляров книги увеличивается и уменьшается, а когда их становится 0, кнопка "Выдать" дизаблится (благодаря упомянутому CanGiveItem).
В приведенном приложении все данные и реализация логики вынесены в отдельное место. В фоновом коде формы мы не добавили ни строчки. XAML понятен. Благодаря паттерну MVVM в коде легко разобраться и его легко сопровождать. Стоит добавить, что рассмотренный MVVM Toolkit содержит весьма полезный набор классов, особенно хорошо подходящий для небольших задач, не требующих модульности или необходимости использовать IoC. Для сложных приложений, можно рекомендовать более "тяжелые" Toolkit’ы, один из них мы будем рассматривать в последующих лекциях.
Цель лабораторной работы: показать реализацию
На самом деле MVVM – это просто паттерн, и для его применения не нужны никакие тулкиты. Все тулкиты – это просто хэлперы, которые облегчают Вам жизнь, избавляя от рутины. Согласно Википедии сейчас существует более 14 различных MVVM toolkit’ов.
В лекции №13 вполне детально был рассмотрен паттерн MVVM, поэтому описание использования MVVM toolkit’а в приведенном ниже учебном элементе будет предельно краткое и схематичное.
Большинство MVVM Toolkit’ов включают шаблон для Visual Studio и (или) Expression Blend:
(рис 14.1) Шаблон MVVM toolkit’а для Expression Blend
Если мы реализуем паттерн MVVM средствами Model-View-ViewModel Toolkit 0.1, то данный тулкит сразу создает следующую структуру файлов в проекте:
(рис 14.2) Структура файлов в проекте, созданном при помощи MVVM toolkit’а
Возьмем какую-нибудь каноничную задачу. Например, отображение списка книг читального зала. У книги есть:
Пусть к нам ходят читатели и то и дело берут книги почитать или возвращают их обратно. Требуется в любой момент знать, сколько экземпляров той или иной книги у нас осталось, и можем ли мы ее выдать.
В рассматриваемой задаче модель будет состоять из одного простого класса: Book.cs
class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Count { get; set; }
public Book(string title, string author, int count)
{
this.Title = title;
this.Author = author;
this.Count = count;
}
}
Напишем ViewModel для нашей модели: Book.cs
class BookViewModel: ViewModelBase
{
public Book Book;
public BookViewModel(Book book)
{
this.Book = book;
}
public string Title
{
get { return Book.Title; }
set
{
Book.Title = value;
OnPropertyChanged("Title");
}
}
public string Author
{
get { return Book.Author; }
set
{
Book.Author = value;
OnPropertyChanged("Author");
}
}
public int Count
{
get { return Book.Count; }
set
{
Book.Count = value;
OnPropertyChanged("Count");
}
}
}
BookViewModel унаследован от класса ViewModelBase, который предоставляет нам MVVM Toolkit. ViewModelBase в свою очередь, реализует интерфейс INotifyPropertyChanged и содержит функцию OnPropertyChanged. Все это нужно для того, чтобы всегда можно было вызвать событие "изменилось такое-то поле". Как видно в коде, при любом изменении поля мы такое событие вызываем и передаем в качестве параметра его название. Потом на форме биндинг может это событие обработать и, как следствие, интерфейс и ViewModel всегда будут друг с другом синхронизированы.
Помимо BookViewModel у нас есть еще класс MainViewModel, уже сгенерированный и даже связанный с формой. Добавим в него поле:
ObservableCollection<BookViewModel> BooksList { get; set; }
Также слегка изменим конструктор:
public MainViewModel(List<Book> books)
{
BooksList = new ObservableCollection<BookViewModel>
(books.Select(b => new BookViewModel(b)));
}
Это и есть наше окно, либо User Control. У любого FrameworkElement-а есть поле DataContext. DataContext может быть любым объектом, иметь какие угодно поля, а его главная задача — являться источником данных для Databinding-а. Форма у нас всего одна, DataContext для нее заполняется в методе OnStartup, что в App.xaml.cs. Немного модифицируем то, что сделал нам MVVM Toolkit, получится следующее: App.xaml.cs
private void OnStartup(object sender, StartupEventArgs e)
{
List<Book> books = new List<Book>()
{
new Book("Колобок", null, 3),
new Book("CLR via C#", "Джеффри Рихтер", 1),
new Book("Война и мир", "Л.Н. Толстой", 2)
};
MainView view = new MainView(); // создали View
MainViewModel viewModel =
new ViewModels.MainViewModel(books); // Создали ViewModel
view.DataContext = viewModel; // положили ViewModel во View в качестве
// DataContext
view.Show();
}
Осталось написать XAML-код формы. MainView.xaml
<Window x:Class="SampleMVVM.Views.MainView"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:c="clr-namespace:SampleMVVM.Commands"
Title="Main Window" Height="400" Width="800">
<ListView ItemsSource="{Binding BooksList}">
<ListView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" BorderThickness="1" Margin="10">
<StackPanel Margin="10">
<TextBlock Text="{Binding Title}" FontWeight="Bold"/>
<TextBlock Text="{Binding Author}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Осталось:" />
<TextBlock Text="{Binding Count}"
FontWeight="Bold" Margin="10,0"/>
<TextBlock Text="шт" />
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Window>
Обратите внимание на конструкцию Binding в разметке формы. Именно таким образом можно привязывать поля объекта, находящегося в DataContext-е, к атрибутам контролов. Мы не написали ни строчки кода, но тем не менее при запуске получим следующее:
(рис 14.3) Результат проекта созданного при помощи MVVM toolkit’а
Сделаем так, что для выделенной в списке книги будет открываться редактор. Изменим XAML-разметку формы: MainView.xaml
<Window x:Class="SampleMVVM.Views.MainView"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:c="clr-namespace:SampleMVVM.Commands"
Title="Main Window" Height="400" Width="350">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding BooksList}"
IsSynchronizedWithCurrentItem="True">
<ListView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" BorderThickness="1"
Margin="10">
<StackPanel Margin="10">
<TextBlock Text="{Binding Title}"
FontWeight="Bold"/>
<TextBlock Text="{Binding Author}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Осталось:" />
<TextBlock Text="{Binding Count}"
FontWeight="Bold" Margin="10,0"/>
<TextBlock Text="шт" />
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ContentControl Grid.Column="1" Content="{Binding BooksList}">
<ContentControl.ContentTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" BorderThickness="1"
Margin="10">
<StackPanel Margin="10">
<TextBlock Text="Название:"/>
<TextBox Text="{Binding Title,
UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,10"/>
<TextBlock Text="Автор:"/>
<TextBox Text="{Binding Author,
UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,10"/>
</StackPanel>
</Border>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</Window>
Стоит обратить внимание на конструкцию UpdateSourceTrigger=PropertyChanged в строке биндинга. Это значит, что любое изменение, производимое в данном поле, будет немедленно отражаться на источнике. Это легко увидеть:
(рис 14.4) Результат конструкции UpdateSourceTrigger=PropertyChanged
Если этого не написать, источник будет обновляться только по окончании редактирования (т.е. когда контрол будет терять фокус). Это может привести к следующей ошибке интерфейса: когда жмешь "Сохранить", сохраняется все, кроме только что измененного поля.
Добавим в приложение функциональности. Пусть некие читатели берут книги и возвращают. Соответственно, сделаем две кнопки — "Выдать" и "Забрать", меняющие количество имеющихся в наличии книг. Если книг не осталось (Count = 0), кнопка "Выдать" должна дизаблиться.
В MVVM не пишутся обработчики событий. Функции, которые нужно выполнять контролам, пишутся во ViewModel и биндятся к контролам точно так же, как поля. Только используется механизм команд.
Команда должна представлять из себя экземпляр класса, реализующего интерфейс ICommand. К счастью, MVVM Toolkit снова нам помог и сгенерил целых два таких класса - DelegateCommand для реализации команды без параметров и DelegateCommand<T> - для реализации команды с параметром типа T.
Мы параметры передавать не будем. Код во ViewModel будет таков: BookViewModel.cs
#region Забрать
private DelegateCommand getItemCommand;
public ICommand GetItemCommand
{
get
{
if (getItemCommand == null)
{
getItemCommand = new DelegateCommand(GetItem);
}
return getItemCommand;
}
}
private void GetItem()
{
Count++;
}
#endregion
#region Выдать
private DelegateCommand giveItemCommand;
public ICommand GiveItemCommand
{
get
{
if (giveItemCommand == null)
{
giveItemCommand = new DelegateCommand(GiveItem, CanGiveItem);
}
return giveItemCommand;
}
}
private void GiveItem()
{
Count--;
}
private bool CanGiveItem()
{
return Count > 0;
}
#endregion
Обратите внимание, что этот код добавляется в BookViewModel, а не в MainViewModel. Дело в том, что мы будем добавлять кнопки в ContentControl, DataContext-ом которого является именно BookViewModel.
С первой командой все ясно. Создали команду, и назначили ей в качестве действия метод GetItem, который и будет вызываться при ее активации. Со второй немного интереснее, но тоже просто. Помимо того, что она выполняет некоторое действие, она еще и может проверять с помощью метода CanGiveItem(), может она выполняться или нет.
В XAML-разметку нашей формы добавим следующее
<ContentControl Grid.Column="1" Content="{Binding BooksList}">
<ContentControl.ContentTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" BorderThickness="1" Margin="10">
<StackPanel Margin="10">
<TextBlock Text="Название:"/>
<TextBox Text="{Binding Title,
UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,10"/>
<TextBlock Text="Автор:"/>
<TextBox Text="{Binding Author,
UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,10"/>
<StackPanel Orientation="Horizontal">
<Button Content="Выдать" Command="{Binding
GiveItemCommand}" Margin="10,0" />
<Button Content="Забрать" Command="{Binding
GetItemCommand}" Margin="10,0" />
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Вот и все. Мы получили требуемую функциональность. Количество экземпляров книги увеличивается и уменьшается, а когда их становится 0, кнопка "Выдать" дизаблится (благодаря упомянутому CanGiveItem).
В приведенном приложении все данные и реализация логики вынесены в отдельное место. В фоновом коде формы мы не добавили ни строчки. XAML понятен. Благодаря паттерну MVVM в коде легко разобраться и его легко сопровождать. Стоит добавить, что рассмотренный MVVM Toolkit содержит весьма полезный набор классов, особенно хорошо подходящий для небольших задач, не требующих модульности или необходимости использовать IoC. Для сложных приложений, можно рекомендовать более "тяжелые" Toolkit’ы, один из них мы будем рассматривать в последующих лекциях.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.