MVVM Light Toolkit – Команды и сообщения

Введение

В этой статье мы рассмотрим основные возможности фреймворка MVVM Light Toolkit, такие как использование команд и взаимодействие на основе сообщений.

В качестве примера мы разработаем приложение для Windows Phone 7, которое должно отображать список книг. Кроме списка книг, на интерфейсе должны быть расположены две кнопки – «Юмор» и «Драма», нажатие на которые должно отмечать «галкой» книги соответствующего жанра.

Приложение будет реализовано с использованием паттерна MVVM.

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

Пререквизиты

Для использования материала необходимо установить сборки и шаблоны MVVM Light V3 (stable). Их можно скачать по ссылкам на странице с описанием процесса установки.

Создаем каркас приложения

Создадим новый проект. В качестве шаблона проекта используем шаблон MVVM Light.

clip_image002

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

Составные части MVVM

Как вы уже знаете, аббревиатура MVVM расшифровывается как Model (Модель) – View (Представление) – ViewModel (Модель представления). Для реализации приложения, основанного на этом шаблоне проектирования нам необходимо реализовать каждый из этих компонентов как минимум один раз.

Модель

В нашем приложении мы будем работать с книгами и жанрами. Соответственно, нам потребуется создать типы Book и BookGenre.

namespace MvvmLightDemo.Model
{
	public class Book
	{
		public string Title { get; set; }

		public string Author { get; set; }

		public BookGenre Genre { get; set; }
	}
}

Приведенный выше код с определением сущностей мы помещаем в папку Model. Обратите внимание, что класс Book не реализует интерфейс INotifyPropertyChanged. По идее, класс Книга не должен знать ничего о специфике реализации среды, где он будет использоваться. Этот класс предоставляет информацию только о книге и не пытается казаться тем, чем он на самом деле не является.

Представление

Для реализации представления мы будем использовать файл MainPage.xaml, созданный из шаблона проекта. Для доведения его до соответствия требованиям, нам необходимо добавить два элемента Button и один ListBox. Давайте представим, что мы дизайнеры и откроем этот файл в Expression Blend.

clip_image004

Обратите внимание, что мы работаем только с разметкой и ничего не добавляем в code behind (мы – дизайнеры!).

Мы решаем, что в списке книг нам необходимо показывать чекбокс слева от строки с названием и автором книги. Причем название и автор у нас – одна строка.

Для того чтобы показывать чекбокс в списке, нам необходимо изменить шаблон отображения данных.

clip_image005

clip_image006

Разместив элементы управления по своему вкусу, мы можем снять с себя полномочия дизайнера и вернуться в Visual Studio 2010 к роли разработчика.

Модель представления

Прежде чем продвинуться дальше, давайте подведем промежуточный итог.

Что мы имеем?

Мы имеем структуру хранения информации о книгах, которая описана типом Book. Мы знаем, какие жанры книг нам доступны. Дизайнер спроектировал интерфейс нашего приложения.

Что нам осталось сделать?

Нам необходимо сделать так, чтобы список книг можно было отобразить на интерфейсе, что бы нажатие кнопок приводило к изменению отображения галок в чекбоксах. Иными словами, нам необходимо связать Модель и Представление. Эта роль в шаблоне MVVM отведена последнему компоненту – модели представления. Задача модели представления – преобразовать модель таким образом, чтобы представление могло отобразить необходимую информацию. Соответственно, если представление отображает книги в виде списка, то модель представления должна предоставить этот список. Так же модель представления отвечает за отработку взаимодействия с пользователем, который работает с представлением. Если пользователь отдает команду (нажимает кнопку, выбирает элемент в списке, и т.п.), то представление должно знать, к какому методу модели представления обратиться, что бы действие пользователя могло быть выполнено.

Таким образом, наша модель представления должна обеспечить для представления:

1. Список книг

2. Команду выбора книг предпочтительного жанра

Учитывая, что дизайнеры не ожидают от наших данных о книгах поле типа Boolean, которое бы отображало предпочтение пользователя, мы создадим еще одну модель представления для взаимодействия с неочевидным на первый взгляд представлением, которое «затаилось» внутри DataTemplate списка книг.

public class BookViewModel : ViewModelBase
{
	private readonly Book _book;
	private BookGenre _favorite;

	public BookViewModel(Book book)
	{
		_book = book;
	}

	public string Title
	{
		get { return string.Format("{0} by {1}", _book.Title, _book.Author); }
	}

	public bool IsFavorite
	{
		get { return _book.Genre == _favorite; }
	}

	public override void Cleanup()
	{
		base.Cleanup();
	}
}

Определившись с BookViewModel, мы можем продолжить реализацию модели представления основного пользовательского интерфейса приложения.

Добавим демо данные в виде массива книг и коллекцию для хранения BookViewModel. Ниже мы рассмотрим подробнее каждый из добавленных элементов.

public class MainViewModel : ViewModelBase
{
	//demo data source
	private Book[] DemoBooks = {
		new Book {Title = "Book 1", Author = "Pushkin A.S.", Genre = BookGenre.Drama},
		new Book {Title = "Book 2", Author = "Pushkin A.S.", Genre = BookGenre.Drama},
		new Book {Title = "Book 3", Author = "Pushkin A.S.", Genre = BookGenre.Drama},
		new Book {Title = "Book 4", Author = "Pushkin A.S.", Genre = BookGenre.Drama},
		new Book {Title = "Book 5", Author = "Chekhov A.P.", Genre = BookGenre.Humor},
		new Book {Title = "Book 6", Author = "Chekhov A.P.", Genre = BookGenre.Humor},
		new Book {Title = "Book 7", Author = "Chekhov A.P.", Genre = BookGenre.Humor},
		new Book {Title = "Book 8", Author = "Chekhov A.P.", Genre = BookGenre.Humor},
	};

	public ObservableCollection Books { get; set; }

	public MainViewModel()
	{
		Books = new ObservableCollection();
		DemoBooks.ToList().ForEach(book => Books.Add(new BookViewModel(book)));
	}
…
}

Обработка события нажатия на кнопку

В нашем представлении есть две кнопки, нажатие на которые необходимо обрабатывать.

Самый простой способ – повесить обработчик на событие Click и в нем вызывать методы модели представления. Этот подход порой хорошо работает на небольших проектах, в которых не используются MVC-подобные паттерны проектирования, или на проектах с коротким жизненным циклом.

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

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

Использование MVVM Light Toolkit для обработки события нажатия на кнопку

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

clip_image007

В нашем случае, если пользователь нажал на кнопку «Юмор» мы должны показать ему, какие книги из списка относятся к этому жанру – каждая книга выбранного жанра должна быть отмечена галкой.

Для реализации мы воспользуемся следующими компонентами из MVVM Light Toolkit:

· Класс RelayCommand<T>

· EventToCommand behavior

Добавим в определение класса MainViewModel свойство и инициализируем его в конструкторе

public RelayCommand SetFavoriteCommand { get; set; }

SetFavoriteCommand = new RelayCommand(genre =>MessageBox.Show(genre), _ => Books.Count > 0);

Конструктор RelayCommand принимает типизированный инстанс класса Action<T>, который и берет на себя работу по выполнению команды. Кроме того, RelayCommand предоставляет второй конструктор, во втором параметре которого можно передать условие доступности команды. В нашем случае мы используем именно этот конструктор. Критерием является необходимость наличия хотя бы одной книги в коллекции.

Для того что бы запускать команду из представления, заменим разметку, описывающую кнопки следующей разметку в XAML файле нашего представления:

<Button Content="Humor"
		Height="72"
		VerticalAlignment="Top"
		Margin="0,0,82,0">
	<i:Interaction.Triggers>
		<i:EventTrigger EventName="Click">
			<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding SetFavoriteCommand}"
														CommandParameterValue="humor" />
		</i:EventTrigger>
	</i:Interaction.Triggers>
</Button>
<Button Content="Drama"
		Height="72"
		VerticalAlignment="Top"
		Margin="0,0,0,0">
	<i:Interaction.Triggers>
		<i:EventTrigger EventName="Click">
			<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding SetFavoriteCommand}"
														CommandParameterValue="drama" />
		</i:EventTrigger>
	</i:Interaction.Triggers>
</Button>

В коде выше следует обратить внимание на триггер и использование EventToCommand. Мы указываем тип события, команду, которую необходимо вызвать при возникновении события, и указываем параметр для команды.

Данную разметку можно добавить и через Expression Blend. Достаточно выбрать EventToCommand из списка Assets и перетащить его на нужный элемент интерфейса:

clip_image009

Настраиваем триггер:

clip_image011

Связываем с командой из дата-контекста:

clip_image013

Можно собрать приложение, поставить брейкпоинт на коде команды в конструкторе и проверить работу связки EventToCommand и RelayCommand в действии.

clip_image014

Использование MVVM Light Toolkit организации общения между компонентами

Мы только что успешно реализовали реагирование на пользовательский ввод. Давайте рассмотрим, каким образом MVVM Light Toolkit может помочь нам в организации взаимодействия между компонентами.

В нашем упрощенном примере мы хотим устанавливать галку в чекбоксах в зависимости от того какую кнопку нажмет пользователь.

Как и в случае с обработкой нажатия кнопки, мы можем просто изменить поле у каждой подходящей книги (у BookViewModel) и закончить с этим, через механизмы биндинга и нотификации об изменениях в полях данных мы увидим наши галки на своих местах.

Здесь опять возникает ситуация когда MainViewModel должна знать больше чем нужно об устройстве BookViewModel и становится зависима от деталей реализации этого класса.

Другой момент, на который хотелось бы обратить внимание, заключается в том, что при развитии функционала приложения нам может потребоваться выполнять еще какое-то действие при нажатии на кнопку. Например, сохранять предпочтение пользователя в IsolatedStorage. А еще через какое-то время мы решим делать запись в логе, чтобы отслеживать, как меняются предпочтения пользователя.

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

Для того что бы избавиться от зависимостей, воспользуемся классом Messenger из MVVM Light Toolkit.

Идея достаточно простая и элегантная. Элементы системы, заинтересованные в обработке определенного события (сообщения) подписываются на него через класс Messenger. При подписке на сообщение, подписчик указывает тип сообщения и делегат, который необходимо вызвать. Элемент системы, который является источником сообщений, так же использует для отправки сообщений класс Messenger. Получив сообщение, класс Messenger ретранслирует его всем подписчикам.

clip_image015

Для реализации этого подхода в нашем приложении внесем ряд изменений. Команда SetFavoriteCommand должна отправлять выбранный жанр в Messenger, а в конструкторе BookViewModel должна производиться подписка модели представления на сообщение о выборе жанра.

MainViewModel.cs:

SetFavoriteCommand = new RelayCommand(
	genre => Messenger.Default.Send(new GenericMessage(
			genre == "humor"
				? BookGenre.Humor
				: BookGenre.Drama)),
	_ => Books.Count > 0);

BookViewModel.cs:

public BookViewModel(Book book)
{
	_book = book;
	Messenger.Default.Register< GenericMessage >(this, OnFavoriteGenreChanged);
}

private void OnFavoriteGenreChanged(GenericMessage msg)
{
	_favorite = msg.Content;
	RaisePropertyChanged("Title");
	RaisePropertyChanged("IsFavorite");
}

Если через какое то время потребуется добавить новое действие в ответ на нажатие кнопок, то вся модификация сведётся к добавлению нового обработчика и подписке его на сообщение через Messenger.

В реальном приложении не следует использовать GenericMessage в явном виде, а объявить наследника и использовать уже его.

clip_image016 clip_image017

Заключение

MVVM Light Toolkit на мой взгляд весьма полезный набор классов, особенно хорошо подходящий для небольших задач, не требующих модульности или необходимости использовать IoC. Даже для сложных приложений, в которых я бы использовал Prism/Unity/MEF можно найти применение и для MVVM Light Toolkit.

В данной статье я постарался продемонстрировать то, с какой легкостью можно создавать поддерживаемые и тестируемые MVVM приложения при помощи классов из MVVM Light Toolkit. Надеюсь, что у меня это получилось, и вы попробуете этот замечательный инструмент в своей работе!

Удачи!

Исходный код MvvmLightDemo.zip

Posted in Silverlight, WPF | Tagged , , , | 2 Comments

Bluetooth headset and Skype

Last month I bought nice and lightweight, buy yet really long-running on one full charge, Bluetooth stereo headset Plantronics Backbeat 903+. This was plain impulse buy, but things ruled out pretty nice about the quality of sound and wearing comfort (I really can wear it all day long), except one misfortune – the headset was not doing well with Skype. The other party I was talking too always asked me to repeat, they did not hear me well. Moreover, every time I was getting away from my laptop for more than 10 meters the connection would break and Windows would throw the following message:

Error: Bluetooth stack com server has stopped working

The above error was what I was seeing too often, often enough to give up on using this headset’s mic – I resorted to internal  microphone, and that seemed okay until I discovered that BtStackProcess.exe was eating up over 300+ megs of RAM and draining too much of processor time “doing its laundry”. I promptly went googling and discovered some topics on Skype developer forums.

Despite the fact that the topic was discussing v.4 issues, the solution offered by tropicana did help me to overcome mine. It turned out that you do not need to use BtStackProcess.exe and BluetoothHeadSetProxy.exe at all, the OS should take care of the plumbing.

Tools -> Options -> Advanced -> Manage other programs’ access to Skype –> Set “Not allowed to use Skype” to both Broadcom plugins.

image

 

image

Happy skyping!

Posted in Miscellaneous | 4 Comments

Новые технические материалы на русском языке

Disclaimer: Ссылки любезно предоставлены Microsoft. Основная часть этого контента подготовлена представителями ИТ-сообщества, использующими платформу Microsoft.

Windows Server

Виртуализация

Windows Deployment

Обучающие материалы

Web

Обучающие материалы

Exchange

Lync

Office

Материалы по разработке

Azure

Введение
Облачные вычисления

Технические возможности платформы Windows Azure

Архитектура приложений в облаке

Дополнительные материалы

Windows Client

Обучающие материалы

Элементы управления

Материалы по платформе

Шаблоны

IE

HTML5, CSS3 и DHTML

Posted in .NET | Tagged , , | Leave a comment