Поделиться через


Общие сведения о присоединенных событиях (WPF .NET)

Расширяемый язык разметки приложений (XAML) определяет языковой компонент и тип события, называемый присоединенным событием. Присоединенные события можно использовать для определения нового маршрутизируемого события в классе, не являющемся элементом, и вызова этого события на любом элементе вашего дерева. Для этого необходимо зарегистрировать присоединенное событие в качестве маршрутизированного события и предоставить определенные резервного кода, поддерживающий функции присоединенного события. Поскольку присоединенные события регистрируются как перенаправленные события, при их возникновении на элементе они распространяются через дерево элементов.

Необходимые условия

В статье предполагается, что у вас есть базовые знания о маршрутизируемых событиях в Windows Presentation Foundation (WPF) и что вы прочитали Обзор маршрутизации событий и XAML в WPF. Чтобы следовать примерам в этой статье, это поможет вам, если вы знакомы с XAML и знаете, как писать приложения WPF.

Синтаксис присоединенного события

В синтаксисе XAML присоединенное событие указывается по имени события и его тип владельца в виде <owner type>.<event name>. Поскольку имя события содержит имя его типа владельца, синтаксис позволяет присоединить событие к любому элементу, который можно инстанцировать. Этот синтаксис также применим к обработчикам регулярных перенаправленных событий, которые присоединяются к произвольному элементу вдоль маршрута событий.

Следующий синтаксис атрибута XAML подключает обработчик AquariumFilter_Clean для присоединенного события AquariumFilter.Clean к элементу aquarium1:

<aqua:Aquarium x:Name="aquarium1" Height="300" Width="400" aqua:AquariumFilter.Clean="AquariumFilter_Clean"/>

В этом примере префикс aqua: необходим, так как классы AquariumFilter и Aquarium существуют в другом пространстве имен и сборке CLR.

Вы также можете присоединить обработчики для присоединенных событий в коде за ним. Чтобы это сделать, сначала вызовите метод AddHandler объекта, к которому обработчик должен подключиться, а затем передайте идентификатор события и обработчик в качестве параметров методу.

Как WPF реализует добавленные события

Присоединенные события WPF реализуются как маршрутизируемые события, поддерживаемые полем RoutedEvent. В результате присоединенные события распространяются через дерево элементов после их возникновения. Как правило, объект, который вызывает присоединенное событие, известное как источник событий, является системой или источником службы. Системные или служебные источники не являются прямой частью дерева элементов. Для других присоединенных событий источник событий может быть элементом в дереве, например компонентом в составном элементе управления.

Сценарии присоединенных событий

В WPF присоединённые события используются в определённых функциональных областях, где есть абстракция уровня сервиса. Например, WPF использует прикрепленные события, реализуемые с помощью статических классов Mouse или Validation. Классы, взаимодействующие с службой или использующие ее, могут взаимодействовать с событием с помощью синтаксиса присоединенного события или отображать присоединенное событие как перенаправленное событие. Последний вариант является частью того, как класс может интегрировать возможности службы.

Входная система WPF широко использует присоединенные события. Однако почти все эти присоединенные события отображаются как эквивалентные несвязанные маршрутизированные события через базовые элементы. Каждое маршрутизированное входное событие является членом базового класса элементов и поддерживается оберткой события CLR. Вы редко будете использовать или обрабатывать прикрепленные события напрямую. Например, проще обрабатывать базовое присоединенное событие Mouse.MouseDown на UIElement с помощью эквивалентного UIElement.MouseDown перенаправленного события, чем с помощью синтаксиса присоединенного события в XAML или коде.

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

Обработка присоединенного события

Процесс написания кода и обработки присоединенного события в основном совпадает с процессом для не присоединённого маршрутизируемого события.

Как отмечалось ранее, существующие подключенные события WPF обычно не предназначены для непосредственной обработки в WPF. Чаще всего присоединенное событие позволяет элементу в составном элементе управления сообщать о его состоянии родительскому элементу в элементе управления. В этом сценарии событие вызывается в коде и зависит от обработки классов в соответствующем родительском классе. Например, элементы в Selector должны инициировать присоединенное событие Selected, которое затем обрабатывается классом Selector. Класс Selector потенциально преобразует событие Selected в перенаправленное событие SelectionChanged. Дополнительные сведения о перенаправленных событиях и обработке классов см. в разделе Маркировка перенаправленных событий как обработанные и обработка классов.

Определение настраиваемого присоединенного события

Если вы создаёте класс на основе общих базовых классов WPF, вы можете реализовать пользовательское присоединённое событие, включив два метода доступа в свой класс. Эти методы:

  • Метод Add<event name>Handler, с первым параметром, представляющим элемент, к которому подключен обработчик события, и вторым параметром, представляющим собой добавляемый обработчик события. Метод должен быть public и staticбез возвращаемого значения. Метод вызывает метод AddHandler базового класса, передавая перенаправленное событие и обработчик в качестве аргументов. Этот метод поддерживает синтаксис атрибута XAML для присоединения обработчика событий к элементу. Этот метод также обеспечивает доступ кода к хранилищу обработчика событий для присоединенного события.

  • Метод Remove<event name>Handler, где первый параметр — это элемент, к которому подключается обработчик события, а второй параметр — это обработчик события, который необходимо удалить. Метод должен быть помечен как public и static, без возвращаемого значения. Метод вызывает метод RemoveHandler базового класса, передавая перенаправленное событие и обработчик в качестве аргументов. Этот метод обеспечивает доступ кода к хранилищу обработчика событий для присоединенного события.

WPF реализует присоединенные события в качестве перенаправленных событий, так как идентификатор RoutedEvent определяется системой событий WPF. Кроме того, маршрутизация события — это естественное расширение концепции языка XAML для присоединенного события. Данная стратегия реализации ограничивает обработку присоединенных событий как классами, производными от UIElement, так и от ContentElement, поскольку только эти классы имеют AddHandler реализации.

Например, следующий код определяет присоединенное событие Clean для класса владельца AquariumFilter, который не является классом элементов. Код определяет присоединенное событие как маршрутизируемое событие и реализует необходимые методы доступа.

public class AquariumFilter
{
    // Register a custom routed event using the bubble routing strategy.
    public static readonly RoutedEvent CleanEvent = EventManager.RegisterRoutedEvent(
        "Clean", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AquariumFilter));

    // Provide an add handler accessor method for the Clean event.
    public static void AddCleanHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
    {
        if (dependencyObject is not UIElement uiElement)
            return;

        uiElement.AddHandler(CleanEvent, handler);
    }

    // Provide a remove handler accessor method for the Clean event.
    public static void RemoveCleanHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
    {
        if (dependencyObject is not UIElement uiElement)
            return;

        uiElement.RemoveHandler(CleanEvent, handler);
    }
}
Public Class AquariumFilter

    ' Register a custom routed event using the bubble routing strategy.
    Public Shared ReadOnly CleanEvent As RoutedEvent = EventManager.RegisterRoutedEvent(
        "Clean", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(AquariumFilter))

    ' Provide an add handler accessor method for the Clean event.
    Public Shared Sub AddCleanHandler(dependencyObject As DependencyObject, handler As RoutedEventHandler)
        Dim uiElement As UIElement = TryCast(dependencyObject, UIElement)

        If uiElement IsNot Nothing Then
            uiElement.[AddHandler](CleanEvent, handler)
        End If
    End Sub

    ' Provide a remove handler accessor method for the Clean event.
    Public Shared Sub RemoveCleanHandler(dependencyObject As DependencyObject, handler As RoutedEventHandler)
        Dim uiElement As UIElement = TryCast(dependencyObject, UIElement)

        If uiElement IsNot Nothing Then
            uiElement.[RemoveHandler](CleanEvent, handler)
        End If
    End Sub

End Class

Метод RegisterRoutedEvent, возвращающий присоединенный идентификатор события, является тем же методом, используемым для регистрации несоединяемых перенаправленных событий. Подключенные и не подключенные маршрутируемые события регистрируются в централизованном внутреннем хранилище. Эта реализация хранилища событий поддерживает концепцию "события в качестве интерфейса", которая обсуждается в обзоре маршрутизации событий.

В отличие от "обертки" события CLR, используемой в обратных не присоединённых маршрутизируемых событиях, методы доступа к присоединённым событиям можно реализовать в классах, которые не наследуются от UIElement или ContentElement. Это возможно, так как присоединенный код резервного копирования событий вызывает методы UIElement.AddHandler и UIElement.RemoveHandler в переданном экземпляре UIElement. В отличие от этого, оболочка CLR для несвязанных маршрутизируемых событий вызывает эти методы непосредственно в классе-владельце, поэтому такой класс должен быть производным от UIElement.

Вызов присоединенного события WPF

Процесс вызова присоединенного события по сути такой же, как и процесс вызова не присоединенного маршрутизируемого события.

Как правило, код не должен вызывать существующие события, определенные WPF, так как эти события соответствуют общей концептуальной модели "service". В этой модели классы сервисов, такие как InputManager, отвечают за вызов событий, определенных WPF.

При определении настраиваемого присоединенного события с использованием модели WPF, в которой присоединенные события основываются на маршрутизированных событиях, используйте метод UIElement.RaiseEvent для поднятия присоединенного события на любом UIElement или ContentElement. При инициировании маршрутизированного события, независимо от того, присоединено оно или нет, необходимо назначить элемент в дереве элементов в качестве источника события. Затем этот источник идентифицируется как вызывающая сторона RaiseEvent. Например, чтобы вызвать присоединенное AquariumFilter.Clean маршрутизированное событие в aquarium1:

aquarium1.RaiseEvent(new RoutedEventArgs(AquariumFilter.CleanEvent));
aquarium1.[RaiseEvent](New RoutedEventArgs(AquariumFilter.CleanEvent))

В предыдущем примере aquarium1 является источником событий.

См. также