Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
WPF-практика 5 События и команды.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.21 Mб
Скачать

Упражнение 3. Создание и прослушивание пользовательского события

Наподобие библиотечных маршрутизированных событий мы можем создавать свои собственные перенаправленные события, которые называют пользовательскими. Порядок добавления в класс событий таков:

  1. Вначале объявляется статическое поле класса только для чтения типа RoutedEvent, которое будет являться базовым при упаковке события. В соответствии с соглашением, имя поля правильно заканчивать постфиксом Event. Имя базового поля ассоциируется с идентификатором поля как ссылкой на объект RoutedEvent, а не с именем создаваемого события

  2. С помощью метода RegisterRoutedEvent() класса System.Windows. EventManager событие нужно зарегистрировать в среде исполнения CLR (Common Language Runtime) и сохранить ссылку на объект события в статическом поле. В дальнейшем этот объект можно передавать обработчикам события. Вызов метода регистрации можно разместить в статическом конструкторе класса или вызвать сразу при инициализации поля

  3. Объявить само событие с помощью делегата RoutedEventHandler, использовав расширенный способ объявления события, который используется для упаковки поля события. Делегат обеспечивает стандартную сигнатуру для обработчиков

    • public delegate void RoutedEventHandler(object sender, RoutedEventArgs e)

  4. Определить, если нужно, метод диспетчеризации события с префиксом 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() и той же сигнатурой

В результате должны быть созданы обработчики с именами:

  1. MyButton_Tap(object sender, RoutedEventArgs e)

  2. UniformGrid_Tap(object sender, RoutedEventArgs e)

  3. Grid_Tap(object sender, RoutedEventArgs e)

  4. nDockPanel_Tap(object sender, RoutedEventArgs e)

  5. 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

Результаты можно выводить и на консольное окно. Тогда нужно выполнить следующее:

  1. В функции ShowTap() заменить метод System.Diagnostics.Debug. WriteLine() на Console. WriteLine()

  2. Изменить тип приложения с 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

  1. MyButton: Наблюдаю событие Tap

  2. UniformGrid: Наблюдаю событие Tap

  3. Grid: Наблюдаю событие Tap

  4. DockPanel: Наблюдаю событие Tap

  5. Window1: Наблюдаю событие Tap

RoutingStrategy.Tunnel

    Стратегия маршрутизации: Tunnel

  1. Window1: Наблюдаю событие Tap

  2. DockPanel: Наблюдаю событие Tap

  3. Grid: Наблюдаю событие Tap

  4. UniformGrid: Наблюдаю событие Tap

  5. MyButton: Наблюдаю событие Tap

RoutingStrategy.Direct

    Стратегия маршрутизации: Direct

  1. MyButton: Наблюдаю событие Tap

В таблице наглядно показано, как осуществляется стратегия маршрутизации события по дереву элементов: событие всплывает, нисходит или обрабатывается в месте возбуждения и немедленно останавливается ( Bubble, Tunnel или Direct ).