Маркировка перенаправленных событий как обработанных и обработка классов (WPF .NET)
Хотя нет абсолютного правила, когда следует пометить перенаправленное событие как обработанное, рекомендуется пометить событие как обработанное, если код реагирует на событие значительным образом. Перенаправленное событие, помеченное как обработанное, будет продолжаться по маршруту, но вызываются только обработчики, настроенные для реагирования на обработанные события. В основном помечая перенаправленное событие как обработанное, ограничивает видимость прослушивателей вдоль маршрута событий.
Перенаправленные обработчики событий могут быть обработчиками экземпляров или обработчиками классов. Обработчики экземпляров обрабатывают маршрутизируемые события на объектах или элементах XAML. Обработчики классов обрабатывают перенаправленное событие на уровне класса и вызываются перед тем, как любой обработчик экземпляра отвечает на то же событие в любом экземпляре класса. Когда маршрутизируемые события помечаются как обработанные, это часто делается в обработчиках классов. В этой статье рассматриваются преимущества и потенциальные недостатки маркировки перенаправленных событий, которые обрабатываются, различные типы перенаправленных событий и перенаправленные обработчики событий, а также подавление событий в составных элементах управления.
Необходимые условия
В статье предполагается, что у вас есть базовые знания о маршрутизируемых событиях и что вы прочитали обзор маршрутизируемых событий. Чтобы следовать примерам в этой статье, это поможет вам, если вы знакомы с языком разметки расширяемых приложений (XAML) и узнаете, как писать приложения Windows Presentation Foundation (WPF).
Когда следует пометить перенаправленные события как обработанные
Как правило, только один обработчик должен предоставлять значительный ответ для каждого перенаправленного события. Избегайте использования перенаправленной системы событий, чтобы обеспечить значительный ответ между несколькими обработчиками. Определение того, что представляет собой значительный ответ, является субъективным и зависит от приложения. В качестве общего руководства:
- Важные ответы включают установку фокуса, изменение общедоступного состояния, настройку свойств, влияющих на визуальное представление, создание новых событий и полную обработку события.
- Незначительные ответы включают изменение частного состояния без визуального или программного влияния, ведение журнала событий и изучение данных событий без реагирования на событие.
Некоторые элементы управления WPF подавляют события уровня компонентов, которые не нуждаются в дальнейшей обработке, помечая их как обработанные. Если вы хотите обработать событие, которое было отмечено как обработанное элементом управления, см. как обойти подавление событий элементами управления.
Чтобы пометить событие как обработанное, задайте в данных события значение свойства Handled на true
. Хотя это значение можно вернуть к false
, но такая необходимость возникает редко.
Предварительный просмотр и всплывающих маршрутизируемых пар событий
предварительный просмотр и пары перенаправленных событий, относящиеся к событиям ввода. Несколько входных событий реализуют пару маршрутизируемых событий: туннелирование и всплытие, например, PreviewKeyDown и KeyDown. Префикс Preview
означает, что событие пузырьков запускается после завершения события предварительной версии. Каждая пара событий предварительного просмотра и всплывающего события использует один и тот же экземпляр данных события.
Обработчики маршрутизируемых событий вызываются в порядке, соответствующем стратегии маршрутизации события.
- Предварительное событие движется от корневого элемента приложения вниз к элементу, который вызвал маршрутизированное событие. Обработчики событий предварительного просмотра, присоединенные к корневому элементу приложения, вызываются сначала, а затем обработчики, подключенные к последовательным вложенным элементам.
- После завершения события предпросмотра сочетанное событие перемещается из элемента, вызвавшего маршрутизированное событие, в корневой элемент приложения. Обработчики всплытия событий, присоединенные к тому же элементу, который вызвал маршрутизируемое событие, вызываются сначала, а затем обработчики, подключенные к последовательным родительским элементам.
Парные события предварительного просмотра и всплытия являются частью внутренней реализации нескольких классов WPF, которые объявляют и вызывают собственные маршрутизированные события. Без внутренней реализации на уровне класса события предварительного просмотра и всплывающие события полностью разделены и не будут совместно использовать данные событий, независимо от их именования. Сведения о том, как реализовать распространение или туннелирование входных маршрутизируемых событий в пользовательском классе, см. в разделе Создание пользовательского маршрутизируемого события.
Так как каждая пара событий предварительного просмотра и всплытия использует один и тот же экземпляр данных событий, если предварительное маршрутизируемое событие помечается как обработанное, то также будет обрабатываться его парное всплывающее событие. Если маршрутизированное событие помечено как обработанное, это не повлияет на связанное предварительное событие, поскольку предварительное событие завершено. Будьте осторожны при маркировке пар событий предварительного просмотра и пузырьков входных событий, которые обрабатываются. Обработанное событие предварительного просмотра входных данных не вызывает обычно зарегистрированных обработчиков событий для оставшейся части маршрута туннелирования, и связанное всплывающее событие не будет поднято. Обработанное всплывающее событие ввода не вызывает обычно зарегистрированные обработчики событий на оставшейся части маршрута всплытия.
Обработчики маршрутизируемых событий экземпляров и классов
Перенаправленные обработчики событий могут быть либо обработчиками экземпляров, либо обработчиками класса. Обработчики классов для данного класса вызываются перед тем, как любой обработчик экземпляра отвечает на то же событие в любом экземпляре этого класса. В связи с этим поведением, когда перенаправленные события помечены как обработанные, они часто помечаются как такие в обработчиках классов. Существует два типа обработчиков классов:
- обработчики событий статического класса, зарегистрированные путем вызова метода RegisterClassHandler в конструкторе статических классов.
-
обработчики событий класса Override, зарегистрированные путем переопределения методов виртуального события базового класса. Методы виртуального события базового класса в основном существуют для входных событий и имеют имена, начинающиеся с
имени события On и OnPreview имени события.
Обработчики событий экземпляра
Обработчики экземпляров можно подключить к объектам или элементам 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
. Порядок событий в предыдущем примере кода:
- Обработчик экземпляра, подключенный к
componentWrapper
, активируется маршрутизированным событиемPreviewKeyDown
. - Обработчик статического класса, подключенный к
componentWrapper
, активируется событием маршрутизацииKeyDown
. - Обработчик статического класса, подключенный к
componentWrapperBase
, активируется событием маршрутизацииKeyDown
. - Обработчик переопределения класса, подключенный к
componentWrapper
, активируется маршрутизируемым событиемKeyDown
. - Обработчик переопределённого класса, подключенный к
componentWrapperBase
, активируется маршрутизируемым событиемKeyDown
. - Перенаправленное событие
KeyDown
помечается как обработанное. - Обработчик экземпляра, подключенный к
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.
Пример того, как обойти подавление входных событий, см. в разделе Работа над подавлением событий элементами управления.
См. также
- EventManager
- предварительные события
- Создание настраиваемого маршрутизируемого события
- Обзор маршрутизируемых событий
.NET Desktop feedback