| исключение в лабораторной работе № 3 |
События и команды в WPF
Прямой вызов команд
Команды WPF можно вызывать и напрямую, а необязательно присоединять к источникам. Правда, это будет уже извращение и сам механизм команд будет использоваться только частично. При таком подходе придется реагировать на состояния источников и на возможность выполнения команды самостоятельным кодом. Но знать о существовании такой возможности программисту следует. Покажем это...
-
Добавьте к интерфейсу
окна еще четыре кнопки так
<Window x:Class="BindingCommandsXAML.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1: Декларативная привязка" Height="300" Width="300"
Background="#FFD4D0C8"
WindowStartupLocation="CenterScreen"
>
<Window.CommandBindings>
<CommandBinding
Command="ApplicationCommands.Open"
Executed="OpenCommand_Executed">
</CommandBinding>
<CommandBinding
Command="ApplicationCommands.Save"
Executed="SaveCommand_Executed">
</CommandBinding>
</Window.CommandBindings>
<StackPanel Margin="5">
<Menu>
<MenuItem Header="_File">
<MenuItem Command="ApplicationCommands.Open" />
<MenuItem Command="ApplicationCommands.Save" />
</MenuItem>
</Menu>
<Button Margin="5" Padding="5" Focusable="False"
Command="ApplicationCommands.Open"
Content="{x:Static ApplicationCommands.Open}"
/>
<Button Margin="5" Padding="5" Focusable="False"
Command="ApplicationCommands.Save"
Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"
/>
<Button Margin="5" Padding="5" Focusable="False"
Content="DirectCommandsOpen"
Click="directCommandsOpen_Click"
/>
<Button Margin="5" Padding="5" Focusable="False"
Content="DirectCommandsSave"
Click="directCommandsSave_Click"
/>
<Button Margin="5" Padding="5" Focusable="False"
Content="DirectBindingsOpen"
Click="directBindingsOpen_Click"
/>
<Button Margin="5" Padding="5" Focusable="False"
Content="DirectBindingsSave"
Click="directBindingsSave_Click"
/>
</StackPanel>
</Window>-
Вызовите контекстное
меню для записей события Click и командой Navigate to Event Handler создайте четыре обработчика в файле процедурного кода -
Заполните обработчики
следующим кодом прямого вызова команд
private void directCommandsOpen_Click(object sender, RoutedEventArgs e)
{
ApplicationCommands.Open.Execute(null, this);
}
private void directCommandsSave_Click(object sender, RoutedEventArgs e)
{
ApplicationCommands.Save.Execute(null, this);
}
private void directBindingsOpen_Click(object sender, RoutedEventArgs e)
{
this.CommandBindings[0].Command.Execute(null);
}
private void directBindingsSave_Click(object sender, RoutedEventArgs e)
{
this.CommandBindings[1].Command.Execute(null);
}В прямых вызовах двух первых обработчиков начальный параметр метода Execute() ожидает объект с дополнительной информацией, который будет передан на обработчик команды. Второй параметр принимает целевой элемент привязки команды с возможностями интерфейса IInputElement, который будет прослушивать команду и вызывать обработчик. В нашем случае это объект окна. Два следующих обработчика используют вызовы команд из коллекции прослушивающего элемента (окна), привязанные к нему в разметке.
-
Запустите приложение
и убедитесь, что добавленные кнопки инициируют прямой вызов команд
Упражнение 5. Привязка команд в процедурном коде
Частично повторим предыдущее упражнение, но привязку команд выполним в процедурном коде.
-
Добавьте к решению
командой File/Add/New Project новый проект с именем BindingCommandsCode и назначьте его стартовым
-
Откорректируйте разметку
окна Window1 так
<Window x:Class="BindingCommandsCode.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1: Кодовая привязка" Height="300" Width="300"
Background="#FFD4D0C8"
WindowStartupLocation="CenterScreen"
>
<StackPanel Margin="5">
<Menu>
<MenuItem Header="_File">
<MenuItem Command="ApplicationCommands.Open" />
<MenuItem Command="ApplicationCommands.Save" />
</MenuItem>
</Menu>
<Button Margin="5" Padding="5" Focusable="False"
Command="ApplicationCommands.Open"
Content="Open"
/>
<Button Margin="5" Padding="5" Focusable="False"
Command="ApplicationCommands.Save"
Content="Save"
/>
</StackPanel>
</Window>Заметьте, что в этой разметке отсутствует как секция привязки команд, так и ссылки на обработчики команд. Все это мы выполним в процедурной части окна.
-
Внесите следующие изменения
в класс Window1 файла процедурного кода Window1.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
// Регистрация обработчика
this.Loaded += new RoutedEventHandler(Window1_Loaded);
}
void Window1_Loaded(object sender, RoutedEventArgs e)
{
// Привязка команд в коде
CommandBinding binding = new CommandBinding();
binding.Command = ApplicationCommands.Open;
binding.Executed +=
new ExecutedRoutedEventHandler(OpenCommand_Executed);
this.CommandBindings.Add(binding);
binding = new CommandBinding();
binding.Command = ApplicationCommands.Save;
binding.Executed +=
new ExecutedRoutedEventHandler(SaveCommand_Executed);
this.CommandBindings.Add(binding);
}
void OpenCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Выполнена команда Open");
}
void SaveCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Выполнена команда Save");
}
}-
Запустите приложение
и убедитесь в работоспособности механизма команд WPF
Жесты как источники команд
Комбинации клавиш, предоставляющие прямой доступ к выполнению команд меню, ранее называли акселераторами (ускорителями), теперь - жестами. Жесты можно хранить в самой команде в коллекции InputGestures, а можно хранить в прослушивающем элементе в коллекции InputBindings наряду с командой. Кроме интерфейсных элементов жесты могут служить еще одним источником команд. Команда, имеющая жесты в своей коллекции, может вообще не быть присоединенной ни к одному из визуальных элементов, а возбуждаться только жестами.
Добавление жестов в команду
Жесты представленны абстрактным классом System.Windows.Input. InputGesture. Этот класс наследует двум типам жестов: клавиатурных и мыши, которые представлены объектами KeyGesture и MouseGesture соответственно. Команды, в свою очередь, имеют коллекцию жестов InputGestures, которую заранее можно наполнить объектами нужных жестов клавиатуры и мыши. Комбинации клавиш и кнопок мыши проще всего добавлять в объект жеста через его конструктор при создании экземпляра по схеме ( Key, ModifierKeys ) и ( MouseAction, ModifierKeys ).
В следующем примере показано, как в команду можно добавить жесты, которые будут ее запускать
// Клавиатурный жест Control+F
InputGesture gesture = new KeyGesture(Key.F, ModifierKeys.Control, "Ctrl+F");
ApplicationCommands.Find.InputGestures.Add(gesture);
// Комбинированный жест Control+LeftClick
gesture = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control);
ApplicationCommands.Find.InputGestures.Add(gesture);
// Клавиатурный жест Control+Q
KeyGesture keyGesture = new KeyGesture(Key.Q, ModifierKeys.Control, "Ctrl+Q");
ApplicationCommands.Find.InputGestures.Add(keyGesture);
// Комбинированный жест Alt+LeftClick
MouseGesture mouseGesture = new MouseGesture();
mouseGesture.MouseAction = MouseAction.LeftClick;
mouseGesture.Modifiers = ModifierKeys.Alt;
ApplicationCommands.Find.InputGestures.Add(mouseGesture);
//gesture = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Alt);
//ApplicationCommands.Find.InputGestures.Add(gesture);Теперь команда Find будет запускаться щелчком на кнопке, щелчком левой кнопки мыши с нажатой клавишей Ctrl, комбинациями клавиш Ctrl+F или Ctrl+Q.
Продолжим модификацию нашего упражнения, чтобы проиллюстрировать сказанное о жестах.
-
Добавьте в обработчик
события Loaded класса окна Window1 следующий код, модифицирующий жесты библиотечных
команд Open и Save
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
// Регистрация обработчика
this.Loaded += new RoutedEventHandler(Window1_Loaded);
}
void Window1_Loaded(object sender, RoutedEventArgs e)
{
// Привязка команд в коде
CommandBinding binding = new CommandBinding();
binding.Command = ApplicationCommands.Open;
binding.Executed +=
new ExecutedRoutedEventHandler(OpenCommand_Executed);
this.CommandBindings.Add(binding);
binding = new CommandBinding();
binding.Command = ApplicationCommands.Save;
binding.Executed +=
new ExecutedRoutedEventHandler(SaveCommand_Executed);
this.CommandBindings.Add(binding);
// Очистка коллекций прежних жестов команд
ApplicationCommands.Open.InputGestures.Clear();
ApplicationCommands.Save.InputGestures.Clear();
// Добавление новых жестов клавиатуры Alt+O и Alt+S
InputGesture key = new KeyGesture(Key.O, ModifierKeys.Alt, "Alt+O");
ApplicationCommands.Open.InputGestures.Add(key);
//
KeyGesture keyGesture = new KeyGesture(Key.S, ModifierKeys.Alt, "Alt+S");
ApplicationCommands.Save.InputGestures.Add(keyGesture);
// Добавление новых жестов мыши Ctrl+LeftClick и Ctrl+RightClick
InputGesture mouse = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control);
ApplicationCommands.Open.InputGestures.Add(mouse);
//
MouseGesture mouseGesture = new MouseGesture();
mouseGesture.MouseAction = MouseAction.RightClick;
mouseGesture.Modifiers = ModifierKeys.Control;
ApplicationCommands.Save.InputGestures.Add(mouseGesture);
}
void OpenCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Выполнена команда Open");
}
void SaveCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Выполнена команда Save");
}
}Вначале мы очистили коллекции команд от прежних жестов, затем добавили свои жесты, в том числе с участием мыши. Коллекции можно было и не очищать, тогда работали бы прежние жесты наряду с вновь добавленными.
-
Запустите приложение
и убедитесь, что теперь прежние клавиатурные комбинации Ctrl+O и Ctrl+S не
работают, вместо них программа реагирует на жесты клавиатуры Alt+O и Alt+S, а также выполняются комбинированные жесты с участием мыши Ctrl+LeftClick и Ctrl+RightClick
В нашей программе после добавления жестов появился существенный недостаток - сами жесты мы поменяли, но в меню все-таки остались старые маркеры команд Ctrl+O и Ctrl+S. Хоть мы и пытались в конструкторе KeyGesture() определить в последнем параметре displayString новые маркеры для жестов в меню, но все осталось по прежнему. Дело здесь в том, что объект меню создается и инициализируется в конструкторе класса по объекту уже присоединенной к источнику команды. И это происходит раньше, чем мы в коде начинаем модифицировать саму команду.
Чтобы это исправить, можно воспользоваться несколькими способами:
Способ 1
-
Определитете в разметке
в элементах меню свойство InputGestureText следующим образом
<Menu>
<MenuItem Header="_File">
<MenuItem Command="ApplicationCommands.Open"
InputGestureText="Alt+O" />
<MenuItem Command="ApplicationCommands.Save"
InputGestureText="Alt+S" />
</MenuItem>
</Menu>-
Запустите приложение
и убедитесь, что теперь маркеры команд меню изменились
Способ 2
Вместо разметки изменить свойство InputGestureText можно в процедурном коде, но для этого элементам меню нужно присвоить имена.
-
Удалите в разметке
из дескрипторов элементов меню параметры Command и InputGestureText, а добавьте
вместо них имена элементов, как показано ниже
<Menu>
<MenuItem Header="_File">
<MenuItem Name="openMenu" />
<MenuItem Name="saveMenu" />
</MenuItem>
</Menu>-
Добавьте в самый конец
обработчика события Loaded после кода добавления жестов следующую пару строк
динамического присоединения модифицированных команд к меню-источнику
void Window1_Loaded(object sender, RoutedEventArgs e)
{
// Привязка команд в коде
CommandBinding binding = new CommandBinding();
binding.Command = ApplicationCommands.Open;
binding.Executed +=
new ExecutedRoutedEventHandler(OpenCommand_Executed);
this.CommandBindings.Add(binding);
binding = new CommandBinding();
binding.Command = ApplicationCommands.Save;
binding.Executed +=
new ExecutedRoutedEventHandler(SaveCommand_Executed);
this.CommandBindings.Add(binding);
// Очистка коллекций прежних жестов команд
ApplicationCommands.Open.InputGestures.Clear();
ApplicationCommands.Save.InputGestures.Clear();
// Добавление новых жестов клавиатуры Alt+O и Alt+S
InputGesture key = new KeyGesture(Key.O, ModifierKeys.Alt, "Alt+O");
ApplicationCommands.Open.InputGestures.Add(key);
//
KeyGesture keyGesture = new KeyGesture(Key.S, ModifierKeys.Alt, "Alt+S");
ApplicationCommands.Save.InputGestures.Add(keyGesture);
// Добавление новых жестов мыши Ctrl+LeftClick и Ctrl+RightClick
InputGesture mouse = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control);
ApplicationCommands.Open.InputGestures.Add(mouse);
//
MouseGesture mouseGesture = new MouseGesture();
mouseGesture.MouseAction = MouseAction.RightClick;
mouseGesture.Modifiers = ModifierKeys.Control;
ApplicationCommands.Save.InputGestures.Add(mouseGesture);
// Присоединяем модифицированные команды к меню-источнику
openMenu.Command = ApplicationCommands.Open;
saveMenu.Command = ApplicationCommands.Save;
}-
Запустите приложение
и убедитесь, что теперь маркеры команд меню изменились в соответствии с введенными
нами при формировании новых жестов
Способ 3
-
Разметку меню сделайте
такой
<Menu>
<MenuItem Header="_File">
<MenuItem Name="openMenu"
Command="ApplicationCommands.Open" />
<MenuItem Name="saveMenu"
Command="ApplicationCommands.Save" />
</MenuItem>
</Menu>-
Замените в обработчике Window1_Loaded () только что добавленный код на новый
// Присоединяем модифицированные команды к меню-источнику
//openMenu.Command = ApplicationCommands.Open;
//saveMenu.Command = ApplicationCommands.Save;
openMenu.InputGestureText = "Alt+O";
saveMenu.InputGestureText = "Alt+S";-
Запустите приложение
и убедитесь, что маркеры команд меню стали правильными
Добавление жестов в прослушивающий элемент
Есть еще один способ добавления жестов, о котором стоит упомянуть. Ранее обсуждалось, что все пользовательские элементы WPF наследуют от UIElement или ContentElement, а эти классы, в свою очередь, имеют в качестве свойства коллекцию InputBindings типа InputBindingCollection. Коллекция может быть заполнена классами KeyBinding или MouseBinding, каждый из которых связывает жест клавиатуры (представленный объектом KeyGesture ) или мыши (представленный объектом MouseGesture ) с командой. Оба класса наследуют тип InputBinding.
Например, привязать жесты с командой к объекту окна можно одним из следующих способов
<Window x:Class="Tmp.window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Window.InputBindings>
<KeyBinding Key="F"
Modifiers="Control"
Command="ApplicationCommands.Find" />
<MouseBinding MouseAction="Control+LeftClick"
Command="ApplicationCommands.Find" />
<KeyBinding Key="Q"
Modifiers="Control"
Command="ApplicationCommands.Find" />
<MouseBinding Gesture="Alt+LeftClick"
Command="ApplicationCommands.Find" />
</Window.InputBindings>
<Grid>
<Button Command="ApplicationCommands.Find"
Height="23"
Width="75"
>
Поиск
</Button>
</Grid>
</Window>В классе MouseBinding свойство Modifiers доступно только для чтения, поэтому жест для мыши нужно вводить в свойство MouseAction сразу целиком, как показано в только что приведенном примере.
// Клавиатурный жест Control+F
KeyGesture keyGesture = new KeyGesture(Key.F, ModifierKeys.Control);
KeyBinding keyBinding = new KeyBinding(ApplicationCommands.Find, keyGesture);
this.InputBindings.Add(keyBinding);
// Комбинированный жест Control+LeftClick
MouseGesture mouseGesture = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control);
MouseBinding mouseBinding = new MouseBinding(ApplicationCommands.Find, mouseGesture);
this.InputBindings.Add(mouseBinding);
// Клавиатурный жест Control+Q
InputGesture gesture = new KeyGesture(Key.Q, ModifierKeys.Control);
ICommand command = ApplicationCommands.Find;
keyBinding = new KeyBinding(command, (KeyGesture)gesture);
this.InputBindings.Add(keyBinding);
// Комбинированный жест Alt+LeftClick
gesture = new MouseGesture();
((MouseGesture)gesture).MouseAction = MouseAction.LeftClick;
((MouseGesture)gesture).Modifiers = ModifierKeys.Alt;
mouseBinding = new MouseBinding();
mouseBinding.Command = command;
mouseBinding.Gesture = gesture;
this.InputBindings.Add(mouseBinding);Упражнение 6. Разработка простого блокнота без механизма команд
В данном упражнении мы разработаем приложение, частично имитирующее текстовый блокнот, но механизм команд WPF применять не будем. В последующем упражнении на базе разработанного кода мы создадим альтернативный вариант текстового блокнота, но уже с использованием команд. Ух-х-х, поехали!
-
Командой Add/New Project добавьте к решению новый проект WPF Application с именем Notepad1 и назначьте его стартовым
Основную часть интерфейса приложения, как и положено, мы создадим декларативно в разметке XAML, а управление - в части процедурного кода C#.
-
Отредактируйте интерфейсную
часть окна следующим образом
<Window x:Class="Notepad1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1: Управление состоянием источников команд"
Width="500" Height="375"
MinWidth="500" MinHeight="375"
WindowStartupLocation="CenterScreen"
ResizeMode="CanResizeWithGrip"
>
<DockPanel>
</DockPanel>
</Window>Создание главного меню
-
Добавьте в дескриптор <DockPanel> </DockPanel> разметку создания меню
<DockPanel LastChildFill="True">
<!-- Меню -->
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_New" InputGestureText="Ctrl+N">
</MenuItem>
<MenuItem Header="_Open..." InputGestureText="Ctrl+O" />
<MenuItem Header="_Save" InputGestureText="Ctrl+S" />
<MenuItem Header="Save _As..." />
<Separator />
<MenuItem Header="Page Set_up..." />
<MenuItem Header="P_rint Preview" InputGestureText="Ctrl+F2" />
<MenuItem Header="_Print..." InputGestureText="Ctrl+P" />
<Separator />
<MenuItem Header="E_xit" />
</MenuItem>
<MenuItem Header="_Edit">
<MenuItem Header="_Undo" InputGestureText="Ctrl+Z" />
<MenuItem Header="_Redo" InputGestureText="Ctrl+Y" />
<Separator></Separator>
<MenuItem Header="Cu_t" InputGestureText="Ctrl+X" />
<MenuItem Header="_Copy" InputGestureText="Ctrl+C" />
<MenuItem Header="_Paste" InputGestureText="Ctrl+V" />
<MenuItem Header="De_lete" InputGestureText="Del" />
<Separator></Separator>
<MenuItem Header="_Find..." InputGestureText="Ctrl+F" />
<MenuItem Header="Find _Next" InputGestureText="F3" />
<MenuItem Header="_Replace..." InputGestureText="Ctrl+H" />
<MenuItem Header="_Go To..." InputGestureText="Ctrl+G" />
<Separator></Separator>
<MenuItem Header="Select _All" InputGestureText="Ctrl+A" />
</MenuItem>
<MenuItem Header="F_ormat">
<MenuItem Header="_Font..." />
<Separator />
<MenuItem Header="_Word Wrap" IsCheckable="True" IsChecked="True" InputGestureText="Ctrl+W" />
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="_About" />
</MenuItem>
</Menu>
</DockPanel>Знаки подчеркивания мы использовали для того, чтобы выделить символы, по которым пользователь может раскрывать меню и запускать команды после нажатия клавиш Alt или F10. Отмеченные символы должны быть уникальными как на горизонтальной линейке на уровне разделов, так и в рамках одного раздела меню. Каждому пункту присвоено название команды и клавиатурного жеста. В команде Word Wrap свойство IsCheckable обеспечивает самопереключаемость флажка, а его начальное состояние задается свойством IsChecked.

