- •Лабораторная работа: События и команды в wpf
- •Обзор библиотечных событий
- •Упражнение 1. Обработка событий клавиатуры
- •События мыши
- •Упражнение 2. Прослушивание событий мыши
- •Упражнение 3. Создание и прослушивание пользовательского события
- •Обработчики уровня класса
- •Добавление информации в объект аргумента события
- •Задание для Упражнения 3
- •Модель команд
- •Объекты команд
- •Библиотечные классы команд
- •Присоединение команды к источнику
- •Привязка команды к прослушивающему элементу
- •Упражнение 4. Привязка команд в разметке
- •Перекрытие функций диспетчеризации событий
- •Прямой вызов команд
- •Упражнение 5. Привязка команд в процедурном коде
- •Жесты как источники команд
- •Добавление жестов в команду
- •Способ 1
- •Способ 2
- •Способ 3
- •Добавление жестов в прослушивающий элемент
- •Упражнение 6. Разработка простого блокнота без механизма команд
- •Создание главного меню
- •Добавление иконок
- •Создание логических ресурсов
- •Создание панели инструментов, строки состояния и рабочей области
- •Замена встроенного контекстного меню
- •Назначение ресурсов неразделяемыми
- •Замена иконок на прозрачные
- •Отключение встроенных команд
- •Подключение иконки приложения
- •Распределение класса по нескольким файлам и создание вспомогательных функций
- •Размещение вариантов заголовков окна в ресурсах приложения
- •Создание заготовок обработчиков
- •Регистрация обработчиков в разметке
- •Реализация обработчиков раздела меню File
- •Обработка системной кнопки
- •Реализация части обработчиков раздела меню Edit
- •Разработка и кодирование диалогового окна Find and Replace
- •Кодирование функциональности Find and Replace в основном окне
- •Подключение функциональности Find and Replace к источникам задач
- •Разработка диалогового окна Go To
- •Применение вложенных ресурсов
- •Подключение функциональности Go To к основному окну
- •Прочие задачи
- •Принудительная перерисовка окна
- •Добавление жестов
- •Логика отключения источников задач
- •Реализация логики отключения источников задачи Save
- •Упражнение 7. Разработка простого блокнота с использованием механизма команд
- •Создание нового проекта из копии существующего
- •Краткий анализ задачи
- •Создание и привязка команд
- •Реализация логики доступности источников команд
- •Отображение позиции курсора в строке состояния
Упражнение 3. Создание и прослушивание пользовательского события
Наподобие библиотечных маршрутизированных событий мы можем создавать свои собственные перенаправленные события, которые называют пользовательскими. Порядок добавления в класс событий таков:
Вначале объявляется статическое поле класса только для чтения типа RoutedEvent, которое будет являться базовым при упаковке события. В соответствии с соглашением, имя поля правильно заканчивать постфиксом Event. Имя базового поля ассоциируется с идентификатором поля как ссылкой на объект RoutedEvent, а не с именем создаваемого события
С помощью метода RegisterRoutedEvent() класса System.Windows. EventManager событие нужно зарегистрировать в среде исполнения CLR (Common Language Runtime) и сохранить ссылку на объект события в статическом поле. В дальнейшем этот объект можно передавать обработчикам события. Вызов метода регистрации можно разместить в статическом конструкторе класса или вызвать сразу при инициализации поля
Объявить само событие с помощью делегата RoutedEventHandler, использовав расширенный способ объявления события, который используется для упаковки поля события. Делегат обеспечивает стандартную сигнатуру для обработчиков
public delegate void RoutedEventHandler(object sender, RoutedEventArgs e)
Определить, если нужно, метод диспетчеризации события с префиксом On
Объявление класса регистрации событий выглядит так
public static RoutedEvent RegisterRoutedEvent(
string name,
RoutingStrategy routingStrategy,
Type handlerType,
Type ownerType
)
name - имя маршрутизируемого события. Имя должно быть уникальным для данного типа владельца и не может быть пустой строкой или иметь значение ссылки null
routingStrategy - стратегия маршрутизации события, заданная в качестве значения перечисления System.Windows.RoutingStrategy. Это перечисление содержит элементы Tunnel, Bubble и Direct
handlerType - тип обработчика событий, который должен быть типом делегата и не может иметь значение ссылки null
ownerType - тип класса владельца маршрутизируемого события, который не может иметь значение ссылки null
Метод регистрации возвращает объект вновь зарегистрированного маршрутизируемого события, который нужно сохранить в статическом поле и в далнейшем упаковать в само событие. Упаковка события нужно для присоединения обработчиков, когда обработчики назначаются в разметке. Для прикрепления обработчиков в процедурном коде нужно использовать метод UIElement.AddHandler() явно, как будет показано ниже.
В данном упражнении создадим в классе собственное перенаправленное событие, которое будет исполнять назначенную стратегию маршрутизации ( Bubble, Tunnel, Direct ).
Добавьте к решению командой File/Add/New Project новый проект с именем UserEvents и назначьте его стартовым
Заполните файл Window1.xaml следующей базовой разметкой пользовательского интерфейса
<Window x:Class="UserEvents.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Пользовательские Routed-события" Height="300" Width="300"
Background="Red"
ToolTip="Элемент Window - Red"
MinWidth="300"
MinHeight="300"
ResizeMode="CanResizeWithGrip"
>
<DockPanel>
<Grid
Width="220"
Height="200"
Background="Green"
ToolTip="Элемент Grid - Green"
>
<UniformGrid
Height="140" Width="130"
Background="Blue"
ToolTip="Элемент UniformGrid - Blue"
>
<Button
VerticalAlignment="Center"
ToolTip="Элемент Button - #FFD4D0C8"
Margin="5,0,5,0"
>
Возбудить событие
</Button>
</UniformGrid>
</Grid>
</DockPanel>
</Window>
Чтобы не мудрствовать лукаво, на данном этапе мы взяли пустой пользовательский интерфейс из предыдущего упражнения и немного его упростили, убрав меню и контекстное меню окна.
Запустите приложение - получим заготовку окна с полуфункциональным интерфейсом для продолжения выполнения упражнения
Следующим шагом мы создадим класс с событием, расширяющий класс Button, и упакуем его в отдельный файл с именем MyButton.cs. Модель расширения кнопки будет удобна для возбуждения нашего события по перекрытому виртуальному методу OnClick().
Добавьте к текущему проекту командой Project/Add New Item новый файл с именем MyButton.cs по шаблону Custom Control (WPF), как показано на рисунке
увеличить изображение
Удалите сопутствующую папку Themes вместе с ее содержимым, которую автоматически создала оболочка для выбранного шаблона
Шаблон Custom Control (WPF) применяется для разработки пользовательских компонентов 'с нуля', но мы его здесь использовали потому, что в нем наиболее полно представлены подключенные пространства имен WPF
Заполните файл MyButton.cs следующим кодом
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace UserEvents
{
// Класс определения пользовательского события Tap,
// возбуждаемого по щелчку мыши на расширенной кнопке
public class MyButton : Button
{
// Объявляем базовое поле события
public static readonly RoutedEvent TapEvent;
// Инициализируем в статическом конструкторе базовое поле события
// (можно было инициализировать сразу при объявлении поля, без конструктора)
static MyButton()
{
TapEvent = EventManager.RegisterRoutedEvent(
"Tap", // Зарегистрированное имя
RoutingStrategy.Bubble, // Стратегия перенаправления
typeof(RoutedEventHandler), // Тип делегата обработчиков
typeof(MyButton) // Тип владельца события
);
}
// Контейнер для создания обработчиков в XAML
public event RoutedEventHandler Tap
{
add { base.AddHandler(TapEvent, value); }
remove { base.RemoveHandler(TapEvent, value); }
}
}
}
Событие нужно чем-то возбудить, собственно для этого мы и выбрали расширение именно кнопки. Переопределяемые события возбуждаются методом RaiseEvent(), который наследуется от класса UIElement всеми элементами управления пользовательского интерфейса (кроме документных для ContentElement ).
Добавьте в класс-расширение MyButton кнопки перекрытие виртуального метода OnClick(), наследуемого от базового класса Button, со следующим кодом
public static int count; // Счетчик перехвата события
// Перекроем метод щелчка базовой кнопки для возбуждения события
protected override void OnClick()
{
count = 0; // Сбрасываем счетчик
//Console.Clear(); // Очищаем консоль
base.RaiseEvent(new RoutedEventArgs(MyButton.TapEvent));// Возбуждаем событие
}
Необязательный поле-счетчик count мы ввели для нумерации узлов прохождения прослушиваемого события при выдачи результатов. Он должен накапливать значение, поэтому объявлен как статический, чтобы иметь область видимости уровня объекта-типа, а не объекта-экземпляра.
Далее мы вложим пользовательское событие в элементы логического дерева интерфейса и прикрепим к нему обработчики для прослушивания. Заметим, что владельцем пользовательского события Tap является сам класс-расширение кнопки, поэтому обработчик щелчка нужно прикрепить прямо к событию.
Для наглядности часть обработчиков пользовательского события мы прикрепим в разметке, а часть - в процедурном коде страницы. В последнем случае прослушивающим элементам дерева нужно присвоить имена. И еще, для видимости класса с событием в файле разметки в объект окна Window1 (или индивидуально в каждый прослушивающий элемент) нужно добавить пространство имен по синтаксису
xmlns:custom="clr-namespace:UserEvents"
с любым уникальным именем, например, custom.
Дополните разметку следующим выделенным кодом
<Window x:Class="UserEvents.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Пользовательские Routed-события" Height="300" Width="300"
Background="Red"
ToolTip="Элемент Window - Red"
MinWidth="300"
MinHeight="300"
ResizeMode="CanResizeWithGrip"
xmlns:custom="clr-namespace:UserEvents"
Name="nWindow1"
>
<DockPanel
Name="nDockPanel"
>
<Grid
Width="220"
Height="200"
Background="Green"
ToolTip="Элемент Grid - Green"
custom:MyButton.Tap="Grid_Tap"
>
<UniformGrid
Height="140" Width="130"
Background="Blue"
ToolTip="Элемент UniformGrid - Blue"
custom:MyButton.Tap="UniformGrid_Tap"
>
<custom:MyButton
VerticalAlignment="Center"
ToolTip="Элемент Button - #FFD4D0C8"
Margin="5,0,5,0"
Tap="MyButton_Tap"
>
Возбудить событие
</custom:MyButton>
</UniformGrid>
</Grid>
</DockPanel>
</Window>
Элементам Window1 и DockPanel мы только присвоили имена. Автоматически создать для них заготовки обработчиков с нужной сигнатурой оболочка не сможет, поскольку она их не видит. А для элементов с вложенными событиями - создаст.
Щелкайте на записях присоединения обработчиков в элементах Grid, UniformGrid, MyButton кода разметки и командой контекстного меню Navigate to Event Handler создайте в процедурном коде соответствующие заготовки обработчиков
Создайте в процедурном коде по любой из полученных заготовок еще два обработчика с именами nDockPanel_Tap() и nWindow1_Tap() и той же сигнатурой
В результате должны быть созданы обработчики с именами:
MyButton_Tap(object sender, RoutedEventArgs e)
UniformGrid_Tap(object sender, RoutedEventArgs e)
Grid_Tap(object sender, RoutedEventArgs e)
nDockPanel_Tap(object sender, RoutedEventArgs e)
nWindow1_Tap(object sender, RoutedEventArgs e)
Добавьте в экземплярный конструктор класса MyButton процедурный код динамического прикрепления обработчиков к пользовательскому событию для именованных элементов nDockPanel и nWindow1 следующим образом
// Конструктор экземпляра
public Window1()
{
InitializeComponent();
// Динамический способ присоединения обработчиков
nWindow1.AddHandler(MyButton.TapEvent,
new RoutedEventHandler(this.nWindow1_Tap));
nDockPanel.AddHandler(MyButton.TapEvent,
new RoutedEventHandler(this.nDockPanel_Tap));
}
Добавьте в конец класса Window1 функцию ShowTap() вывода результатов прослушивания события Tap
void ShowTap(object obj, RoutedEventArgs args)
{
if (MyButton.count == 0)
{
System.Diagnostics.Debug.WriteLine(
String.Format("\n\t Стратегия маршрутизации: {0}",
args.RoutedEvent.RoutingStrategy));
}
String typeName = obj.GetType().Name;
System.Diagnostics.Debug.WriteLine(
String.Format("{0}) {1}: Наблюдаю событие Tap",
++MyButton.count, typeName));
}
Данная функция будет выводить информацию о прохождении события по элементам дерева в панель оболочки Output. Применение метода String. Format() объясняется тем, что ни одна из 4-х перегрузок функции Debug. WriteLine() не способна принимать строку форматирования со спецификаторами формата типа {0}, {1}, и т.д. (в отличие от метода Console. WriteLine() ). При каждом новом возбуждении события в кнопке-источнике мы будем добавлять к выводу результатов заголовок с применяемой стратегией маршрутизации.
Если у вас панель Output еще не включена, то включите ее командой меню оболочки View/Output
Результаты можно выводить и на консольное окно. Тогда нужно выполнить следующее:
В функции ShowTap() заменить метод System.Diagnostics.Debug. WriteLine() на Console. WriteLine()
Изменить тип приложения с Windows Application на Console Application командой Project/Properties (в списке Output type )
увеличить изображение
Вставьте в каждый из созданных обработчиков пользовательского события Tap вызов функции ShowTap() по следующему образцу
private void nWindow1_Tap(object sender, RoutedEventArgs e)
{
this.ShowTap(sender, e);
}
Здесь мы стремились сделать код как можно унифицированнее, чтобы в случае смены целевого объекта вывода результатов, например, с панели Output на консоль, не пришлось бы много исправлять.
Наконец-то наступил момент истины - время испытать наш код и подтвердить правильность механизма работы пользовательских событий, который должен быть точно таким же, как и библиотечных.
Запустите последовательно приложение с разными значениям стратегии маршрутизации (перечисление RoutingStrategy ) при регистрации события в классе MyButton. В окне Output должен появится следующий результат
Результат работы пользовательского события |
|
Стратегия |
Результат |
RoutingStrategy.Bubble |
Стратегия маршрутизации: Bubble
|
RoutingStrategy.Tunnel |
Стратегия маршрутизации: Tunnel
|
RoutingStrategy.Direct |
Стратегия маршрутизации: Direct
|
В таблице наглядно показано, как осуществляется стратегия маршрутизации события по дереву элементов: событие всплывает, нисходит или обрабатывается в месте возбуждения и немедленно останавливается ( Bubble, Tunnel или Direct ).
