Общие сведения о системе команд

Команды представляют собой механизм ввода в Windows Presentation Foundation (WPF), обеспечивающий обработку ввода на более семантическом уровне по сравнению с устройствами ввода. Примерами команд являются операции Копировать, Вырезать и Вставить, доступные во многих приложениях.

В этом разделе представлены общие сведения о командах WPF, классах, входящих в состав модели команд, а также о порядке использования и создания команд в приложениях.

В этом разделе содержатся следующие подразделы.

  • Что представляют собой команды?

  • Простой пример команды в 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 установлен фокус клавиатуры).

<StackPanel>
  <Menu>
    <MenuItem Command="ApplicationCommands.Paste" />
  </Menu>
  <TextBox />
</StackPanel>
            ' Creating the UI objects
            Dim mainStackPanel As New StackPanel()
            Dim pasteTextBox As New TextBox()
            Dim stackPanelMenu As New Menu()
            Dim pasteMenuItem As New MenuItem()

            ' Adding objects to the panel and the menu
            stackPanelMenu.Items.Add(pasteMenuItem)
            mainStackPanel.Children.Add(stackPanelMenu)
            mainStackPanel.Children.Add(pasteTextBox)

            ' Setting the command to the Paste command
            pasteMenuItem.Command = ApplicationCommands.Paste
// Creating the UI objects
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();

// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);

// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;

// Setting the command target to the TextBox
pasteMenuItem.CommandTarget = pasteTextBox;

Четыре основных понятия в системе команд 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.

В интерфейсе ICommandSource предоставляются три свойства: Command, CommandTarget и CommandParameter:

  • Command — команда, которая выполняется при вызове источника команды.

  • CommandTarget — объект, для которого выполняется команда. Необходимо отметить, что в WPF свойство CommandTarget интерфейса ICommandSource применимо только в том случае, если интерфейс ICommand принадлежит классу RoutedCommand. Если для свойства CommandTarget установлено значение ICommandSource, а соответствующая команда не принадлежит классу RoutedCommand, цель команды игнорируется. Если значение свойства CommandTarget не задано, в качестве цели команды используется элемент, в котором установлен фокус клавиатуры.

  • CommandParameter — тип данных, определяемый пользователем, который используется для передачи информации обработчикам, реализующим команду.

В WPF представлены следующие классы, реализующие интерфейс ICommandSource: ButtonBase, MenuItem, Hyperlink и InputBinding. При щелчке объектов ButtonBase, MenuItem и Hyperlink вызывается соответствующая команда. Объект InputBinding вызывает команду при выполнении связанного с ним объекта InputGesture.

В следующем примере описывается порядок использования объекта MenuItem в ContextMenu в качестве источника для команды Properties.

<StackPanel>
  <StackPanel.ContextMenu>
    <ContextMenu>
      <MenuItem Command="ApplicationCommands.Properties" />
    </ContextMenu>
  </StackPanel.ContextMenu>
</StackPanel>
            Dim cmdSourcePanel As New StackPanel()
            Dim cmdSourceContextMenu As New ContextMenu()
            Dim cmdSourceMenuItem As New MenuItem()

            ' Add ContextMenu to the StackPanel.
            cmdSourcePanel.ContextMenu = cmdSourceContextMenu
            cmdSourcePanel.ContextMenu.Items.Add(cmdSourceMenuItem)

            ' Associate Command with MenuItem.
            cmdSourceMenuItem.Command = ApplicationCommands.Properties
StackPanel cmdSourcePanel = new StackPanel();
ContextMenu cmdSourceContextMenu = new ContextMenu();
MenuItem cmdSourceMenuItem = new MenuItem();

// Add ContextMenu to the StackPanel.
cmdSourcePanel.ContextMenu = cmdSourceContextMenu;
cmdSourcePanel.ContextMenu.Items.Add(cmdSourceMenuItem);

// Associate Command with MenuItem.
cmdSourceMenuItem.Command = ApplicationCommands.Properties;

Как правило, источник команды будет осуществлять прослушивание события CanExecuteChanged. Это событие служит для оповещения источника команды о том, что изменена возможность выполнения команды для текущей цели команды. Источник команды может запрашивать текущее состояние объекта RoutedCommand с помощью метода CanExecute. Затем источник команды может отключить сам себя, если команда не может быть выполнена. Примером этого является выделение объекта MenuItem серым цветом, если команда не может быть выполнена.

Объект InputGesture может использоваться в качестве источника команды. В WPF существует два типа входных жестов: KeyGesture и MouseGesture. Объект KeyGesture можно сравнить с сочетанием клавиш, например CTRL+C. Объект KeyGesture включает в себя объект Key и набор объектов ModifierKeys. Объект MouseGesture включает в себя объект MouseAction и набор необязательных объектов ModifierKeys.

Для выполнения объекта InputGesture в качестве источника команды следует связать его с командой. Для этого предусмотрено несколько способов. Одним из способов является использование объекта InputBinding.

В следующем примере описывается порядок создания привязки KeyBinding между объектами KeyGesture и RoutedCommand.

<Window.InputBindings>
  <KeyBinding Key="B"
              Modifiers="Control" 
              Command="ApplicationCommands.Open" />
</Window.InputBindings>
            Dim OpenKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)

            Dim OpenCmdKeybinding As New KeyBinding(ApplicationCommands.Open, OpenKeyGesture)

            Me.InputBindings.Add(OpenCmdKeybinding)
KeyGesture OpenKeyGesture = new KeyGesture(
    Key.B,
    ModifierKeys.Control);

KeyBinding OpenCmdKeybinding = new KeyBinding(
    ApplicationCommands.Open,
    OpenKeyGesture);

this.InputBindings.Add(OpenCmdKeybinding);

Другим способом связывания объектов InputGesture и RoutedCommand является добавление объекта InputGesture в коллекцию InputGestureCollection объекта RoutedCommand.

В следующем примере описывается порядок добавления объекта KeyGesture в коллекцию InputGestureCollection объекта RoutedCommand.

            Dim OpenCmdKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)

            ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture)
KeyGesture OpenCmdKeyGesture = new KeyGesture(
    Key.B,
    ModifierKeys.Control);

ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture);

Класс CommandBinding

Объект CommandBinding предназначен для связывания команды с обработчиками событий, которые реализуют ее.

Класс CommandBinding содержит свойство Command, а также события PreviewExecuted, Executed PreviewCanExecute и CanExecute.

Свойство Command представляет собой команду, с которой связывается объект CommandBinding. Логика команды реализуется с помощью обработчиков событий, которые присоединены к событиям PreviewExecuted и Executed. Обработчики событий, присоединенные к событиям PreviewCanExecute и CanExecute, определяют возможность выполнения команды для текущей цели команды.

В следующем примере описывается порядок создания привязки CommandBinding в корневом объекте Window приложения. Объект CommandBinding связывает команду Open с обработчиками событий Executed и CanExecute.

<Window.CommandBindings>
  <CommandBinding Command="ApplicationCommands.Open"
                  Executed="OpenCmdExecuted"
                  CanExecute="OpenCmdCanExecute"/>
</Window.CommandBindings>
            ' Creating CommandBinding and attaching an Executed and CanExecute handler
            Dim OpenCmdBinding As New CommandBinding(ApplicationCommands.Open, AddressOf OpenCmdExecuted, AddressOf OpenCmdCanExecute)

            Me.CommandBindings.Add(OpenCmdBinding)
// Creating CommandBinding and attaching an Executed and CanExecute handler
CommandBinding OpenCmdBinding = new CommandBinding(
    ApplicationCommands.Open,
    OpenCmdExecuted,
    OpenCmdCanExecute);

this.CommandBindings.Add(OpenCmdBinding);

Далее создаются обработчики событий ExecutedRoutedEventHandler и CanExecuteRoutedEventHandler. Обработчик ExecutedRoutedEventHandler открывает объект MessageBox, в котором отображаются сведения о выполнении команды. Обработчик CanExecuteRoutedEventHandler устанавливает значение true свойства CanExecute.

Private Sub OpenCmdExecuted(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
    Dim command, targetobj As String
    command = CType(e.Command, RoutedCommand).Name
    targetobj = CType(sender, FrameworkElement).Name
    MessageBox.Show("The " + command + " command has been invoked on target object " + targetobj)
End Sub
void OpenCmdExecuted(object target, ExecutedRoutedEventArgs e)
{
    String command, targetobj;
    command = ((RoutedCommand)e.Command).Name;
    targetobj = ((FrameworkElement)target).Name;
    MessageBox.Show("The " + command +  " command has been invoked on target object " + targetobj);
}
Private Sub OpenCmdCanExecute(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
    e.CanExecute = True
End Sub
void OpenCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}

Объект CommandBinding присоединяется к определенному объекту, например корневому объекту Window приложения или элементу управления. Объект, к которому присоединен объект CommandBinding, определяет область привязки. Например, объект CommandBinding, присоединенный к предку цели команды, доступен с помощью события Executed. Однако объект CommandBinding, присоединенный к потомку цели команды, в этом случае недоступен. Это напрямую связано с методами восходящей и нисходящей маршрутизации объекта RoutedEvent, используемыми по отношению к объекту, который вызывает событие.

В некоторых случаях к цели команды присоединяется сам объект CommandBinding, как например в случае с классом TextBox и командами Cut, Copy и Paste. Однако во многих случаях рекомендуется присоединять объект CommandBinding к предку цели команды, например главному объекту Window или объекту Application, особенно в тех случаях, когда один и тот же объект CommandBinding может использоваться для нескольких целей команды. Эти решения принимаются на стадии разработки во время создания инфраструктуры команд.

Цель команды

Целью команды является элемент, для которого она выполняется. В отношении RoutedCommand целью команды является элемент, с которого начинается маршрутизация событий Executed и CanExecute. Как было отмечено ранее, в WPF свойство CommandTarget интерфейса ICommandSource применяется только в тех случаях, когда интерфейс ICommand принадлежит классу RoutedCommand. Если для свойства CommandTarget установлено значение ICommandSource, а соответствующая команда не принадлежит классу RoutedCommand, цель команды игнорируется.

Цель команды может быть явно задана в источнике команды. Если цель команды не определена, в этом качестве используется элемент, в котором установлен фокус клавиатуры. Одним из преимуществ использования в качестве цели команды элемента, в котором установлен фокус клавиатуры, является возможность использования одного источника команды для вызова команд для нескольких целей (в этом случае не требуется отслеживать цель команды). Например, если объект MenuItem вызывает команду Вставить в приложении, в котором содержатся элементы управления TextBox и PasswordBox, в качестве цели могут выступать объекты TextBox и PasswordBox (в зависимости от того, в каком из них установлен фокус клавиатуры).

В следующем примере описывается порядок явного определения цели команды в разметке и коде программной части.

<StackPanel>
  <Menu>
    <MenuItem Command="ApplicationCommands.Paste"
              CommandTarget="{Binding ElementName=mainTextBox}" />
  </Menu>
  <TextBox Name="mainTextBox"/>
</StackPanel>
            ' Creating the UI objects
            Dim mainStackPanel As New StackPanel()
            Dim pasteTextBox As New TextBox()
            Dim stackPanelMenu As New Menu()
            Dim pasteMenuItem As New MenuItem()

            ' Adding objects to the panel and the menu
            stackPanelMenu.Items.Add(pasteMenuItem)
            mainStackPanel.Children.Add(stackPanelMenu)
            mainStackPanel.Children.Add(pasteTextBox)

            ' Setting the command to the Paste command
            pasteMenuItem.Command = ApplicationCommands.Paste
// Creating the UI objects
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();

// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);

// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;

// Setting the command target to the TextBox
pasteMenuItem.CommandTarget = pasteTextBox;

Класс 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". Также можно получить привязки для других устройств ввода, например жестов пера Tablet PC или голосовых команд.

При ссылке на команды в различных библиотеках команд с помощью XAML обычно можно опустить имя класса библиотеки классов, которое предоставляет статическое свойство команды. Обычно имена команд задаются однозначно в виде строк. Собственные типы используются для логической группировки команд, но не являются обязательными для устранения неоднозначности. Например, можно использовать сокращенную форму команды Command="Cut" вместо Command="ApplicationCommands.Cut". Этот удобный механизм встроен в обработчик команд WPF XAML (более точно, это поведение преобразователя типа ICommand), на который во время загрузки ссылается обработчик WPF XAML.

Создание пользовательских команд

При необходимости можно создать собственные команды, расширяющие возможности, представленные в классах библиотеки команд. Существует два способа создания пользовательской команды. Написание команды с нуля с реализацией интерфейса ICommand. Однако чаще применяется создание объекта RoutedCommand или RoutedUICommand.

Пример создания пользовательской команды RoutedCommand см. в файле Create a Custom RoutedCommand Sample.

См. также

Задачи

Практическое руководство. Реализация ICommandSource

Как добавить команду в MenuItem

Ссылки

RoutedCommand

CommandBinding

InputBinding

CommandManager

Основные понятия

Общие сведения о входных данных

Общие сведения о перенаправленных событиях

Другие ресурсы

Create a Custom RoutedCommand Sample