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


Маркировка перенаправленных событий как обработанных и обработка классов (WPF .NET)

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

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

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

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

Когда следует пометить перенаправленные события как обработанные

Как правило, только один обработчик должен предоставлять значительный ответ для каждого перенаправленного события. Избегайте использования перенаправленной системы событий, чтобы обеспечить значительный ответ между несколькими обработчиками. Определение того, что представляет собой значительный ответ, является субъективным и зависит от приложения. В качестве общего руководства:

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

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

Чтобы пометить событие как обработанное, задайте в данных события значение свойства Handled на true. Хотя это значение можно вернуть к false, но такая необходимость возникает редко.

Предварительный просмотр и всплывающих маршрутизируемых пар событий

предварительный просмотр и пары перенаправленных событий, относящиеся к событиям ввода. Несколько входных событий реализуют пару маршрутизируемых событий: туннелирование и всплытие, например, PreviewKeyDown и KeyDown. Префикс Preview означает, что событие пузырьков запускается после завершения события предварительной версии. Каждая пара событий предварительного просмотра и всплывающего события использует один и тот же экземпляр данных события.

Обработчики маршрутизируемых событий вызываются в порядке, соответствующем стратегии маршрутизации события.

  1. Предварительное событие движется от корневого элемента приложения вниз к элементу, который вызвал маршрутизированное событие. Обработчики событий предварительного просмотра, присоединенные к корневому элементу приложения, вызываются сначала, а затем обработчики, подключенные к последовательным вложенным элементам.
  2. После завершения события предпросмотра сочетанное событие перемещается из элемента, вызвавшего маршрутизированное событие, в корневой элемент приложения. Обработчики всплытия событий, присоединенные к тому же элементу, который вызвал маршрутизируемое событие, вызываются сначала, а затем обработчики, подключенные к последовательным родительским элементам.

Парные события предварительного просмотра и всплытия являются частью внутренней реализации нескольких классов WPF, которые объявляют и вызывают собственные маршрутизированные события. Без внутренней реализации на уровне класса события предварительного просмотра и всплывающие события полностью разделены и не будут совместно использовать данные событий, независимо от их именования. Сведения о том, как реализовать распространение или туннелирование входных маршрутизируемых событий в пользовательском классе, см. в разделе Создание пользовательского маршрутизируемого события.

Так как каждая пара событий предварительного просмотра и всплытия использует один и тот же экземпляр данных событий, если предварительное маршрутизируемое событие помечается как обработанное, то также будет обрабатываться его парное всплывающее событие. Если маршрутизированное событие помечено как обработанное, это не повлияет на связанное предварительное событие, поскольку предварительное событие завершено. Будьте осторожны при маркировке пар событий предварительного просмотра и пузырьков входных событий, которые обрабатываются. Обработанное событие предварительного просмотра входных данных не вызывает обычно зарегистрированных обработчиков событий для оставшейся части маршрута туннелирования, и связанное всплывающее событие не будет поднято. Обработанное всплывающее событие ввода не вызывает обычно зарегистрированные обработчики событий на оставшейся части маршрута всплытия.

Обработчики маршрутизируемых событий экземпляров и классов

Перенаправленные обработчики событий могут быть либо обработчиками экземпляров, либо обработчиками класса. Обработчики классов для данного класса вызываются перед тем, как любой обработчик экземпляра отвечает на то же событие в любом экземпляре этого класса. В связи с этим поведением, когда перенаправленные события помечены как обработанные, они часто помечаются как такие в обработчиках классов. Существует два типа обработчиков классов:

Обработчики событий экземпляра

Обработчики экземпляров можно подключить к объектам или элементам XAML, непосредственно вызвав метод AddHandler. Маршрутизируемые события WPF реализуют оболочку события общего языка выполнения (CLR), которая использует метод AddHandler для подключения обработчиков событий. Так как синтаксис атрибута XAML для присоединения обработчиков событий приводит к вызову оболочки события CLR, даже присоединение обработчиков в XAML сводится к вызову AddHandler. Для обработанных событий:

  • Обработчики, присоединенные с помощью синтаксиса атрибута XAML или общей сигнатуры AddHandler, не вызываются.
  • Вызываются обработчики, подключенные с помощью перегрузки AddHandler(RoutedEvent, Delegate, Boolean) с параметром handledEventsToo, равным true. Эта перегрузка доступна в редких случаях, когда необходимо реагировать на обработанные события. Например, некоторые элементы в дереве элементов помечают событие как обработанное, но другие элементы дальше по маршруту событий должны реагировать на обработанное событие.

В следующем примере XAML добавляется пользовательский элемент управления с именем componentWrapper, который упаковывает TextBox с именем componentTextBoxв StackPanel с именем outerStackPanel. Обработчик событий экземпляра для события PreviewKeyDown присоединен к componentWrapper с помощью синтаксиса атрибута XAML. В результате обработчик экземпляра будет реагировать только на необработанные события туннелирования PreviewKeyDown, вызванные componentTextBox.

<StackPanel Name="outerStackPanel" VerticalAlignment="Center">
    <custom:ComponentWrapper
        x:Name="componentWrapper"
        TextBox.PreviewKeyDown="HandlerInstanceEventInfo"
        HorizontalAlignment="Center">
        <TextBox Name="componentTextBox" Width="200" />
    </custom:ComponentWrapper>
</StackPanel>

Конструктор MainWindow присоединяет обработчик экземпляра для события всплытия KeyDown к componentWrapper, используя перегрузку UIElement.AddHandler(RoutedEvent, Delegate, Boolean) с параметром handledEventsToo, установленным в true. В результате обработчик событий экземпляра будет реагировать как на необработанные, так и обработанные события.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        // Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
        componentWrapper.AddHandler(KeyDownEvent, new RoutedEventHandler(Handler.InstanceEventInfo),
            handledEventsToo: true);
    }

    // The handler attached to componentWrapper in XAML.
    public void HandlerInstanceEventInfo(object sender, KeyEventArgs e) => 
        Handler.InstanceEventInfo(sender, e);
}
Partial Public Class MainWindow
    Inherits Window

    Public Sub New()
        InitializeComponent()

        ' Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
        componentWrapper.[AddHandler](KeyDownEvent, New RoutedEventHandler(AddressOf InstanceEventInfo),
                                      handledEventsToo:=True)
    End Sub

    ' The handler attached to componentWrapper in XAML.
    Public Sub HandlerInstanceEventInfo(sender As Object, e As KeyEventArgs)
        InstanceEventInfo(sender, e)
    End Sub

End Class

Реализация ComponentWrapper кода показана в следующем разделе.

Обработчики событий статического класса

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

Обработчики событий статического класса, зарегистрированные с помощью перегрузки RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) с параметром handledEventsToo, равным true, будут реагировать как на необработанные, так и обработанные маршрутизованные события.

Обработчики статических классов обычно регистрируются для реагирования только на необработанные события. В этом случае, если обработчик производного класса на узле помечает событие как обработанное, обработчики базового класса для этого события не будут вызываться. В этом сценарии обработчик базового класса фактически заменяется обработчиком производных классов. Обработчики базового класса часто способствуют проектированию элементов управления в таких областях, как внешний вид, логика состояния, обработка входных данных и обработка команд, поэтому следите за их заменой. Производные обработчики классов, которые не помечают событие как обработанное, дополняют обработчики базового класса вместо замены их.

В следующем примере кода показана иерархия классов для пользовательского элемента управления ComponentWrapper, на который ссылается предыдущий КОД XAML. Класс ComponentWrapper является производным от класса ComponentWrapperBase, который, в свою очередь, является производным от класса StackPanel. Метод RegisterClassHandler, используемый в статическом конструкторе классов ComponentWrapper и ComponentWrapperBase, регистрирует обработчик событий статического класса для каждого из этих классов. Система событий WPF вызывает обработчик статических классов ComponentWrapper перед обработчиком статических классов ComponentWrapperBase.

public class ComponentWrapper : ComponentWrapperBase
{
    static ComponentWrapper()
    {
        // Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(typeof(ComponentWrapper), KeyDownEvent, 
            new RoutedEventHandler(Handler.ClassEventInfo_Static));
    }

    // Class event handler that overrides a base class virtual method.
    protected override void OnKeyDown(KeyEventArgs e)
    {
        Handler.ClassEventInfo_Override(this, e);

        // Call the base OnKeyDown implementation on ComponentWrapperBase.
        base.OnKeyDown(e);
    }
}

public class ComponentWrapperBase : StackPanel
{
    // Class event handler implemented in the static constructor.
    static ComponentWrapperBase()
    {
        EventManager.RegisterClassHandler(typeof(ComponentWrapperBase), KeyDownEvent, 
            new RoutedEventHandler(Handler.ClassEventInfoBase_Static));
    }

    // Class event handler that overrides a base class virtual method.
    protected override void OnKeyDown(KeyEventArgs e)
    {
        Handler.ClassEventInfoBase_Override(this, e);

        e.Handled = true;
        Debug.WriteLine("The KeyDown routed event is marked as handled.");

        // Call the base OnKeyDown implementation on StackPanel.
        base.OnKeyDown(e);
    }
}
Public Class ComponentWrapper
    Inherits ComponentWrapperBase

    Shared Sub New()
        ' Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(GetType(ComponentWrapper), KeyDownEvent,
                                          New RoutedEventHandler(AddressOf ClassEventInfo_Static))
    End Sub

    ' Class event handler that overrides a base class virtual method.
    Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
        ClassEventInfo_Override(Me, e)

        ' Call the base OnKeyDown implementation on ComponentWrapperBase.
        MyBase.OnKeyDown(e)
    End Sub

End Class

Public Class ComponentWrapperBase
    Inherits StackPanel

    Shared Sub New()
        ' Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(GetType(ComponentWrapperBase), KeyDownEvent,
                                          New RoutedEventHandler(AddressOf ClassEventInfoBase_Static))
    End Sub

    ' Class event handler that overrides a base class virtual method.
    Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
        ClassEventInfoBase_Override(Me, e)

        e.Handled = True
        Debug.WriteLine("The KeyDown event is marked as handled.")

        ' Call the base OnKeyDown implementation on StackPanel.
        MyBase.OnKeyDown(e)
    End Sub

End Class

Код-закадровая реализация обработчиков событий для элементов переопределения класса в этом примере кода рассматривается в следующем разделе.

Переопределите обработчики событий класса.

Некоторые базовые классы визуальных элементов предоставляют пустые On<имя события> и OnPreview<имя события> виртуальные методы для каждого из своих общедоступных маршрутизируемых входных событий. Например, UIElement реализует виртуальные обработчики событий OnKeyDown и OnPreviewKeyDown, а также многие другие. Можно переопределить обработчики виртуальных событий базового класса, чтобы реализовать обработчики событий класса переопределения для производных классов. Например, можно добавить обработчик класса, переопределяющий событие DragEnter в любом производном классе UIElement, через переопределение виртуального метода OnDragEnter. Переопределение виртуальных методов базового класса — это более простой способ реализации обработчиков классов, чем регистрация обработчиков классов в статическом конструкторе. В переопределении можно вызвать события, инициировать логику, специфичную для класса, чтобы изменить свойства элементов в экземплярах, пометить событие как обработанное или выполнить другую логику обработки событий.

В отличие от обработчиков событий статического класса, система событий WPF вызывает переопределённые обработчики событий только для наиболее производного класса в иерархии классов. Затем наиболее производный класс в иерархии классов может использовать ключевое слово base для вызова базовой реализации виртуального метода. В большинстве случаев следует вызывать базовую реализацию, независимо от того, помечаете ли вы событие как обработанное. Не следует вызывать базовую реализацию, если класс имеет требование заменить логику базовой реализации, если таковой имеется. Вызывается ли базовая реализация до или после переопределения кода, зависит от характера реализации.

В предыдущем примере кода виртуальный метод базового класса OnKeyDown переопределяется в классах ComponentWrapper и ComponentWrapperBase. Поскольку система событий WPF вызывает только обработчик событий переопределённого класса ComponentWrapper.OnKeyDown, этот обработчик использует base.OnKeyDown(e) для вызова обработчика событий переопределённого класса ComponentWrapperBase.OnKeyDown, который, в свою очередь, использует base.OnKeyDown(e) для вызова виртуального метода StackPanel.OnKeyDown. Порядок событий в предыдущем примере кода:

  1. Обработчик экземпляра, подключенный к componentWrapper, активируется маршрутизированным событием PreviewKeyDown.
  2. Обработчик статического класса, подключенный к componentWrapper, активируется событием маршрутизации KeyDown.
  3. Обработчик статического класса, подключенный к componentWrapperBase, активируется событием маршрутизации KeyDown.
  4. Обработчик переопределения класса, подключенный к componentWrapper, активируется маршрутизируемым событием KeyDown.
  5. Обработчик переопределённого класса, подключенный к componentWrapperBase, активируется маршрутизируемым событием KeyDown.
  6. Перенаправленное событие KeyDown помечается как обработанное.
  7. Обработчик экземпляра, подключенный к componentWrapper, активируется KeyDown маршрутизируемым событием. Обработчик был зарегистрирован с параметром handledEventsToo, установленным на true.

Подавление событий ввода в составных элементах управления

Некоторые составные элементы управления подавляют события ввода на уровне компонента, чтобы заменить их настраиваемым событием высокого уровня, которое содержит дополнительные сведения или подразумевает более конкретное поведение. Составной элемент управления определяется несколькими практическими элементами управления или базовыми классами. Классическим примером является элемент управления Button, который преобразует различные события мыши в маршрутизированное событие Click. Базовый класс Button является ButtonBase, который косвенно является производным от UIElement. Большая часть инфраструктуры событий, необходимой для обработки входных данных управления, доступна на уровне UIElement. UIElement демонстрирует несколько Mouse событий, таких как MouseLeftButtonDown и MouseRightButtonDown. UIElement также реализует пустые виртуальные методы OnMouseLeftButtonDown и OnMouseRightButtonDown в качестве предварительно зарегистрированных обработчиков классов. ButtonBase переопределяет эти обработчики классов, а при переопределении устанавливает свойство Handled в true и вызывает событие Click. Конечным результатом для большинства прослушивателей является скрытие событий MouseLeftButtonDown и MouseRightButtonDown, а событие высокого уровня Click отображается.

Работа над подавлением входных событий

Иногда подавление событий в отдельных элементах управления может повлиять на логику обработки событий в приложении. Например, если приложение использовал синтаксис атрибута XAML для подключения обработчика для события MouseLeftButtonDown в корневом элементе XAML, этот обработчик не будет вызван, так как элемент управления Button помечает событие MouseLeftButtonDown как обработанное. Если вы хотите, чтобы элементы ближе к корню вашего приложения вызывались для обработанного маршрутизируемого события, вы можете:

  • Присоедините обработчики, вызвав метод UIElement.AddHandler(RoutedEvent, Delegate, Boolean) с параметром handledEventsToo, установленным в значение true. Этот подход требует привязки обработчика событий в коде за кулисами после получения ссылки на объект для элемента, к которому он будет присоединен.

  • Если событие, помеченное как обработанное, является всплывающим событием ввода, подключите обработчики для парного события предварительного просмотра, если они есть. Например, если элемент управления подавляет событие MouseLeftButtonDown, можно подключить обработчик для события PreviewMouseLeftButtonDown. Этот подход работает только для пар событий предварительного просмотра и всплывающих входных событий, которые совместно используют данные о событиях. Будьте осторожны, чтобы не промаркировать PreviewMouseLeftButtonDown как обработанное, так как это полностью подавит событие Click.

Пример того, как обойти подавление входных событий, см. в разделе Работа над подавлением событий элементами управления.

См. также