Главная » WPF » Команды

0

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

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

<MenuItem Header=’_File’>

<MenuItem Header=’E_xit’ Click=’ExitClicked’ />

</MenuItem>

В файле с кодом реализуем  обработчик  события:

void ExitClicked(object sender, RoutedEventArgs e) { Application.Current.Shutdown();

}

Пока все хорошо, но давайте еще добавим текст, в который входит гиперссыл

ка, позволящая выйти из программы:

<TextBlock>

Вас приветствует моя программа. Если вам надоело, можете

<Hyperlink Click=’ExitClicked’>выйти</Hyperlink>.

</TextBlock>

Вот теперь начинаются неприятности. Мы делаем слишком  много предполо жений о реализации метода ExitClicked, например, что его сигнатура совместима с событием Hyperlink.Click и что он не делает ничего другого, кроме завершения приложения. К тому же, в разметку  оказались  зашиты  произвольно выбранные имена  методов из файла  с кодом, а дизайнер,  который  конструирует пользова тельский интерфейс,  не будет знать, к каким обработчикам  событий привязаться.

Для  решения  этой проблемы  и придуманы  команды.  Они  позволяют  назна чить имя желаемому действию. Чтобы воспользоваться командой, нужно сделать три вещи: (1) определить  назначение  команды; (2) написать  реализацию коман ды и (3) создать для команды триггер

Основой   всех   команд   в  WPF  является  довольно   простой   интерфейс

ICommand:

public interface ICommand {

event EventHandler CanExecuteChanged;

bool CanExecute(object parameter);

void Execute(object parameter);

}

Метод CanExecute позволяет  выяснить,  находится  ли команда в таком состоя нии, когда ее можно выполнить.  Обычно  элементы  управления пользуются  этим методом, чтобы активировать или деактивировать себя. Иными словами, если ассо циированная с кнопкой команда возвращает  false из метода CanExecute, то кнопка деактивируется. Такое обобществление понятия «активен»  позволяет  нескольким элементам, связанным с одной командой, поддерживать согласованное состояние.

Метод Execute  основной,  его вызов означает  выполнение команды. Реализа ция класса Button (как и любого другого элемента управления, поддерживающе го команды)  должна включать примерно  такой код:

protected virtual void OnClick(RoutedEventArgs e) {

if (Command != null && Command.CanExecute(CommandParameter)) {

Command.Execute(CommandParameter);

}

// … продолжение реализации

}

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

public class Exit : ICommand {

public bool CanExecute(object parameter) {

return true;

}

public event EventHandler CanExecuteChanged;

public void Execute(object parameter) { Application.Current.Shutdown();

}

}

Для привязки команды к пункту меню или к ссылке мы указываем  в свойстве

Command  имя команды – Exit:

<MenuItem Header=’_File’>

<MenuItem Header=’E_xit’>

<MenuItem.Command>

<l:Exit />

</MenuItem.Command>

</MenuItem>

</MenuItem>

<Hyperlink>

<Hyperlink.Command><l:Exit /></Hyperlink.Command>

</Hyperlink>

Так как команда часто вызывается из нескольких мест, принято заводить ста

тическое поле, содержащее экземпляр команды:

public partial class Window1 : Window {

public static readonly ICommand ExitCommand = new Exit();

}

Дополнительный плюс такой реализации заключается в том, что реализацию класса  Exit  можно скрыть,  объявив,  что поле имеет тип ICommand. Теперь  Exit можно сделать закрытым  классом, а в разметке привязаться к статическому  полю:

<MenuItem Header=’_File’>

<MenuItem Header=’E_xit’ Command=’{x:Static l:Window1.ExitCommand}’ />

</MenuItem>

</MenuItem>

Таким образом, наше окно может раскрывать  свою функциональность в виде команд. Однако тут имеется интересная  проблема. Сейчас команда Exit реализо вана на глобальном уровне; мы можем вызвать ее из любого места и тем самым за вершить  приложение. Предположим, однако, что «exit» должно означать  закры тие текущего окна. В таком случае хотелось бы отделить  реализацию выхода от определения, как это было с событиями.

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

class Exit : ICommand {

public static readonly RoutedEvent ExecuteEvent = EventManager.RegisterRoutedEvent(

«Execute», RoutingStrategy.Bubble, typeof(RoutedEventHandler),

typeof(Exit));

}

Поскольку для этого события  задана стратегия  Bubble, оно будет всплывать от  источника.   Чтобы   возбудить   событие,   мы  изменим   реализацию  метода Execute, так чтобы он искал текущий элемент (в данном примере мы воспользо вались для этой цели методом Keyboard.FocusedElement, но могли бы остано виться  на любом механизме  обнаружения «текущего»),  а затем возбуждал  под ходящее событие:

public void Execute(object parameter) { RoutedEventArgs e =

new RoutedEventArgs(Exit.ExecuteEvent, Keyboard.FocusedElement);

Keyboard.FocusedElement.RaiseEvent(e);

}

Это подводит  нас к идее привязки команд – возможности отделить  реализа цию команды от ее назначения. Вернемся к классу Window1 и добавим в него ре ализацию  команды:

public partial class Window1 : Window {

public Window1() { InitializeComponent();

AddHandler(Exit.ExecuteEvent, ExitExecuted);

}

void ExitExecuted(object sender, RoutedEventArgs e) {

this.Close();

}

}

Тут возможно  некоторое недопонимание. Напомним,  что цель команды – предложить абстракцию того, что должно происходить.  В данном случае элемент управления (скажем, MenuItem) вызывает команду в ответ на событие (к приме ру, Click). При нашей реализации команда Exit возбудит  событие Execute,  кото рое распространится по дереву элементов, а объект Window сможет подписаться на него и выполнить те или иные действия  (рис. 7.4):

1.  Пользователь щелкает по пункту меню.

2.  MenuItem вызывает метод Execute  команды.

3.   Реализация команды Exit возбуждает событие Exit.Execute от имени эле

мента, имеющего фокус (в данном случае MenuItem).

4.  Событие всплывает  вверх по дереву.

5.  Window получает событие Exit.Execute.

6.  Window выполняет обработчик  события (закрывает окно).

Можно  было бы пойти  дальше и включить  в интерфейс ICommand средства поддержки  привязок к вводу (для  обработки ввода с клавиатуры, от мыши и пе ра), параметров и прочего2. Однако в каркасе есть встроенный класс RoutedCommand, который большую часть всего этого уже умеет делать.

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

public partial class Window1 : Window {

public static readonly ICommand ExitCommand =

new RoutedCommand(«Exit», typeof(Window1));

}

Чтобы при выполнении команды что то происходило,  нужно связать с коман дой обрабатывающий код (аналогично тому, как мы поступили с событием в пре дыдущем  примере).  Маршрутизируемые команды  всплывают  (как  и события), поэтому  можно добавить  привязку к команде  в корневое  окно и таким  образом увидеть все команды. Для привязки к команде нужно указать вид интересующей команды и код, который будет выполняться при ее поступлении:

public Window1() { InitializeComponent();

CommandBindings.Add(new CommandBinding(ExitCommand, ExitExecuted));

}

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

void ExitExecuted(object sender, ExecutedRoutedEventArgs e) {

this.Close();

}

Привязка к команде  позволяет  решить, следует  ли активировать команду,  а также  с помощью  свойства  InputBindings отобразить  на команды  действия3   по вводу данных (жесты):

<Window x:Class=’EssentialWPF.Window1’ xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’ xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’ xmlns:l=’clr namespace:EssentialWPF’

Title=’EssentialWPF’

<Window.InputBindings>

<KeyBinding Key=’A’ Modifiers=’Control’ Command=’{x:Static l:Window1.ExitCommand}’ />

</Window.InputBindings>

</Window>

Еще одна существенная особенность – это понятие о «безопасных  командах». С некоторыми командами,  например  вырезания, копирования и вставки,  сопря жены определенные угрозы. С целью гарантировать, что система выполняет та кие операции  только по явному  запросу пользователя (или  если разрешает  сис тема управления цифровыми правами),  класс RoutedCommand может отслежи вать, была ли команда инициирована пользователем.

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

Источник: К. Андерсон  Основы  Windows Presentation Foundation. Пер. с англ. А. Слинкина — М.: ДМК Пресс, 2008 — 432 с.: ил.

По теме:

  • Комментарии