Главная страница
Случайная страница
Разделы сайта
АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника
|
Упражнение 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=" https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns: x=" https://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=" https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns: x=" https://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
- MyButton: Наблюдаю событие Tap
- UniformGrid: Наблюдаю событие Tap
- Grid: Наблюдаю событие Tap
- DockPanel: Наблюдаю событие Tap
- Window1: Наблюдаю событие Tap
| RoutingStrategy.Tunnel
| Стратегия маршрутизации: Tunnel
- Window1: Наблюдаю событие Tap
- DockPanel: Наблюдаю событие Tap
- Grid: Наблюдаю событие Tap
- UniformGrid: Наблюдаю событие Tap
- MyButton: Наблюдаю событие Tap
| RoutingStrategy.Direct
| Стратегия маршрутизации: Direct
- MyButton: Наблюдаю событие Tap
| В таблице наглядно показано, как осуществляется стратегия маршрутизации события по дереву элементов: событие всплывает, нисходит или обрабатывается в месте возбуждения и немедленно останавливается (Bubble, Tunnel или Direct).
|