Обстоятельное резюме
Команда — это механизм ввода в Windows Presentation Foundation (WPF), который обеспечивает обработку входных данных на более семантическом уровне, чем ввод данных устройства. Примерами команд являются операции копирования, вырезанияи вставки, которые встречаются во многих приложениях.
В этом обзоре определяется, какие команды находятся в WPF, какие классы являются частью модели команд, а также как использовать и создавать команды в приложениях.
В этом разделе содержатся следующие разделы:
Пример простой команды в WPF
Что такое команды
Команды имеют несколько назначений. Первое назначение — разделить семантику и объект, вызывающий команду из логики, которая выполняет команду. Это позволяет нескольким и разнородным источникам вызывать одну и ту же логику команд, и позволяет настраивать логику команды для различных целевых объектов. Например, операции редактирования копирования, вырезанияи вставки, которые встречаются во многих приложениях, можно вызывать с помощью различных действий пользователя, если они реализованы посредством команд. Приложение может разрешить пользователю вырезать выбранные объекты или текст, щелкнув кнопку, выбрав элемент в меню или используя сочетание клавиш, например CTRL+X. С помощью команд можно привязать каждый тип действия пользователя к одной логике.
Другая цель команд — указать, доступно ли действие. Чтобы продолжить пример вырезания объекта или текста, действие имеет смысл только в том случае, если выбрано что-то. Если пользователь пытается вырезать объект или текст, не выбрав ничего, ничего не произойдет. Чтобы указать это пользователю, многие приложения отключают кнопки и пункты меню, чтобы пользователь знал, можно ли выполнить действие. Команда может указать, возможно ли действие путем реализации метода CanExecute. Кнопка может подписаться на событие CanExecuteChanged и быть отключена, если CanExecute возвращает false
, или быть включена, если CanExecute возвращает true
.
Семантика команды может быть единой в разных приложениях и классах, но логика действия зависит от конкретного объекта, на который оно направлено. Сочетание клавиш CTRL+X вызывает команду
Простой пример команд в WPF
Самый простой способ использовать команду в WPF — использовать предопределенную RoutedCommand из одного из классов библиотек команд; используйте элемент управления, имеющий встроенную поддержку для обработки команды; используйте элемент управления, имеющий встроенную поддержку вызова команды. Команда Paste является одной из предопределенных команд в классе ApplicationCommands. Элемент управления TextBox имеет встроенную логику для обработки команды Paste. А класс MenuItem имеет встроенную поддержку вызовов команд.
В следующем примере показано, как настроить MenuItem так, чтобы при его нажатии на TextBoxбыла вызвана команда Paste, если 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 возникает, если диспетчер команд, который централизованно управляет операциями команд, обнаруживает изменение в источнике команд, которое может привести к недействительности команды, которая была вызвана, но еще не выполнена связыванием команд. Реализация ICommand в WPF — это класс RoutedCommand, на котором сосредоточено внимание этого обзора.
Основными источниками входных данных в WPF являются мышь, клавиатура, рукописные и перенаправленные команды. Более ориентированные на устройства входные данные используют RoutedEvent для уведомления объектов на странице приложения о том, что произошло событие ввода. RoutedCommand не отличается. Методы Execute и CanExecuteRoutedCommand не содержат логику приложения для команды, а вместо этого вызывают маршрутируемые события, которые туннелируются и распространяются через дерево элементов, пока не встречают объект с 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>
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 в InputGestureCollectionRoutedCommand.
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. Если CommandTarget задан в ICommandSource и соответствующая команда не является RoutedCommand, целевой объект команды игнорируется.
Источник команды может явно задать целевой объект команды. Если целевой объект команды не определен, элемент с фокусом клавиатуры будет использоваться в качестве целевого объекта команды. Одним из преимуществ использования элемента с фокусом клавиатуры в качестве целевого объекта команды является то, что разработчик приложения может использовать один и тот же источник команд для вызова команды на нескольких целевых объектах без необходимости отслеживать целевой объект команды. Например, если
В следующем примере показано, как явно задать целевой объект команды в разметке и в коде позади.
<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 выполняет несколько функций, связанных с командами. Он предоставляет набор статических методов для добавления и удаления 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см. в разделе Создание пользовательского примера routedCommand.
См. также
.NET Desktop feedback