- PVSM.RU - https://www.pvsm.ru -
Представим типичный пользовательский интерфейс. Есть несколько элементов управления, которые запускают некоторые повторяемые (за время жизни приложения) действия разной сложности. Чтобы сложные действия, такие как обращение к различным носителям, обращение к сети или сложное вычисление, не снижали отзывчивость интерфейса, они должны быть асинхронными. Дополнительно могут быть элементы управления, отменяющие асинхронно запущенное действие. Действие имеет свойство состояния (неактивно, запущено, завершено успешно, завершено с ошибкой, отменено), которое тем или иным образом отображается пользователю. Принятый в WPF, Silverlight и WinPhone шаблон проектирования MVVM [1] диктует, чтобы такое «действие» было частью модели представления, давая возможность вызывать сервисы модели из пользовательского интерфейса без создания между ними жёсткой связи. К сожалению, такое «действие» в базовой библиотеке классов не реализовано. Ближайшие имеющиеся в библиотеке сущности, такие как задачи System.Threading.Tasks.Task, команды System.Windows.Input.ICommand и делегаты System.Delegate, не подходят: задачи всегда одноразовые и не могут представлять повторяемое действие, делегаты и команды не поддерживают отмену и не содержат свойств состояния, а команды вообще не могут быть асинхронными. Далее я предлагаю решение в виде небольшой библиотеки классов, дающей возможность легко использовать описанные «действия» в ваших приложениях.
Для начала, суммируем наши требования.
Требования пользователя:
Требования разработчика приложений:
Обычные требования шаблона проектирования MVVM:
Ни одно из найденных в Интернете решений не удовлетворяет всем перечисленным требованиям. Посмотреть можно Commands in MVVM [2], RelayCommand Yes Another One! [3], Asynchronous WPF Command [4], WPF Commands and Async Commands [5] и самое близкое по требованиям Шаблоны для асинхронных MVVM-приложений: команды [6]. Интересно, что все найденные решения имеют одну нелогичность: они сосредоточены вокруг команд, добавляя к интерфейсу ICommand совершенно чуждую ему асинхронность. С моей точки зрения, гораздо логичнее оттолкнуться от задач (Task), сделав их повторяемыми и добавив к ним нужные команды, такие как «запуск», «отмена» или «пауза».
В моей библиотеке реализованы следующие сущности. Класс RepeatableTask [7] представляет собой повторяемую задачу, которая является самодостаточной и никак не связанной ни с шаблоном MVVM, ни вообще с пользовательским интерфейсом. В конструкторе RepeatableTask указывается синхронный или асинхронный метод, который будет выполняться при запуске действия. Назначение методов Start() и Cancel() — очевидное. События TaskStarting, TaskStarted и OnTaskStarted позволяют устанавливать обработчики событий жизненного цикла действия. В наследованном от RepeatableTask классе CommandedRepeatableTask [8] добавлены команды запуска и отмены, которые можно использовать для привязки к пользовательскому интерфейсу. Команды ChainedRelayCommand [9] созданы на основе широко распространённой RelayCommand [10] и дополнены поддержкой объединения в цепь чтобы формировать группы взаимосвязанных (с точки зрения интерфейса пользователя) действий. Класс CommandChain [11] содержит список объединённых в цепь команд и параметры их совместного исполнения.
Использовать описанные сущности легко, особенно для тех, кто работает по шаблону MVVM и уже знаком с RelayCommand. Для создания простых действий, которые нет необходимости выполнять асинхронно (например, сортировка списка по клику на его заголовке), используйте ChainedRelayCommand также, как как RelayCommand. Создаёте команду, указывая метод модели, который производит сортировку. Команду выставляете в виде свойства модели представления. В представлении для нужного элемента управления для свойства Command указываете привязку к созданному свойству модели представления. Для упомянутого случая сортировки по клику на заголовке списка, имеет смысл для каждого столбца указать одну и ту же команду, но кроме Command указать свойство CommandParameter со значением по которому делать сортировку. Для действия, которое требует асинхронного исполнения, создавайте повторяемую задачу CommandedRepeatableTask, указывая метод модели. Задачу выставляете в виде свойства модели представления, а в представлении привязываете свойства задачи StartCommand и StopCommand к соответствующим элементам управления. Если действия образуют взаимоисключающую группу, то после создания первого действия, второе и последующие действия создавайте не через конструктор, а через вызов метода CreateLinked первого действия. Действия в группе (цепи) используйте также, как одиночные. При этом доступность команды пуска (StartCommand) каждого действия будет зависеть от состояния других действий группы. А команда отмены (StopCommand) любого из них будет вести себя одинаково (отменять любое из запущенных действий группы).
Подведём итог. Созданный класс CommandedRepeatableTask удовлетворяет всем перечисленным требованиям для «действий» и дополнительно предоставляет следующие удобства:
Библиотека создана в виде Portable Class Library Profile259 (.NET Framework 4.5, Windows 8, Windows Phone 8.1, Windows Phone Silverlight 8). Для сборки под .NET Framework 4 добавлен отдельный проект, который состоит только из ссылок на исходники основного. В проекте-примере показаны различные способы использования библиотеки в WPF-приложении: в кнопках панели инструментов, в элементах главного меню, в элементах контекстного меню записей списка и в заголовках списка (для его сортировки). Решение со всеми перечисленными проектами выложено в публичный репозиторий на github [12]. Готовая для использования сборка выложена в виде nuget-пакета [13]. Удачного программирования!
Автор: novar
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/74635
Ссылки в тексте:
[1] MVVM: http://ru.wikipedia.org/wiki/Model-View-ViewModel
[2] Commands in MVVM: http://habrahabr.ru/post/196960/
[3] RelayCommand Yes Another One!: http://brettedotnet.wordpress.com/2011/11/28/relaycommand-yes-another-one/
[4] Asynchronous WPF Command: http://www.codeproject.com/Articles/320762/Asynchronous-WPF-Command
[5] WPF Commands and Async Commands: http://cisart.wordpress.com/2013/10/10/wpf-commands-and-async-commands/
[6] Шаблоны для асинхронных MVVM-приложений: команды: http://msdn.microsoft.com/ru-ru/magazine/dn630647.aspx
[7] RepeatableTask: https://github.com/novar0/RepeatableTask/blob/master/RepeatableTask/Tasks/RepeatableTask.cs
[8] CommandedRepeatableTask: https://github.com/novar0/RepeatableTask/blob/master/RepeatableTask/UI/CommandedRepeatableTask.cs
[9] ChainedRelayCommand: https://github.com/novar0/RepeatableTask/blob/master/RepeatableTask/UI/ChainedRelayCommand.cs
[10] RelayCommand: http://msdn.microsoft.com/ru-ru/magazine/dd419663.aspx#id0090030
[11] CommandChain: https://github.com/novar0/RepeatableTask/blob/master/RepeatableTask/UI/CommandChain.cs
[12] публичный репозиторий на github: https://github.com/novar0/RepeatableTask
[13] nuget-пакета: https://www.nuget.org/packages/RepeatableTask
[14] Источник: http://habrahabr.ru/post/242073/
Нажмите здесь для печати.