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

Система команд представляет собой механизм ввода в 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.

<StackPanel>
  <Menu>
    <MenuItem Command="ApplicationCommands.Paste" />
  </Menu>
  <TextBox />
</StackPanel>
// 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;
' 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

Четыре основных понятия в системе команд 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. Если для ICommandSource задано значение CommandTarget, и соответствующая команда — не 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>
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;
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

Как правило, источник команды будет осуществлять прослушивание события 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>
KeyGesture OpenKeyGesture = new KeyGesture(
    Key.B,
    ModifierKeys.Control);

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

this.InputBindings.Add(OpenCmdKeybinding);
Dim OpenKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)

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

Me.InputBindings.Add(OpenCmdKeybinding)

Другой способ связать InputGesture с RoutedCommand заключается в добавлении InputGesture в InputGestureCollection для RoutedCommand.

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

KeyGesture OpenCmdKeyGesture = new KeyGesture(
    Key.B,
    ModifierKeys.Control);

ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture);
Dim OpenCmdKeyGesture As 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
CommandBinding OpenCmdBinding = new CommandBinding(
    ApplicationCommands.Open,
    OpenCmdExecuted,
    OpenCmdCanExecute);

this.CommandBindings.Add(OpenCmdBinding);
' Creating CommandBinding and attaching an Executed and CanExecute handler
Dim OpenCmdBinding As New CommandBinding(ApplicationCommands.Open, AddressOf OpenCmdExecuted, AddressOf OpenCmdCanExecute)

Me.CommandBindings.Add(OpenCmdBinding)

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

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 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 OpenCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}
Private Sub OpenCmdCanExecute(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
    e.CanExecute = True
End Sub

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 в зависимости от того, какой элемент управления имеет фокус клавиатуры.

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

<StackPanel>
  <Menu>
    <MenuItem Command="ApplicationCommands.Paste"
              CommandTarget="{Binding ElementName=mainTextBox}" />
  </Menu>
  <TextBox Name="mainTextBox"/>
</StackPanel>
// 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;
' 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

Диспетчер команд 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". Это удобный механизм, встроенный в обработчик XAML WPF для команд (точнее, это поведение ICommandпреобразователя типов, на которое ссылается обработчик WPF XAML во время загрузки).

Создание настраиваемых команд

Если команды в классах библиотеки команд не соответствуют вашим потребностям, вы можете создать собственные команды. Настраиваемые команды можно создать двумя способами. Первый способ подразумевает создание с нуля и реализацию интерфейса ICommand. Другой способ, а также наиболее распространенный подход, — создание RoutedCommand или RoutedUICommand.

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

См. также