This article has been localized into Russian by the community.
Использование команд
В прошлой статье было много теории о командах и том как они работают. Теперь мы посмотрим, как на практике присвоить команду элементу интерфейса и создать между ними связь.
Начнём с простого примера:
<Window x:Class="WpfTutorialSamples.Commands.UsingCommandsSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UsingCommandsSample" Height="100" Width="200">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New" Executed="NewCommand_Executed" CanExecute="NewCommand_CanExecute" />
</Window.CommandBindings>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Command="ApplicationCommands.New">New</Button>
</StackPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
namespace WpfTutorialSamples.Commands
{
public partial class UsingCommandsSample : Window
{
public UsingCommandsSample()
{
InitializeComponent();
}
private void NewCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("The New command was invoked");
}
}
}
Мы определили привязку команды в окне, добавив её в коллекцию окна CommandBindings. Мы уточнили, какую команду хотим использовать (команда New из ApplicationCommands), а также два обработчика событий. Визуальный интерфейс состоит из одной кнопки. Нашу команду назначили кнопке путём присвоения её свойству кнопки Command.
В коде мы обрабатываем два события. Обработчик CanExecute, который WPF выполнит во время бездействия приложения, чтобы проверить доступность выполнения команды, очень прост в этом примере, так как мы хотим выполнять команду в любой момент. Мы попросту установили в true значение свойства CanExecute, принадлежащее аргументу события.
Когда команда будет вызвана, обработчик Executed покажет окно с сообщением. Если запустить пример и нажать кнопку вы увидите сообщение. Имейте ввиду, что эта команда уже (в качестве бонуса) имеет предустановленные горячие клавиши. Вместо нажатия на кнопку, попробуйте нажать Ctrl+N на клавиатуре - результат тот же.
Использование метода CanExecute
В первом примере, мы написали CanExecute который просто возвращает true, так что кнопка доступна всегда. Конечно так не должно быть для всех кнопок - часто требуется включать и выключать кнопку в зависимости от состояния приложения.
Самый тривиальный пример: когда нужно переключать состояние кнопок Cut(вырезать) и Copy(копировать) в зависимости от наличия выделенного текста, и включать кнопку Paste(вставить) только при наличии скопированного или вырезанного текста. Что мы и сделали в примере:
<Window x:Class="WpfTutorialSamples.Commands.CommandCanExecuteSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CommandCanExecuteSample" Height="200" Width="250">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Cut" CanExecute="CutCommand_CanExecute" Executed="CutCommand_Executed" />
<CommandBinding Command="ApplicationCommands.Paste" CanExecute="PasteCommand_CanExecute" Executed="PasteCommand_Executed" />
</Window.CommandBindings>
<DockPanel>
<WrapPanel DockPanel.Dock="Top" Margin="3">
<Button Command="ApplicationCommands.Cut" Width="60">_Cut</Button>
<Button Command="ApplicationCommands.Paste" Width="60" Margin="3,0">_Paste</Button>
</WrapPanel>
<TextBox AcceptsReturn="True" Name="txtEditor" />
</DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
namespace WpfTutorialSamples.Commands
{
public partial class CommandCanExecuteSample : Window
{
public CommandCanExecuteSample()
{
InitializeComponent();
}
private void CutCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = (txtEditor != null) && (txtEditor.SelectionLength > 0);
}
private void CutCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
txtEditor.Cut();
}
private void PasteCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = Clipboard.ContainsText();
}
private void PasteCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
txtEditor.Paste();
}
}
}
В итоге мы имеем простой интерфейс с парой кнопок и текстовым полем. Первая кнопка вырезает текст, а вторая вставляет.
В коде, мы имеем два события для каждой кнопки: Одни события отвечают за действие, чье имя кончается на _Executed, и события CanExecute. Я описал некую логику для определения возможности выполнения команды и присвоил результат возвращаемому значению CanExecute в EventArgs.
Крутая штука состоит в том, что Вам не нужно вызывать эти методы для обновления кнопок: WPF сделает всю грязную работу за Вас, когда появится свободный момент, гарантируя тем самым, что интерфейс будет обновлён всегда.
Поведение команд по умолчанию и CommandTarget
Как мы видели в прошлом примере, обработка команд может иметь довольно мало кода, с большим количеством методов и стандартной логикой. Вот почему разработчики WPF решили заняться созданием стандартных команд за Вас. По факту, мы можем избежать написания кода в прошлом примере, ведь TextBox может сам позаботиться о командах типа Cut, Copy, Paste, Undo и Redo.
WPF сам обрабатывает события Executed и CanExecute когда текстовое поле вроде TextBox имеет фокус. Вы можете перезаписывать эти события, что мы собственно и сделали в прошлом примере, но если Вам хватит стандартного поведения, Вы можете позволить WPF сделать работу за Вас. Взгляните, насколько проще этот пример:
<Window x:Class="WpfTutorialSamples.Commands.CommandsWithCommandTargetSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CommandsWithCommandTargetSample" Height="200" Width="250">
<DockPanel>
<WrapPanel DockPanel.Dock="Top" Margin="3">
<Button Command="ApplicationCommands.Cut" CommandTarget="{Binding ElementName=txtEditor}" Width="60">_Cut</Button>
<Button Command="ApplicationCommands.Paste" CommandTarget="{Binding ElementName=txtEditor}" Width="60" Margin="3,0">_Paste</Button>
</WrapPanel>
<TextBox AcceptsReturn="True" Name="txtEditor" />
</DockPanel>
</Window>
Код для этого примера не нужен - WPF сделает всё сам, но только потому что мы хотим использовать эти специфичные команды для этих специфичных элементов управления. TextBox делает работу за нас.
Заметьте как я использовал свойства CommandTarget на кнопках чтобы привязать команды к нашему элементу TextBox. Это обязательно в данном примере, потому что WrapPanel не управляет фокусом так же как например Toolbar или Menu. Хотя задавать цель для команды всё же стоит.
Сводка
Работа с командами довольно прямолинейна, но требует дополнительную разметку и код. Но награда особенно очевидна когда вам нужно вызывать одно действие из разных мест, или когда вы используете встроенные команды которыми полностью управляет WPF, как видно из последнего примера.