Как можно управлять доступностью команд в приложении wpf
В отличие от этого, управляющие команды WPF представляют собой подобные событиям сущности, которые независимы от конкретного элемента управления и во многих отношениях могут успешно применяться к многочисленным (и внешне несвязанным) типам элементов управления. Приведем несколько примеров: WPF поддерживает команды Сору (Копировать), Paste (Вставить) и Cut (Вырезать), которые могут применяться к широкому разнообразию элементов пользовательского интерфейса (пунктам меню, кнопкам панели инструментов, специальным кнопкам), а также клавиатурные комбинации (<Ctrl+C>, <Ctrl+V> и т.д.).
Хотя другие наборы инструментов для построения пользовательских интерфейсов (вроде Windows Forms) предлагают для этих целей стандартные события, в результате получается избыточный и трудный в сопровождении код. В модели WPF команды могут использоваться в качестве альтернативы. В результате получается более компактный и гибкий код.
В хорошо спроектированном Windows-приложении прикладная логика находится не в обработчиках событий, а закодирована в высокоуровневых методах. Каждый из этих методов представляет одну решаемую приложением "задачу". Каждая задача может полагаться на дополнительные библиотеки (вроде отдельно компилируемых компонентов, в которых инкапсулируется бизнес-логика или доступ к базам данных). Пример таких отношений показан на рисунке:
Наиболее очевидным способом использования такого проектного решения является добавление обработчиков событий везде, где они нужны, и применение каждого из них для вызова соответствующего метода приложения. По сути, в таком случае код окна превращается в облегченную коммутационную панель, которая реагирует на ввод и пересылает запросы внутрь приложения.
Хотя это решение вполне разумно, онo не позволяет сэкономить на кодировании. Многие задачи приложения могут инициироваться по различным маршрутам, из-за чего часто все равно приходится писать несколько обработчиков событий, вызывающих один и тот же метод приложения. Именно в этом нет особой проблемы (потому что код коммутационной панели прост), но жизнь гораздо усложняется, когда приходится иметь дело с состоянием пользовательского интерфейса.
Понять, о чем идет речь, поможет простой пример. Предположим, что есть программа, в состав которой входит метод по имени PrintDocument(). Этот метод может инициироваться четырьмя способами: через главное меню (выбором пункта меню File --> Print (Файл --> Печать)), через контекстное меню (щелчком правой кнопкой мыши в пустой области и выбором в контекстном меню пункта Print (Печать)), с помощью клавиатурной комбинации (<Ctrl+P>) и посредством соответствующей кнопки панели инструментов.
В определенных точках жизненного цикла приложения задача PrintDocument() должна быть временно недоступной. Это подразумевает отключение соответствующих команд в двух меню и кнопки в панели инструментов так, чтобы на них нельзя было выполнять щелчок, а также игнорирование клавиатурной комбинации <Ctrl+P>. Написание в данном случае всего необходимого кода — очень непростая проблема. Даже еще хуже то, что ошибки в нем могут привести к тому, что различные блоки кода состояния будут перекрываться некорректно, оставляя элемент управления в активном состоянии даже тогда, когда он не должен быть доступен. Написание и отладка подобного кода является одним из наименее приятных аспектов разработки Windows-приложений.
К удивлению многих опытных разработчиков Windows-приложений, в наборе инструментальных средств Windows Forms не было функциональности, которая могла бы облегчить решение подобных задач. Разработчики могли создавать необходимую инфраструктуру самостоятельно, но большинство из них предпочитало этого не делать. К счастью, WPF заполняет этот пробел, предлагая новую модель команд, в которой предоставляются два ключевых средства:
делегирование событий надлежащим командам;
поддержание включенного состояния элемента управления в синхронизированном виде с помощью состояния соответствующей команды.
Предлагаемая в WPF модель команд является не настолько прямолинейной, как можно было ожидать. Для подключения к модели маршрутизируемых событий ей требуется несколько отдельных компонентов, о которых речь пойдет позже. Однако в концептуальном плане она достаточно проста. На рисунке ниже показано, как построение приложения на основе команд позволяет изменить проектное решение, показанное на рисунке выше:
Теперь каждое действие, которое инициирует печать (т.е. щелчок на соответствующей кнопке либо элементе меню и нажатие клавиатурной комбинации <Ctrl+P>), отображается на одну и ту же команду. Эта команда с помощью привязки соединяется в коде со всего лишь одним обработчиком событий. Предлагаемая в WPF система команд представляет собой замечательное средство для упрощения проектирования приложений. Однако в ней все равно имеются кое-какие серьезные пробелы. В частности, WPF не поддерживает:
отслеживание команд (например, хронологию выполнявшихся команд);
команды, которые обладают состоянием и могут находиться в различных режимах (такие как команда, которая может быть включена и отключена).
Для реализации функциональности приложения в части обработки информации о сотрудниках предприятия необходимо для страницы PageEmployee установить механизм запуска задач ( Отменить, Создать, Редактировать, Сохранить, Найти и Удалить ) при выборе соответствующих пунктов меню и нажатии на кнопки панели инструментов. Технология WPF предлагает модель команд для выполнения такой привязки.
Модель команд обеспечивает делегирование событий определенным командам и управление доступностью элементов управления в зависимости от состояния соответствующей команды. В WPF команда представляет собой задачу приложения и механизм слежения за тем, когда она может быть выполнена. В то же время сама команда не содержит конкретного кода выполнения задачи. Одна и та же команда может быть привязана к одному или нескольким интерфейсным элементам приложения. Инициируют команду источники, которые могут быть различными элементами управления, например пункты меню MenuItem или кнопки – Button. Целевым объектом команды является элемент, для которого предназначена эта команда .
Классы, реализующие команды должны поддерживать интерфейс ICommand. В этом интерфейсе определены два метода Execute, CanExecute и событие CanExecuteChanged.
В WPF имеется библиотека базовых команд. Команды доступны через статические свойства следующих статических классов:
- ApplicationCommands ;
- NavigationCommands ;
- EditingCommands ;
- MediaCommands.
Для создания пользовательских команд целесообразно использовать классы RoutedCommand, который имеет реализацию интерфейса ICommand.
В разрабатываемом приложении для функций Отменить, Создать, Сохранить и Найти будем использовать команды и библиотеки WPF – статический класс ApplicationCommands, а для функций Редактировать и Удалить спроектируем пользовательские команды.
Для разработки пользовательских команд добавим в проект папку Commands и в ней создадим класс DataCommands.
В классе DataCommands объявлены два свойства Delete и Edit типа RoutedCommand. Класс RoutedCommand определяет команду, реализующую ICommand. В конструкторе данного класса определяется объект inputs типа InputGestureCollection. Класс InputGestureCollection представляет упорядоченную коллекцию объектов InputGesture, которые позволяют с помощью класса KeyGesture задать комбинацию клавиш для вызова команды.
Для использования страницей PageEmployee пользовательских команд в XAML -документе необходимо добавить пространство имен , где расположен класс DataCommands. Данному пространству имен присвоим ссылку command.
Теперь для страницы PageEmployee сформируем коллекцию объектов CommandBinding, которая осуществляет привязку команд для данного элемента и объявляет связь между командой, ее событиями и обработчиками.
Для класса CommandBinding свойство Command определяет ссылку на соответствующую команду, а свойства Executed и CanExecute задают обработчики событий при выполнении команды.
На странице приложения используются следующие команды: Отменить, Создать, Редактировать, Поиск, Сохранить и Удалить. Команды могут быть доступны или недоступны пользователю при работе приложения. Это проверяет метод CanExecute при генерации события CanExecuteChanged, которое вызывается при изменении состояния команды. Доступность команд определяется состоянием, в котором находится приложение . В тоже время выполнение какой-либо команды переводит, как правило, приложение в какое-либо другое состояние. Для проектируемого приложения можно определить следующие состояния:
- первоначальная загрузка страницы (1);
- просмотр данных по всем сотрудникам (2);
- редактирование данных по отдельному сотруднику (3);
- создание новой записи по сотруднику в базе данных (4).
На рис. 4.19 приведена диаграмма состояний приложения, на которой кружками обозначены состояния, а дуги соответствуют переходам при выполнении определенной команды.
На основе диаграммы состояний построим таблицу доступности команд в различных состояниях (табл. 4.2).
Из табл. 4.2 видно, что в приложении режим доступности команд в состояниях 1 и 2 противоположно режиму доступности команд в состояниях 3 и 4. Фактически для приложения имеются два режима (один объединяет состояния 1 и 2, а второй – состояния 3 и 4), управлять которыми можно с помощью логической переменной .
В код программы класса PageEmployee введем логическое поле isDirty для управления доступностью команд.
В дальнейшем в обработчики добавим код для обеспечения требуемой функциональности.
В коде класса остается добавить обработчики (реализация метода CanExecute ), которые управляют доступностью команд. Так как при анализе табл. 4.2 было выявлено, что для приложения различимо только два состояния доступности команд, то и обработчиков тоже достаточно иметь в программе два.
Теперь необходимо модифицировать XAML -документ в части задания свойства Command при описании пунктов меню и панели инструментов для привязки команд.
При описании меню ( Menu ) XAML -документ модифицирован следующим образом.
Соответствующие изменения XAML -документа необходимо провести и для панели инструментов ToolBar.
При выполнении приложения различные состояния доступности пунктов меню приведены на рис. 4.20 и рис. 4.21.
Рис. 4.20. Состояние доступности пунктов меню после загрузки приложения
Рис. 4.21. Состояние доступности пунктов меню после редактирования данных
Следующим этапом разработки приложения является создание источника данных, который обеспечит взаимодействие приложения с базой данных.
Ключевые термины
Приложение WPF, пространство имен , атрибут , частичный класс , простое свойство, сложное свойство, фрейм , гиперссылка , меню , панель инструментов , команда , привязка команд.
MainWindow, xmlns, partial, InitializeComponent, NavigationWindow, Frame, FrameworkElement, Page, Hyperlink , NavigateUri, Menu , MenuItem, DataGrid , DataGridTextColomn, DataGridCheckBoxColomn, DataGridComboBoxColomn, DataGridHyperlinkColomn, DataGridTemplateColumn, ICommand, Execute , CanExecute, CanExecuteChanged, CommandBinding.
Краткие итоги
В данной теме были рассмотрены вопросы разработки страничного WPF-приложения, организации перехода по страницам, создания системы меню , панели команд, табличного представления информации для просмотра и редактирования данных, системы универсальных команд многократного использования.
Система команд представляет собой механизм ввода в Windows Presentation Foundation (WPF), обеспечивающий обработку входных данных на более семантическом уровне по сравнению с вводом устройств. Примеры команд включают операции Копировать, Вырезать и Вставить, доступные во многих приложениях.
В этом обзоре определяется понятие команд в WPF, классы, входящие в модель команд, и способы использования и создания команд в приложениях.
В этом разделе содержатся следующие подразделы.
Что такое команды
Команды применяются в различных целях. Первой задачей является отделение семантики и объекта, вызывающего команду, от логики, которая выполняет команду. Это позволяет нескольким и разнородным источникам вызывать одну логику команды, а также настраивать логику команды для различных целевых объектов. Например, операции редактирования Копировать, Вырезать и Вставить, которые встречаются во многих приложениях, могут вызываться с помощью разных действий пользователя, если они реализованы с помощью команд. Приложение может поддерживать вырезание выделенных объектов или текста путем нажатия определенной кнопки, выбора пункта меню или сочетания клавиш, например CTRL+X. С помощью команд можно привязать разные типы действий пользователя к одной логике.
Другой целью применения команд является указание того, доступно ли действие. Продолжая пример вырезания объекта или текста, действие имеет смысл, только если что-либо выбрано. Если пользователь попытается вырезать объект или текст, не выбрав его, ничего не произойдет. Чтобы уведомить пользователя об этом, во многих приложениях кнопки и пункты меню отключаются, чтобы пользователь понял, что это действие выполнить невозможно. Команда может указывать, возможно ли действие, путем реализации метода CanExecute. Кнопка может подписаться на событие CanExecuteChanged и отключаться, если CanExecute возвращает false , или включаться, если CanExecute возвращает true .
Семантика команды может быть согласованной в разных приложениях и классах, однако логика действия зависит от конкретного объекта, к которому она применяется. Сочетание клавиш CTRL+X вызывает команду Вырезать в классах текста, классах изображений и веб-браузерах, однако фактическая логика для выполнения операции Вырезать определяется приложением, которое ее выполняет. RoutedCommand позволяет клиентам реализовать логику. Текстовый объект может вырезать выделенный текст в буфер обмена, а объект изображения может вырезать выбранное изображение. Когда приложение обрабатывает событие Executed, оно имеет доступ к целевому объекту команды и может выполнить соответствующее действие в зависимости от типа целевого объекта.
Пример простой команды в WPF
Самый простой способ использования команды в WPF — использовать предопределенную команду RoutedCommand из одного из классов библиотеки команд, элемент управления с собственной поддержкой обработки команд или элемент управления с собственной поддержкой вызова команд. Команда Paste является одной из предопределенных команд в классе ApplicationCommands. Элемент управления TextBox содержит встроенную логику для обработки команды Paste. Класс MenuItem включает собственную поддержку для вызова команд.
В следующем примере показано, как настроить MenuItem так, что при его выборе вызывается команда Paste для TextBox, исходя из фокуса клавиатуры на TextBox.
Четыре основных понятия в системе команд WPF
Модель перенаправляемых команд WPF можно разбить на четыре основных понятия: команда, источник команды, цель команды и привязка команды.
Команда — это выполняемое действие.
Источник команды — это объект, который вызывает команду.
Цель команды — это объект, для которого выполняется команда.
Привязка команды — это объект, сопоставляющий логику команды с командой.
В предыдущем примере команда Paste является командой, MenuItem — источником команды, TextBox — целевым объектом команды, а привязка команды предоставляется элементом управления TextBox. Следует отметить, что привязка CommandBinding не всегда предоставляется элементом управления, который является целевым классом команды. Довольно часто CommandBinding должен создаваться разработчиком приложения, кроме того, CommandBinding может быть присоединен к предку целевого объекта команды.
Команды
Команды в WPF создаются путем реализации интерфейса ICommand. ICommand предоставляет два метода, Execute и CanExecute, и событие CanExecuteChanged. Execute выполняет действия, связанные с командой. CanExecute определяет, может ли команда выполняться для текущего целевого объекта команды. Событие CanExecuteChanged вызывается, если диспетчер команд, управляющий операциями системы команд, обнаруживает изменения в источнике команды, которые могут сделать недействительной команду, которая вызвана, но еще не выполнена привязкой команды. Реализация WPF класса ICommand — класс RoutedCommand, который рассматривается в этом обзоре.
Основными источниками входных данных в WPF являются мышь, клавиатура, рукописный ввод и перенаправленные команды. Более аппаратно-ориентированные входные данные используют событие RoutedEvent для уведомления объектов на странице приложения о том, что произошло событие ввода. Объект RoutedCommand ничем не отличается. Методы Execute и CanExecute класса RoutedCommand не содержат логику приложения для команды, но вызывают перенаправленные события, которые проходят и поднимаются по дереву элементов, пока не обнаружат объект с CommandBinding. CommandBinding содержит обработчики для этих событий, а также обработчики для выполнения команды. Дополнительные сведения о маршрутизации событий в WPF см. в разделе Общие сведения о перенаправленных событиях.
Метод Execute для RoutedCommand вызывает события PreviewExecuted и Executed для целевого объекта команды. Метод CanExecute для RoutedCommand вызывает события CanExecute и PreviewCanExecute для целевого объекта команды. Эти события проходят и поднимаются по дереву элементов, пока не будет обнаружен объект, имеющий привязку CommandBinding для этой конкретной команды.
WPF предоставляет набор общих перенаправленных команд, которые охватывают несколько классов: MediaCommands, ApplicationCommands, NavigationCommands, ComponentCommands и EditingCommands. Эти классы состоят только из объектов RoutedCommand и не реализуют логику команды. За реализацию логики команды отвечает объект, для которого выполняется команда.
Источники команд
Источник команды — это объект, который вызывает команду. Примерами источников команды являются MenuItem, Button и KeyGesture.
Источники команд в WPF обычно реализуют интерфейс ICommandSource.
Command — это команда, которая будет выполняться при вызове источника команды.
CommandTarget — это объект, для которого выполняется команда. Следует отметить, что в WPF свойство CommandTarget для ICommandSource применимо, только когда ICommand — RoutedCommand. Если для ICommandSource задано значение CommandTarget, и соответствующая команда — не RoutedCommand, целевой объект команды не учитывается. Если CommandTarget не задан, в качестве целевого объекта будет использоваться элемент с фокусом клавиатуры.
CommandParameter — это определяемый пользователем тип данных, который используется для передачи данных обработчикам, реализующим команду.
Классы WPF, реализующие ICommandSource: ButtonBase, MenuItem, Hyperlink и InputBinding. ButtonBase, MenuItem и Hyperlink вызывают команду при щелчке, а InputBinding вызывает команду при выполнении связанного с ней InputGesture.
В следующем примере показано, как использовать MenuItem в ContextMenu в качестве источника команды для команды Properties.
Как правило, источник команды будет осуществлять прослушивание события CanExecuteChanged. Это событие сообщает источнику команды о том, что возможность выполнения команды для текущего целевого объекта команды может быть изменена. Источник команды может запрашивать текущее состояние RoutedCommand с помощью метода CanExecute. Источник команды может затем отключаться, если не удается выполнить команду. Примером этого является переход MenuItem в неактивное состояние (серый цвет) в случае невозможности выполнить команду.
InputGesture можно использовать в качестве источника команды. Два типа входных жестов в WPF: KeyGesture и MouseGesture. KeyGesture можно представить как сочетание клавиш, например CTRL + C. KeyGesture состоит из Key и набора ModifierKeys. MouseGesture состоит из MouseAction и необязательного набора ModifierKeys.
Чтобы объект InputGesture мог служить источником команды, он должен быть связан с командой. Это можно настроить несколькими способами. Один из способов — использование InputBinding.
В следующем примере показано, как создать привязку KeyBinding между KeyGesture и RoutedCommand.
В следующем примере демонстрируется, как добавить KeyGesture в InputGestureCollection для RoutedCommand.
CommandBinding
CommandBinding привязывает команду к обработчикам событий, которые реализуют команду.
Command — это команда, с которой связан объект CommandBinding. Обработчики событий, присоединенные к событиям PreviewExecuted и Executed, реализуют логику команды. Обработчики событий, присоединенные к событиям PreviewCanExecute и CanExecute, определяют, может ли эта команда выполняться для текущего целевого объекта команды.
В следующем примере демонстрируется создание объекта CommandBinding в корневом объекте Window приложения. CommandBinding связывает команду Open с обработчиками Executed и CanExecute.
Затем создаются объекты ExecutedRoutedEventHandler и CanExecuteRoutedEventHandler. ExecutedRoutedEventHandler открывает MessageBox, где отображается строка, сообщающая о выполнении команды. CanExecuteRoutedEventHandler задает для свойства CanExecute значение true .
CommandBinding присоединяется к конкретному объекту, например к корневому объекту Window приложения или элемента управления. Объект, к которому присоединяется CommandBinding, определяет область привязки. Например, CommandBinding, присоединенный к предку целевого объекта команды, достижим для события Executed, а CommandBinding, присоединенный к потомку целевого объекта команды, недостижим. Это напрямую связано со способом перехода события RoutedEvent из объекта, который вызывает событие.
В некоторых случаях CommandBinding присоединяется к целевому объекту команды напрямую, например с помощью класса TextBox и команд Cut, Copy и Paste. Довольно часто, однако, более удобным будет подключение CommandBinding к предку целевого объекта команды, такому как главное окно Window или объект приложения, особенно в том случае, если одну привязку CommandBinding можно использовать для нескольких целей команды. Это необходимо учитывать при создании инфраструктуры системы команд.
Цель команды
Целью команды является элемент, для которого выполняется команда. По отношению к RoutedCommand целевой объект команды — это элемент, с которого начинается перенаправление Executed и CanExecute. Как было отмечено ранее, в WPF свойство CommandTarget для ICommandSource применимо, только когда ICommand — RoutedCommand. Если для ICommandSource задано значение CommandTarget, и соответствующая команда — не RoutedCommand, целевой объект команды не учитывается.
Источник команды может явно задавать целевой объект команды. Если цель команды не определена, в качестве целевого объекта будет использоваться элемент с фокусом клавиатуры. Одно из преимуществ использования элемента с фокусом клавиатуры в качестве цели команды является то, что это позволяет разработчику приложения использовать один источник команды для вызова команд для нескольких целей без необходимости отслеживания целевого объекта команды. Например, если MenuItem вызывает команду Вставить в приложении, которое имеет элемент управления TextBox и элемент управления PasswordBox, целевым объектом может быть TextBox или PasswordBox в зависимости от того, какой элемент управления имеет фокус клавиатуры.
В следующем примере показано явное задание цели команды в разметке и в коде программной части.
Диспетчер команд CommandManager
CommandManager выполняет ряд функций, связанных с командами. Он предоставляет набор статических методов для добавления обработчиков событий PreviewExecuted, Executed, PreviewCanExecute и CanExecute в конкретный элемент и их удаления из него. Он предоставляет средства для регистрации объектов CommandBinding и InputBinding для определенного класса. CommandManager также предоставляет средства (с помощью события RequerySuggested) для уведомления команды о необходимости вызова события CanExecuteChanged.
Метод InvalidateRequerySuggested вынуждает CommandManager вызвать событие RequerySuggested. Это удобно для ситуаций, когда требуется отключить или включить команду, но отсутствуют условия, о которых известно CommandManager.
Библиотека команд
WPF предоставляет набор стандартных команд. Библиотека команд включает следующие классы: ApplicationCommands, NavigationCommands, MediaCommands, EditingCommands и ComponentCommands. Эти классы предоставляют команды, такие как Cut, BrowseBack и BrowseForward, Play, Stop и Pause.
Многие из этих команд содержат набор привязок ввода по умолчанию. Например, если вы укажете, что приложение обрабатывает команду копирования, вы автоматически получаете привязку клавиатуры "CTRL + C", а также получаете привязки для других устройств ввода, таких как жесты пера планшетного ПК и речевые сведения.
При ссылке на команды в различных библиотеках команд с помощью XAML, обычно можно опустить имя класса библиотеки, который предоставляет статическое свойство команды. Как правило, имена команд задаются однозначно в виде строк и существуют типы владельца, которые обеспечивает логическую группировку команд, однако они не являются необходимыми для устранения неоднозначности. Например, можно указать Command="Cut" вместо более подробной команды Command="ApplicationCommands.Cut" . Это удобный механизм, встроенный в WPF XAML процессор для команд (точнее, это поведение преобразователя типов ICommand , которое WPF XAML указывает процессор во время загрузки).
Создание настраиваемых команд
Если команды в классах библиотеки команд не соответствуют вашим потребностям, вы можете создать собственные команды. Настраиваемые команды можно создать двумя способами. Первый способ подразумевает создание с нуля и реализацию интерфейса ICommand. Другой способ, а также наиболее распространенный подход, — создание RoutedCommand или RoutedUICommand.
Пример создания настраиваемой команды RoutedCommand см. в разделе Create a Custom RoutedCommand Sample (Создание примера настраиваемой команды RoutedCommand).
На примере приложения, использующего паттерн MVVM (Model View View-Model) рассмотрим работу с командами (Commands).
Примеры используемые мною имеют одинаковую логику независимо от платформы: WPF, SilverLight, Windows Phone. В первую очередь рассмотрим несколько вариантов использования команд в любом MVVM приложении. Сначала примеры, потом разбор полетов.
Весь использованный код представляет собой часть моей MVVM библиотеки Apex, однако все части будут предоставлены в полном объеме, что позволит Вам легко интегрировать данные методы в свой проект или библиотеку, или можно просто подключить ссылку на Apex и сразу приступать к работе.
Скриншот 1: Команды в WPF
Скриншот 2: Команды в SilverLight
Скриншот3: Команды в Windows Phone
Что такое команды?
Команды являются привязанными объектами, что позволяет разделить логику и пользовательский интерфейс друг от друга.
- Команды представляют собой объекты, реализующие интерфейс ICommand
- Обычно команды связанны с какой либо функцией
- Элементы пользовательского интерфейса привязываются к командам — кода интерфейс активируется пользователем, то выполняется команда — вызывается соответствующая функция
- Команды знают, включены ли они или нет
- Функции могут отключать команды – автоматическое отключение всех пользовательских элементов ассоциированных с ней
- На сомом деле существует множество различных применений команд. Например использование команд для создания асинхронных функций, обеспечивающих логику, которая может быть проверена с/без помощи использования пользовательского интерфейса и др.
Примеры
Для начала рассмотрим случаи, когда возникает необходимость использования команд. Каждый случай будет рассмотрен более подробно далее в соответствующем пункте.
Рассмотри базовую модель вида (View Model):
Данная модель вида наследует от ViewModel', которая в свою очередь реализует интерфейс INotifyPropertyChanged, однако Вы можете использовать любой другой доступный тип реализации модели вида.
Цель: Вызвать соответствующую функцию модели вида при нажатии пользователем на элемент.
Сейчас рассмотрим самый простой пример использования команд. В первую очередь добавим объект Command к нашей модели вида – ассоциированную функцию, которая вызывается при вызове команды:
Теперь добавим команду в свойство «Command'» элемента управления кнопка(button).
Вот и все. Самый простой пример сделан. Мы прикрепили объект команды к свойству Command элемента интерфейса. При активации элемента, то есть при нажатии на кнопку вызывается команда( вызывается соответствующая ей функция DoSimpleCommand).
Цель: Вызвать функцию модели вида во время активации или нажатия на элемент интерфейса.
Однако это очень простая функция, которую я предпочту не прописывать в явном виде и взамен использую лямда выражение.
Во всех примерах данного мануала Вы можете как и явно создавать функцию, например, как в Пример 1, или использовать лямды, что несколько сократит код и сделает его более элегантным. Выбор за Вами.
Опять повторим процесс привязки команды к свойству Command нашей кнопки:
Пример 3 - Простое использование Command с параметрамиЦель: Создание команды, которая использует параметр, передаваемый ей при привязке данных.
В этом примере используем объект Command (так же есть возможность использовать объект AsynchronousCommand, что мы увидим чуть позднее). Переданный параметр может быть использован в вызываемой функции.
Повторим привязку команды к кнопке или любому другому элементу. Так же не забудем добавить параметр:
Везде, где используются команды возможно передавать в них параметры. При создании команды можно использовать Action (функция команды без параметров) или Action<object> (функция команды с параметром типа object ). Так же существует возможность использовать лямды:
Цель: Добавить возможность включать/отключать команды из кода или XAML
Каждая команда содержит свойство CanExecute, которое при установке в него true включает команду, и при установке false — отключает команду, а так же связанный с ней элемент управления. В следующем примере при отключении команды исчезнет и кнопка с пользовательского интерфейса:
Делаем привязку к кнопке (или другому элементу):
Так же остается возможность привязать свойство команды CanExecute.
В данном примере, для управления состоянием команды в коде назначаем свойству CanExecute соответствующее значение. А для управления через интерфейс (XAML) используем дополнительный элемент CheckBox:
Вне зависимости от того где и как мы создали объект команды, у нас всегда остается возможность передать как второй параметр булево значение, управляющее свойством CanExecute. По умолчанию – установлено в false. В примере передаем true для включения.
Цель: Узнать, когда выполнилась команда, или момент ее выполнения.
У каждой команду существует два события: Executed — вызывается, когда команда отработала, и Executing — в момент выполнения. Так же событие Executing позволяет отменить команду.
Важно: Бывает множество ситуаций, когда вышеописанные события становятся очень полезными. Например, Вы захотите показать всплывающее окно, которое будет спрашивать, хотите ли Вы продолжить в момент выполнения команды. И где разместить такой код? В коде команды? Плохая идея, так как придется создать код интерфейса в модели данных, что в первую очередь засоряет код, а во вторую, что намного важнее — не позволит его протестировать. Наилучшим будет разместить код в Виде (View). И с помощью событий Вы можете это сделать.
Вторым примером демонстрации полезности таких событий может быть ситуация, когда появляется необходимость переместить фокус на другой элемент после выполнения команды. Вы не сможете это сделать в модели вида, так как она не имеет доступ к контролам (элементам управления), однако подписавшись на события Вы сможете реализовать это без каких либо проблем.
Теперь привяжем к кнопке:
На данный момент нет никаких отличий от кода в предыдущих примерах. Подпишемся на события во View.
Важно: в моем представлении, DataContext с именем viewModel:
Наконец то мы подобрались к пониманию всей мощи реализации команд. Есть возможность подписываться на события Executed и Executing в представлении (или даже в другом ViewModel, или объекте), а так же знаем когда оно возникает. Событие Executing передает объект CancelCommandEventArgs — это и есть свойство с именем «Cancel». Если его установить в true — то команда не выполнится. Оба объекта CommandEventArgs и CancelCommandEventArgs поддерживает еще одно свойство – параметр. Это тот параметр, который может быть передан в Command(если он вообще есть).
Цель: Если команды выполняются достаточно долго, то блокируется интерфейс. Должна быть возможность запускать их асинхронно.
Можно создать нечто вроде фонового процесса и там запустить команду. Однако сразу возникает несколько вопросов:
— Что будет, если мы захотим обновить ViewModel в функции, находящейся в потоке? Мы не можем это сделать, не прибегая к действиям в интерфейсе пользователя.
— Как убедиться, что одна команда не вызвала случайно несколько потоков при возникновении только одного воздействия на интерфейс?
— Как не замусорить View Model, если будет множество команд, которые должны будут выполняться в отдельных потоках?
— Как обеспечить совместимость с WP и Silverlight, если имеются различные опции для потоков на каждой системе?
Объект AsynchronousCommand был создан с учетом всех этих нюансов и не только. Знакомимся:
Привязка к элементу XAML:
После вызова команды вызывается соответствующая функция, которую мы указали с помощью лямда выражения, которая в свою очередь вызывается в отдельном потоке из пула потоков.
Если же возникает необходимость что-то сделать с объектами View Model (которые могут быть связаны с элементами пользовательского интерфейса), мы можем воспользоваться функцией ReportProgress:
ReportProgress гарантирует, что переданный код будет запущен в нужном потоке, что позволяет довольно просто изменять интерфейс пользователя при необходимости.
Цель: Команда выполняется дольно долго. Показать прогрессбар.
В AsynchronousCommand есть свойство с именем «IsExecuting». Если оно установлено в true — значит команда в процессе выполнения. Так как AsynchronousCommand реализует интерфейс INotifyPropertyChanged, который подразумевает, что у нас есть возможность привязаться к этому свойству и показывать процесс выполнения.
Команду свяжем с кнопкой. А свойство IsExecuting привяжем к другому элементу интерфейса, например, StackPanel, в котором будут находится соответственно TextBlock и ProgressBar:
В этом примере, как только команда начинает выполняться кнопка исчезает, и появляются надпись и прогрессбар. Учтите, что мы осуществляем привязку к свойству IsExecuting команды.
На заметку: Параметр Invert может быть передан в BooleanToVisilityConverter, так как он представляет собой расширенную версию стандартного BooleanToVisibilityConverter, который определен в Apex.Converters. Инвертирует результат. Очень полезная штучка в определенных моментах.
Цель: Позволить пользователю отменять процесс выполнения асинхронной команды.
Воспользуемся некоторыми возможностями AsynchronousCommand. Каждый объект AsynchronousCommand так же содержит и команду с именем CancelCommand. И она может быть привязана к UI пользователя или вызвана внутри кода в нужном месте. При вызове этой команды, свойство IsCancellationRequested объекта AsynchronousCommand устанавливается в true (учтите, что свойство использует INotifyPropertyChanged и у Вас есть возможность привязаться к нему). Вы можете периодически вызывать функцию CancelIfRequested, и если вдруг она вернет true, то команда остановится.
Привяжем команду к кнопке, а свойство IsExecuting к StackPanel:
В этом примере показываем кнопку Cancel во время выполнения команды. Эта кнопка связана со свойством CancellableAsyncCommand.CancelCommand. Благодаря тому, что мы используем функцию CancelIfRequested, у нас есть красивая возможность остановить выполнение асинхронной команды.
На заметку: При остановке выполнения асинхронной команды событие Executed не вызывается. Однако вместо него вызывается событие Cancelled, которое может принимать те же самые параметры.
Цель: Вызвать команду при активации пользовательского элемента, у которого не установлено свойство Command, но заданно событие.
Связка команды с событием происходит следующим образом:
При использовании EventBindings допускается прикрепление любой команды к любому событию.
Рассмотрим класс Command, который использовался в примерах 1-5.
В первую очередь создаем два перегруженных конструктора, в который передается действие без параметров: Action, или с параметрами: Action<object>, где object — тип.
Далее задаем флаг canExecute, отвечающий за возможность выполнения команды. После изменения флага canExecute необходимо вызвать canExecuteChanged.
Далее реализуем интерфейс ICommand
Функцию DoExecute разберем чуть позже.
А теперь реализуем функцию Invoke для каждого события. Таким образом у нас появится возможность вызывать их из производных классов.
На заметку: InvokeAction вызывает либо действие без параметров, либо с параметрами, смотря какое из них установлено.
DoExecute достаточно проста. Просто вызывает соответствующее событие с возможностью отменить выполнение.
Выше описанный класс реализует интерфейс ICommand и предоставляет весь необходимый функционал, использующийся в примерах 1-5.
В примерах 6-8 используется класс AsynchronousCommand, который в свою очередь наследует класс Command, описанный выше.
Сначала объявляем касс и конструктор:
Благодаря реализации интерфейса INotifyPropertyChanged появляется возможность уведомления при изменении переменной IsExecuting. Так как оба конструктора вызывают метод Initialise, рассмотрим его более подробно:
Все, что тут выполняется, несмотря на обилие кода — просто установка флага IsCancellationRequested в значение true. Инициализация создает объект и позволяет получить к нему доступ. Так же имеется свойство IsCancellationRequested, которое информирует, когда оно меняет свое состояние.
Так же необходимо знать, когда команда в процессе выполнения. Добавим следующий код:
Продолжим. Так как у нас есть возможность отмены добавим события Cancelled и PropertyChanged (реализующее интерфейс INotifyPropertyChanged):
Мы не запускаем команду, если она уже выполняется, однако есть возможность ее отменить и установить флаг выполнения.
Мы должны сохранять диспатчер выполняемой команды, так как при выведении данных о процессе выполнения, ссылаемся на соответствующий диспатчер.
Нужно помнить, что процесс сохранения отличается на Silverlight и WPF.
А теперь про потоки. Используя пул потоков, отправляем функцию InvokeAction в очередь, которая вызовет функцию команды в отдельном потоке. Так же не следует забывать, что ReportProgress в зависимости от диспатчера, и именно тут нужно изменять свойства и вызывать Executed. При вызове диспатчера (после успешного завершения действия), необходимо очистить флаг IsExecuting, а так же вызывать одно из событий: Cancelled или Executed. И так, осталось рассмотреть только ReportProgress:
Как это работает – Класс привязки дынных CommandКод EventBindings несколько сложнее из-за различий в WPF и Silverlight. В WPF EventBindingsCollection это FreezableCollection, то есть наследники наследуют также и контекст данных. В Silverlight нет FreezableCollection, поэтому необходимо передавать контекст данных вручную.
Читайте также: