Специальные команды
98WPF --- Привязка, команды и стили WPF --- Специальные команды
Какими бы полными не были пять стандартных классов команд (ApplicationCommands, NavigationCommands, EditingCommands, ComponentCommands и MediaCommands), они, очевидно, не могут предоставить абсолютно все, что нужно приложению. К счастью, в WPF довольно легко определять собственные специальные команды. Все, что для этого понадобится — это создать новый объект RoutedUICommand.
Класс RoutedUICommand имеет несколько конструкторов. Экземпляр RoutedUICommand можно создавать без дополнительной информации, однако практически всегда необходимо задавать имя команды, текст команды и тип владения. Вдобавок может предоставляться сокращенная клавиатурная комбинация для помещения в коллекцию InputGestures.
Наилучший подход предполагает следование примеру библиотек WPF и предоставлять специальные команды через статические свойства. Ниже показан пример:
public class MyCommands
{
// Создание команды requery
private static RoutedUICommand requery;
static MyCommands()
{
// Инициализация команды
InputGestureCollection inputs = new InputGestureCollection();
inputs.Add(new KeyGesture(Key.R, ModifierKeys.Control, "Ctrl + R"));
requery = new RoutedUICommand("Requery", "Requery", typeof(MyCommands), inputs);
}
public static RoutedUICommand Requery
{
get { return requery; }
}
}
Можно также модифицировать коллекцию RoutedCommand.InputGestures существующей команды, например, удаляя определенные привязки клавиш либо добавляя новые. Допускается даже добавлять привязки мыши, чтобы команда запускалась при одновременном нажатии определенной кнопки мыши и клавиши модификатора (хотя в таком случае привязку команды лучше размещать только в элементе, где должна производиться обработка действий мыши).
После определения команду можно использовать в своих привязках команд точно так же, как любую из готовых команд, предлагаемых WPF. Тем не менее, есть одна особенность. Чтобы применять свою команду в XAML, сначала необходимо отобразить свое пространство имен .NET на пространство имен XML. Например, если класс находится в пространстве имен Commands, потребуется добавить следующую строку:
xmlns:local="clr-namespace:Commands"
В данном примере в качестве псевдонима для пространства имен был выбран local. Однако в принципе псевдонимом может быть любое слово, соответствующее стилю файла XAML. Теперь к команде можно получать доступ через пространство имен local.
Ниже показан полный код примера простого окна с кнопкой, запускающей команду Requery:
<Window x:Class="Commands.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Commands"
Title="MyCommand" Height="200" Width="300">
<Window.CommandBindings>
<CommandBinding Command="local:MyCommands.Requery"
Executed="CommandBinding_Executed"></CommandBinding>
</Window.CommandBindings>
<Grid Margin="5">
<Button Command="local:MyCommands.Requery">Requery</Button>
</Grid>
</Window>
Использование одной и той же команды в разных местах
Одной из ключевых концепций в модели команд WPF является область действия. Хотя фактически существует только одна копия каждой команды, эффект применения команды варьируется в зависимости от места ее инициации. Например, два текстовых поля поддерживают команды Cut, Сору и Paste, но соответствующая операция выполняется в том из них, на котором в текущий момент находится фокус.
До сих пор еще не было показано, как добиться подобного поведения для создаваемых самостоятельно команд. Для примера предположим, что создается окно с пространством под два документа:
Команды Cut, Сору и Paste, как обнаружится, будут автоматически работать с правильным текстовым полем. Однако команды New, Open и Save, реализованные самостоятельно, этого делать не будут. Проблема здесь в том, что при срабатывании события Executed для одной из этих команд совершенно не понятно, к какому из текстовых полей оно относится — к первому или второму. Хотя объект ExecutedRoutedEventArgs поддерживает свойство Source, оно отражает лишь элемент, который имеет привязку к команде (подобно ссылке на отправителя). А пока что все привязки команд присоединены к содержащему окну.
Решить эту проблему можно, привязав команду в каждом текстовом поле по-разному с помощью коллекции CommandBindings.
Однако эта реализация имеет два недостатка. Первый состоит в том, что простой флаг isDirty теперь больше использовать нельзя, т.к. требуется следить не за одним, а за двумя текстовыми полями. Для этой проблемы существует несколько решений.
Во-первых, можно воспользоваться свойством TextBox.Tag для сохранения флага isDirty. Тогда при каждом вызове метода CanExecuteSave() достаточно будет просто заглядывать в свойство Tag отправителя. Во-вторых, для сохранения значения isDirty можно создать приватную словарную коллекцию с индексацией по ссылке на элемент управления. Тогда при вызове метода CanExecuteSave() достаточно будет просто найти в этой коллекции значение isDirty, которое принадлежит отправителю. Ниже показан код, который должен использоваться в таком случае:
private Dictionary<Object, bool> isDirty = new Dictionary<Object, bool>();
private void txt_TextChanged(object sender, RoutedEventArgs e)
{
isDirty[sender] = true;
}
private void SaveCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (isDirty.ContainsKey(sender) && isDirty[sender] == true)
{
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
Вторым недостатком текущей реализации является то, что она предусматривает создание двух привязок команд, а на самом деле требуется только одна. Это привносит беспорядок в файл XAML и усложняет его сопровождение. Проблема еще больше усугубляется при наличии большого количества таких команд, которые должны использоваться в обоих текстовых полях.
Решение состоит в создании единой командной привязки и ее добавление в коллекцию CommandBindings обоих элементов TextBox. Реализовать это в коде не составит труда. Однако чтобы сделать это в XAML-разметке, потребуется использовать ресурсы WPF.