路由事件概述Routed Events Overview

本主题描述 Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) 中路由事件的概念。This topic describes the concept of routed events in Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF). 本主题定义路由事件术语、描述路由事件如何通过元素树来路由、概述如何处理路由事件,并介绍如何创建你自己的自定义路由事件。The topic defines routed events terminology, describes how routed events are routed through a tree of elements, summarizes how you handle routed events, and introduces how to create your own custom routed events.

系统必备Prerequisites

本主题假定您具有公共语言运行时 (CLR) 和面向对象的编程的基本知识, 以及如何将元素之间WPFWPF的关系概念化为树的概念。This topic assumes that you have basic knowledge of the common language runtime (CLR) and object-oriented programming, as well as the concept of how the relationships between WPFWPF elements can be conceptualized as a tree. 若要理解本主题中的示例,你还应当了解 可扩展应用程序标记语言 (XAML)Extensible Application Markup Language (XAML) 并知道如何编写非常基本的 WPFWPF 应用程序或页。In order to follow the examples in this topic, you should also understand 可扩展应用程序标记语言 (XAML)Extensible Application Markup Language (XAML) and know how to write very basic WPFWPF applications or pages. 有关详细信息,请参见演练:我的第一个 WPF桌面应用程序和XAML 概述 (WPF)For more information, see Walkthrough: My first WPF desktop application and XAML Overview (WPF).

什么是路由事件?What Is a Routed Event?

可以从功能或实现的角度来理解路由事件。You can think about routed events either from a functional or implementation perspective. 此处对这两种定义均进行了说明,因为有的用户认为前者更有用,有的用户认为后者更有用。Both definitions are presented here, because some people find one or the other definition more useful.

功能定义:路由事件是一种事件类型, 可以在元素树中的多个侦听器上调用处理程序, 而不只是在引发事件的对象上调用。Functional definition: A routed event is a type of event that can invoke handlers on multiple listeners in an element tree, rather than just on the object that raised the event.

实现定义:路由事件是由RoutedEvent类的实例支持的 CLR 事件, 由Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF)事件系统进行处理。Implementation definition: A routed event is a CLR event that is backed by an instance of the RoutedEvent class and is processed by the Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) event system.

典型的 WPFWPF 应用程序中包含许多元素。A typical WPFWPF application contains many elements. 无论这些元素是在代码中创建还是在 XAMLXAML 中声明,它们存在于彼此关联的元素树关系中。Whether created in code or declared in XAMLXAML, these elements exist in an element tree relationship to each other. 根据事件的定义,事件路由可以按两种方向之一传播,但是通常会在元素树中从源元素向上“浮升”,直到它到达元素树的根(通常是页面或窗口)。The event route can travel in one of two directions depending on the event definition, but generally the route travels from the source element and then "bubbles" upward through the element tree until it reaches the element tree root (typically a page or a window). 如果你以前用过 DHTML 对象模型,则可能会熟悉这个浮升概念。This bubbling concept might be familiar to you if you have worked with the DHTML object model previously.

请思考下面的简单元素树:Consider the following simple element tree:

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

此元素树生成类似如下的内容:This element tree produces something like the following:

“是”、“否”和“取消”按钮Yes, No, and Cancel buttons

在这个简化的Click Button元素树中, 事件的源是一个元素, 而Button单击的是有机会处理事件的第一个元素。In this simplified element tree, the source of a Click event is one of the Button elements, and whichever Button was clicked is the first element that has the opportunity to handle the event. 但如果附加到的Button处理程序对事件不起作用, 则该事件将向上冒泡Button到元素StackPanel树中的父级 (即)。But if no handler attached to the Button acts on the event, then the event will bubble upwards to the Button parent in the element tree, which is the StackPanel. 事件可能会冒泡到Border, 然后再冒泡到元素树的页根 (未显示)。Potentially, the event bubbles to Border, and then beyond to the page root of the element tree (not shown).

换句话说, 此Click事件的事件路由是:In other words, the event route for this Click event is:

Button-->StackPanel-->Border-->...Button-->StackPanel-->Border-->...

路由事件的顶级方案Top-level Scenarios for Routed Events

下面简要概述了引发路由事件的方案, 以及为什么典型的 CLR 事件对于这些方案来说是不够的:The following is a brief summary of the scenarios that motivated the routed event concept, and why a typical CLR event was not adequate for these scenarios:

控件组合和封装:WPFWPF的各种控件都具有丰富的内容模型。Control composition and encapsulation: Various controls in WPFWPF have a rich content model. 例如, 可以将图像置于内Button, 这会有效地扩展按钮的可视化树。For example, you can place an image inside of a Button, which effectively extends the visual tree of the button. 但是, 添加的图像不得中断导致按钮响应Click其内容的命中测试行为, 即使用户单击了在技术上包含图像的部分像素也是如此。However, the added image must not break the hit-testing behavior that causes a button to respond to a Click of its content, even if the user clicks on pixels that are technically part of the image.

单一处理程序附件点:Windows 窗体Windows Forms中, 您需要多次附加相同的处理程序, 以处理可能从多个元素引发的事件。Singular handler attachment points: In Windows 窗体Windows Forms, you would have to attach the same handler multiple times to process events that could be raised from multiple elements. 借助路由事件,可以只附加该处理程序一次(如上例中所示),并在必要时使用处理程序逻辑来确定该事件的源位置。Routed events enable you to attach that handler only once, as was shown in the previous example, and use handler logic to determine where the event came from if necessary. 例如,这可以是前面显示的 XAMLXAML 的处理程序:For instance, this might be the handler for the previously shown XAMLXAML:

private void CommonClickHandler(object sender, RoutedEventArgs e)
{
  FrameworkElement feSource = e.Source as FrameworkElement;
  switch (feSource.Name)
  {
    case "YesButton":
      // do something here ...
      break;
    case "NoButton":
      // do something ...
      break;
    case "CancelButton":
      // do something ...
      break;
  }
  e.Handled=true;
}
Private Sub CommonClickHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
  Dim feSource As FrameworkElement = TryCast(e.Source, FrameworkElement)
  Select Case feSource.Name
    Case "YesButton"
      ' do something here ...
    Case "NoButton"
      ' do something ...
    Case "CancelButton"
      ' do something ...
  End Select
  e.Handled=True
End Sub

类处理: 路由事件允许由类定义的静态处理程序。Class handling: Routed events permit a static handler that is defined by the class. 此类处理程序能够抢在任何附加的实例处理程序之前处理事件。This class handler has the opportunity to handle an event before any attached instance handlers can.

引用没有反射的事件: 某些代码和标记技术需要一种方法来识别特定的事件。Referencing an event without reflection: Certain code and markup techniques require a way to identify a specific event. 路由事件将RoutedEvent字段作为标识符创建, 这提供了一种可靠的事件标识技术, 不需要静态或运行时反射。A routed event creates a RoutedEvent field as an identifier, which provides a robust event identification technique that does not require static or run-time reflection.

路由事件的实现方式How Routed Events Are Implemented

路由事件是由RoutedEvent类的实例提供支持并向WPFWPF事件系统注册的 CLR 事件。A routed event is a CLR event that is backed by an instance of the RoutedEvent class and registered with the WPFWPF event system. RoutedEvent注册获得的实例通常public static作为类的字段成员保留,此类成员注册并因此"拥有"路由事件。readonlyThe RoutedEvent instance obtained from registration is typically retained as a public static readonly field member of the class that registers and thus "owns" the routed event. 与名称相同的 clr 事件 (有时称为 "包装" 事件) 的连接是通过重写 CLR 事件的addremove实现来完成的。The connection to the identically named CLR event (which is sometimes termed the "wrapper" event) is accomplished by overriding the add and remove implementations for the CLR event. 通常,addremove 保留为隐式默认值,该默认值使用特定于语言的相应事件语法来添加和删除该事件的处理程序。Ordinarily, the add and remove are left as an implicit default that uses the appropriate language-specific event syntax for adding and removing handlers of that event. 路由事件支持和连接机制在概念上类似于依赖属性是由DependencyProperty类支持并向WPFWPF属性系统注册的 CLR 属性。The routed event backing and connection mechanism is conceptually similar to how a dependency property is a CLR property that is backed by the DependencyProperty class and registered with the WPFWPF property system.

下面的示例演示自定义Tap路由事件的声明, 其中包括RoutedEvent标识符字段的注册和公开以及Tap CLR 事件的addremove实现。The following example shows the declaration for a custom Tap routed event, including the registration and exposure of the RoutedEvent identifier field and the add and remove implementations for the Tap CLR event.

public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
    "Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));

// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
        add { AddHandler(TapEvent, value); } 
        remove { RemoveHandler(TapEvent, value); }
}
Public Shared ReadOnly TapEvent As RoutedEvent = EventManager.RegisterRoutedEvent("Tap", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(MyButtonSimple))

' Provide CLR accessors for the event
Public Custom Event Tap As RoutedEventHandler
    AddHandler(ByVal value As RoutedEventHandler)
        Me.AddHandler(TapEvent, value)
    End AddHandler

    RemoveHandler(ByVal value As RoutedEventHandler)
        Me.RemoveHandler(TapEvent, value)
    End RemoveHandler

    RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Me.RaiseEvent(e)
    End RaiseEvent
End Event

路由事件处理程序和 XAMLRouted Event Handlers and XAML

若要使用 XAMLXAML 为事件添加处理程序,可将该事件的名称声明为用作事件侦听器的元素的属性。To add a handler for an event using XAMLXAML, you declare the event name as an attribute on the element that is an event listener. 该属性的值是所实现的处理程序方法的名称,该方法必须存在于代码隐藏文件的分部类中。The value of the attribute is the name of your implemented handler method, which must exist in the partial class of the code-behind file.

<Button Click="b1SetColor">button</Button>

添加路由事件处理程序的语法与添加路由事件处理程序的语法相同,因为您实际上是将处理程序添加到CLR事件包装器,后者在下面具有路由事件实现。XAMLXAMLThe XAMLXAML syntax for adding standard CLR event handlers is the same for adding routed event handlers, because you are really adding handlers to the CLR event wrapper, which has a routed event implementation underneath. 有关在 XAMLXAML 中添加事件处理程序的详细信息,请参阅 XAML 概述 (WPF)For more information about adding event handlers in XAMLXAML, see XAML Overview (WPF).

路由策略Routing Strategies

路由事件使用以下三种路由策略之一:Routed events use one of three routing strategies:

  • 冒泡事件源上的事件处理程序被调用。Bubbling: Event handlers on the event source are invoked. 路由事件随后会路由到后续的父级元素,直到到达元素树的根。The routed event then routes to successive parent elements until reaching the element tree root. 大多数路由事件都使用浮升路由策略。Most routed events use the bubbling routing strategy. 浮升路由事件通常用于报告来自不同控件或其他 UI 元素的输入或状态变化。Bubbling routed events are generally used to report input or state changes from distinct controls or other UI elements.

  • 只有源元素本身有机会调用处理程序以响应。Direct: Only the source element itself is given the opportunity to invoke handlers in response. 这与 Windows 窗体Windows Forms 用于事件的“路由”相似。This is analogous to the "routing" that Windows 窗体Windows Forms uses for events. 但是, 与标准 CLR 事件不同, 直接路由事件支持类处理 (在下一节中介绍类处理), 并可由EventSetterEventTrigger使用。However, unlike a standard CLR event, direct routed events support class handling (class handling is explained in an upcoming section) and can be used by EventSetter and EventTrigger.

  • 建立最初, 将调用元素树根处的事件处理程序。Tunneling: Initially, event handlers at the element tree root are invoked. 随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。The routed event then travels a route through successive child elements along the route, towards the node element that is the routed event source (the element that raised the routed event). 合成控件的过程中通常会使用或处理隧道路由事件,通过这种方式,可以有意地禁止复合部件中的事件,或者将其替换为特定于整个控件的事件。Tunneling routed events are often used or handled as part of the compositing for a control, such that events from composite parts can be deliberately suppressed or replaced by events that are specific to the complete control. WPFWPF 中提供的输入事件通常是以隧道/浮升对实现的。Input events provided in WPFWPF often come implemented as a tunneling/bubbling pair. 隧道事件有时又称作预览事件,这是由该对所使用的命名约定决定的。Tunneling events are also sometimes referred to as Preview events, because of a naming convention that is used for the pairs.

为什么使用路由事件?Why Use Routed Events?

作为应用程序开发人员,你不需要始终了解或关注要处理的事件是否作为路由事件实现。As an application developer, you do not always need to know or care that the event you are handling is implemented as a routed event. 路由事件具有特殊的行为,但是,如果在引发该行为的元素上处理事件,则该行为通常会不可见。Routed events have special behavior, but that behavior is largely invisible if you are handling an event on the element where it is raised.

如果使用以下任一建议方案,路由事件的功能将得到充分发挥:在公用根处定义公用处理程序、合成自己的控件或者定义自己的自定义控件类。Where routed events become powerful is if you use any of the suggested scenarios: defining common handlers at a common root, compositing your own control, or defining your own custom control class.

路由事件侦听器和路由事件源不必在其层次结构中共享公用事件。Routed event listeners and routed event sources do not need to share a common event in their hierarchy. Any UIElementContentElement可以是任何路由事件的事件侦听器。Any UIElement or ContentElement can be an event listener for any routed event. 因此, 可以将整个工作 API 集中可用的完整路由事件集用作概念 "接口", 应用程序中的不同元素可以交换事件信息。Therefore, you can use the full set of routed events available throughout the working API set as a conceptual "interface" whereby disparate elements in the application can exchange event information. 路由事件的这个“接口”概念特别适用于输入事件。This "interface" concept for routed events is particularly applicable for input events.

路由事件还可以用于通过元素树进行通信,因为事件的事件数据会保留到路由中的每个元素中。Routed events can also be used to communicate through the element tree, because the event data for the event is perpetuated to each element in the route. 一个元素可以更改事件数据中的某些内容,该更改将用于路由中的下一个元素。One element could change something in the event data, and that change would be available to the next element in the route.

除了路由方面以外, 还有两个原因, 任何给定WPFWPF事件都可能作为路由事件实现, 而不是作为标准 CLR 事件实现。Other than the routing aspect, there are two other reasons that any given WPFWPF event might be implemented as a routed event instead of a standard CLR event. 如果要实现自己的事件,则可能也需要考虑这些原则:If you are implementing your own events, you might also consider these principles:

  • 某些WPFWPF样式和模板化功能EventSetter (如EventTrigger和) 要求引用的事件是路由事件。Certain WPFWPF styling and templating features such as EventSetter and EventTrigger require the referenced event to be a routed event. 前面提到的事件标识符方案就是这样的。This is the event identifier scenario mentioned earlier.

  • 路由事件支持类处理机制,类可以通过该机制来指定静态方法,这些静态方法能够在任何已注册的实例处理程序访问路由事件之前,处理这些路由事件。Routed events support a class handling mechanism whereby the class can specify static methods that have the opportunity to handle routed events before any registered instance handlers can access them. 这在控件设计中非常有用,因为类可以强制执行事件驱动的类行为,以防它们在处理实例上的事件时被意外禁止。This is very useful in control design, because your class can enforce event-driven class behaviors that cannot be accidentally suppressed by handling an event on an instance.

本主题将用单独的章节来讨论上述每个因素。Each of the above considerations is discussed in a separate section of this topic.

为路由事件添加和实现事件处理程序Adding and Implementing an Event Handler for a Routed Event

若要在 XAMLXAML 中添加事件处理程序,只需将事件名称作为属性添加到元素中,并将属性值设置为用来实现相应委托的事件处理程序的名称,如下面的示例中所示。To add an event handler in XAMLXAML, you simply add the event name to an element as an attribute and set the attribute value as the name of the event handler that implements an appropriate delegate, as in the following example.

<Button Click="b1SetColor">button</Button>

b1SetColor是所实现的处理程序的名称, 其中包含处理Click事件的代码。b1SetColor is the name of the implemented handler that contains the code that handles the Click event. b1SetColor必须具有与RoutedEventHandler委托相同的签名, 这是Click事件的事件处理程序委托。b1SetColor must have the same signature as the RoutedEventHandler delegate, which is the event handler delegate for the Click event. 所有路由事件处理程序委托的第一个参数都指定要向其中添加事件处理程序的元素,第二个参数指定事件的数据。The first parameter of all routed event handler delegates specifies the element to which the event handler is added, and the second parameter specifies the data for the event.

void b1SetColor(object sender, RoutedEventArgs args)
{
  //logic to handle the Click event
}
Private Sub b1SetColor(ByVal sender As Object, ByVal args As RoutedEventArgs)
  'logic to handle the Click event
End Sub

RoutedEventHandler基本路由事件处理程序委托。RoutedEventHandler is the basic routed event handler delegate. 对于针对某些控件或方案的专用路由事件,要用于路由事件处理程序的委托还可能会变得更加专用化,以便可以传输专用的事件数据。For routed events that are specialized for certain controls or scenarios, the delegates to use for the routed event handlers also might become more specialized, so that they can transmit specialized event data. 例如, 在常见的输入方案中, 可以处理DragEnter路由事件。For instance, in a common input scenario, you might handle a DragEnter routed event. 处理程序应实现DragEventHandler委托。Your handler should implement the DragEventHandler delegate. 使用最具体的委托, 可以DragEventArgs在处理程序中处理, 并Data读取属性, 其中包含拖动操作的剪贴板有效负载。By using the most specific delegate, you can process the DragEventArgs in the handler and read the Data property, which contains the clipboard payload of the drag operation.

有关如何使用 XAMLXAML 向元素中添加事件处理程序的完整示例,请参阅处理路由事件For a complete example of how to add an event handler to an element using XAMLXAML, see Handle a Routed Event.

在用代码创建的应用程序中为路由事件添加处理程序非常简单。Adding a handler for a routed event in an application that is created in code is straightforward. 路由事件处理程序始终可以通过 helper 方法AddHandler添加 (与现有的支持add调用相同的方法)。但是,现有的 WPFWPF 路由事件通常借助于支持机制来实现 addremove 逻辑,这些实现允许使用特定于语言的事件语法来添加路由事件的处理程序,特定于语言的事件语法比 Helper 方法更直观。Routed event handlers can always be added through a helper method AddHandler (which is the same method that the existing backing calls for add.) However, existing WPFWPF routed events generally have backing implementations of add and remove logic that allow the handlers for routed events to be added by a language-specific event syntax, which is more intuitive syntax than the helper method. 下面是 Helper 方法的示例用法:The following is an example usage of the helper method:

void MakeButton()
 {
     Button b2 = new Button();
     b2.AddHandler(Button.ClickEvent, new RoutedEventHandler(Onb2Click));
 }
 void Onb2Click(object sender, RoutedEventArgs e)
 {
     //logic to handle the Click event     
 }
Private Sub MakeButton()
     Dim b2 As New Button()
     b2.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf Onb2Click))
End Sub
 Private Sub Onb2Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
     'logic to handle the Click event     
 End Sub

下一个示例显示了C#运算符语法 (Visual Basic 的运算符语法略有不同, 因为它处理取消引用):The next example shows the C# operator syntax (Visual Basic has slightly different operator syntax because of its handling of dereferencing):

void MakeButton2()
{
  Button b2 = new Button();
  b2.Click += new RoutedEventHandler(Onb2Click2);
}
void Onb2Click2(object sender, RoutedEventArgs e)
{
  //logic to handle the Click event     
}
Private Sub MakeButton2()
  Dim b2 As New Button()
  AddHandler b2.Click, AddressOf Onb2Click2
End Sub
Private Sub Onb2Click2(ByVal sender As Object, ByVal e As RoutedEventArgs)
  'logic to handle the Click event     
End Sub

有关如何在代码中添加事件处理程序的示例,请参阅使用代码添加事件处理程序For an example of how to add an event handler in code, see Add an Event Handler Using Code.

如果使用 Visual Basic, 则还可以使用Handles关键字将处理程序添加为处理程序声明的一部分。If you are using Visual Basic, you can also use the Handles keyword to add handlers as part of the handler declarations. 有关详细信息,请参阅 Visual Basic 和 WPF 事件处理For more information, see Visual Basic and WPF Event Handling.

“已处理”概念The Concept of Handled

所有路由事件都共享一个公用事件数据基类RoutedEventArgsAll routed events share a common event data base class, RoutedEventArgs. RoutedEventArgsHandled定义属性, 该属性采用布尔值。RoutedEventArgs defines the Handled property, which takes a Boolean value. Handled属性的目的是使任何事件处理程序沿路由, 将路由事件标记为已处理, 方法是将的Handled值设置为。 trueThe purpose of the Handled property is to enable any event handler along the route to mark the routed event as handled, by setting the value of Handled to true. 处理程序在路由上的某个元素处对共享事件数据进行处理之后,这些数据将再次报告给路由上的每个侦听器。After being processed by the handler at one element along the route, the shared event data is again reported to each listener along the route.

Handled值影响路由事件在沿路由进一步传播时的报告或处理方式。The value of Handled affects how a routed event is reported or processed as it travels further along the route. 如果Handledtrue路由事件的事件数据中的, 则通常不会再为该特定事件实例调用在其他元素上侦听该路由事件的处理程序。If Handled is true in the event data for a routed event, then handlers that listen for that routed event on other elements are generally no longer invoked for that particular event instance. 这条规则对以下两类处理程序均适用:在 XAMLXAML 中附加的处理程序;由特定于语言的事件处理程序附加语法(如 +=Handles)添加的处理程序。This is true both for handlers attached in XAMLXAML and for handlers added by language-specific event handler attachment syntaxes such as += or Handles. 对于大多数常见的处理程序方案, 通过将设置Handled为来true将事件标记为已处理, 将 "停止" 路由用于隧道路由或冒泡路由, 还用于通过类处理程序在路由中的某个点处理的任何事件。For most common handler scenarios, marking an event as handled by setting Handled to true will "stop" routing for either a tunneling route or a bubbling route, and also for any event that is handled at a point in the route by a class handler.

但是, 有一个 "handledEventsToo" 机制, 侦听器仍可以运行处理程序来响应事件数据Handled true中的路由事件。However, there is a "handledEventsToo" mechanism whereby listeners can still run handlers in response to routed events where Handled is true in the event data. 换言之,将事件数据标记为“已处理”并不会真的停止事件路由。In other words, the event route is not truly stopped by marking the event data as handled. 只能在代码中或在中EventSetter使用 handledEventsToo 机制:You can only use the handledEventsToo mechanism in code, or in an EventSetter:

除了Handled状态在路由事件中生成的行为外, 的Handled概念还影响了应如何设计应用程序并编写事件处理程序代码。In addition to the behavior that Handled state produces in routed events, the concept of Handled has implications for how you should design your application and write the event handler code. 您可以将Handled概念化为由路由事件公开的简单协议。You can conceptualize Handled as being a simple protocol that is exposed by routed events. 你确切地了解如何使用此协议, 但如何使用的值Handled的概念设计如下:Exactly how you use this protocol is up to you, but the conceptual design for how the value of Handled is intended to be used is as follows:

  • 如果路由事件标记为“已处理”,则它不必由该路由中的其他元素再次处理。If a routed event is marked as handled, then it does not need to be handled again by other elements along that route.

  • 如果路由事件未标记为已处理, 则之前沿路由的其他侦听器选择不注册处理程序, 或已注册的处理程序选择不操作事件数据并将设置Handled为。 trueIf a routed event is not marked as handled, then other listeners that were earlier along the route have chosen either not to register a handler, or the handlers that were registered chose not to manipulate the event data and set Handled to true. (或者,当前侦听器很可能是路由中的第一个点。)当前侦听器上的处理程序现在有三个可能的操作过程:(Or, it is of course possible that the current listener is the first point in the route.) Handlers on the current listener now have three possible courses of action:

    • 不执行任何操作;该事件保持未处理状态,该事件将路由到下一个侦听器。Take no action at all; the event remains unhandled, and the event routes to the next listener.

    • 执行代码以响应该事件,但是所执行的操作被视为不足以保证将事件标记为“已处理”。Execute code in response to the event, but make the determination that the action taken was not substantial enough to warrant marking the event as handled. 该事件将路由到下一个侦听器。The event routes to the next listener.

    • 执行代码以响应该事件。Execute code in response to the event. 在传递到处理程序的事件数据中将该事件标记为“已处理”,因为所执行的操作被视为不足以保证将该事件标记为“已处理”。Mark the event as handled in the event data passed to the handler, because the action taken was deemed substantial enough to warrant marking as handled. 该事件仍将路由到下一个侦听器, 但Handled = true在其事件数据中, 只有handledEventsToo侦听器才能调用进一步的处理程序。The event still routes to the next listener, but with Handled=true in its event data, so only handledEventsToo listeners have the opportunity to invoke further handlers.

此概念设计是通过前面提到的路由行为加强的: 更难 (尽管仍可能在代码或样式中) 附加处理路由事件的处理程序, 即使路由中的以前处理程序已设置Handled,也会调用该处理程序。到trueThis conceptual design is reinforced by the routing behavior mentioned earlier: it is more difficult (although still possible in code or styles) to attach handlers for routed events that are invoked even if a previous handler along the route has already set Handled to true.

有关路由事件的Handled类处理的详细信息, 以及有关何时适当地将路由事件标记为Handled的建议, 请参阅将路由事件标记为已处理和类处理For more information about Handled, class handling of routed events, and recommendations about when it is appropriate to mark a routed event as Handled, see Marking Routed Events as Handled, and Class Handling.

在应用程序中,相当常见的做法是只针对引发浮升路由事件的对象来处理该事件,而根本不考虑事件的路由特征。In applications, it is quite common to just handle a bubbling routed event on the object that raised it, and not be concerned with the event's routing characteristics at all. 但是,在事件数据中将路由事件标记为“已处理”仍是一个不错的做法,因为这样可以防止元素树中位置更高的元素也对同一个路由事件附加了处理程序而出现意外的副作用。However, it is still a good practice to mark the routed event as handled in the event data, to prevent unanticipated side effects just in case an element that is further up the element tree also has a handler attached for that same routed event.

类处理程序Class Handlers

如果要定义从DependencyObject中派生的类, 则还可以为作为类的声明或继承的事件成员的路由事件定义和附加类处理程序。If you are defining a class that derives in some way from DependencyObject, you can also define and attach a class handler for a routed event that is a declared or inherited event member of your class. 每当路由事件到达其路由中的元素实例时,都会先调用类处理程序,然后再调用附加到该类某个实例的任何实例侦听器处理程序。Class handlers are invoked before any instance listener handlers that are attached to an instance of that class, whenever a routed event reaches an element instance in its route.

有些 WPFWPF 控件对某些路由事件具有固有的类处理。Some WPFWPF controls have inherent class handling for certain routed events. 路由事件可能看起来从未引发过,但实际上正对其进行类处理,如果使用某些技术,路由事件还是可以由实例处理程序进行处理。This might give the outward appearance that the routed event is not ever raised, but in reality it is being class handled, and the routed event can potentially still be handled by your instance handlers if you use certain techniques. 此外,许多基类和控件会公开可用来替代类处理行为的虚拟方法。Also, many base classes and controls expose virtual methods that can be used to override class handling behavior. 若要深入了解如何解决不需要的类处理以及如何在自定义类中定义自己的类处理,请参阅将路由事件标记为“已处理”和类处理For more information both on how to work around undesired class handling and on defining your own class handling in a custom class, see Marking Routed Events as Handled, and Class Handling.

WPF 中的附加事件Attached Events in WPF

XAMLXAML 语言还定义了一个名为附加事件的特殊类型的事件。The XAMLXAML language also defines a special type of event called an attached event. 使用附加事件,可以将特定事件的处理程序添加到任意元素中。An attached event enables you to add a handler for a particular event to an arbitrary element. 处理事件的元素不必定义或继承附加事件,可能引发事件的对象和用来处理实例的目标也都不必将该事件定义为类成员,或将其作为类成员来“拥有”。The element handling the event need not define or inherit the attached event, and neither the object potentially raising the event nor the destination handling instance must define or otherwise "own" that event as a class member.

WPFWPF 输入系统广泛使用附加事件。The WPFWPF input system uses attached events extensively. 但是,几乎所有的附加事件都是通过基本元素转发的。However, nearly all of these attached events are forwarded through base elements. 输入事件随后会显示为作为基本元素类成员的等效非附加路由事件。The input events then appear as equivalent non-attached routed events that are members of the base element class. Mouse.MouseDown例如, 可以UIElement通过使用MouseDown XAMLXAMLUIElement更轻松地处理基础附加事件, 而不是在或代码中处理附加事件语法。For instance, the underlying attached event Mouse.MouseDown can more easily be handled on any given UIElement by using MouseDown on that UIElement rather than dealing with attached event syntax either in XAMLXAML or code.

有关 WPFWPF 中附加事件的详细信息,请参阅附加事件概述For more information about attached events in WPFWPF, see Attached Events Overview.

XAML 中的限定事件名称Qualified Event Names in XAML

为子元素所引发的路由事件附加处理程序是另一个语法用法,它与 typename.eventname 附加事件语法相似,但它并非严格意义上的附加事件用法。Another syntax usage that resembles typename.eventname attached event syntax but is not strictly speaking an attached event usage is when you attach handlers for routed events that are raised by child elements. 可以向公用父级附加处理程序以利用事件路由,即使公用父级可能没有作为成员的相关路由事件,也是如此。You attach the handlers to a common parent, to take advantage of event routing, even though the common parent might not have the relevant routed event as a member. 请再次思考下面的示例:Consider this example again:

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

此处, 添加处理程序的父元素侦听器是StackPanelHere, the parent element listener where the handler is added is a StackPanel. 但是, 它将为已声明的路由事件添加处理程序, 并将由Button类引发 (ButtonBase Button实际上, 但可通过继承实现)。However, it is adding a handler for a routed event that was declared and will be raised by the Button class (ButtonBase actually, but available to Button through inheritance). Button"拥有" 事件, 但路由事件系统允许任何路由事件的处理程序连接到任何UIElementContentElement实例侦听器, 这些侦听器可能会为公共语言运行时 (CLR) 事件附加侦听器。Button "owns" the event, but the routed event system permits handlers for any routed event to be attached to any UIElement or ContentElement instance listener that could otherwise attach listeners for a common language runtime (CLR) event. 对于这些限定的事件特性名来说,默认的 xmlns 命名空间通常是默认的 WPFWPF xmlns 命名空间,但是还可以为自定义路由事件指定带有前缀的命名空间。The default xmlns namespace for these qualified event attribute names is typically the default WPFWPF xmlns namespace, but you can also specify prefixed namespaces for custom routed events. 有关 xmlns 的详细信息,请参阅 WPF XAML 的 XAML 命名空间和命名空间映射For more information about xmlns, see XAML Namespaces and Namespace Mapping for WPF XAML.

WPF 输入事件WPF Input Events

路由事件在 WPFWPF 平台中的常见应用之一是用于事件输入。One frequent application of routed events within the WPFWPF platform is for input events. WPFWPF 中,按照约定,隧道路由事件的名称以单词“Preview”开头。In WPFWPF, tunneling routed events names are prefixed with the word "Preview" by convention. 输入事件通常成对出现,一个是浮升事件,另一个是隧道事件。Input events often come in pairs, with one being the bubbling event and the other being the tunneling event. 例如, KeyDown事件PreviewKeyDown和事件具有相同的签名, 前者是冒泡输入事件, 后者是隧道输入事件。For example, the KeyDown event and the PreviewKeyDown event have the same signature, with the former being the bubbling input event and the latter being the tunneling input event. 偶尔,输入事件只有浮升版本,或者有可能只有直接路由版本。Occasionally, input events only have a bubbling version, or perhaps only a direct routed version. 在文档中,路由事件主题交叉引用具有备用路由策略的类似路由事件(如果存在这类路由事件),托管的引用页面中的相关部分阐明每个路由事件的路由策略。In the documentation, routed event topics cross-reference similar routed events with alternative routing strategies if such routed events exist, and sections in the managed reference pages clarify the routing strategy of each routed event.

实现成对出现的 WPFWPF 输入事件,使来自输入的单个用户操作(如按鼠标按钮)按顺序引发该对中的两个路由事件。WPFWPF input events that come in pairs are implemented so that a single user action from input, such as a mouse button press, will raise both routed events of the pair in sequence. 首先引发隧道事件并沿路由传播。First, the tunneling event is raised and travels its route. 然后引发浮升事件并沿路由传播。Then the bubbling event is raised and travels its route. 这两个事件以字面方式共享相同的事件数据实例RaiseEvent , 因为引发冒泡事件的实现类中的方法调用会侦听隧道事件中的事件数据, 并在新引发的事件中重用它。The two events literally share the same event data instance, because the RaiseEvent method call in the implementing class that raises the bubbling event listens for the event data from the tunneling event and reuses it in the new raised event. 具有隧道事件处理程序的侦听器首先获得将路由事件标记为“已处理”的机会(首先是类处理程序,然后是实例处理程序)。Listeners with handlers for the tunneling event have the first opportunity to mark the routed event handled (class handlers first, then instance handlers). 如果隧道路由中的某个元素将路由事件标记为“已处理”,则会针对浮升事件发送已处理的事件数据,而且将不调用等效的浮升输入事件的附加典型处理程序。If an element along the tunneling route marked the routed event as handled, the already-handled event data is sent on for the bubbling event, and typical handlers attached for the equivalent bubbling input events will not be invoked. 已处理的浮升事件看起来好像尚未引发。To outward appearances it will be as if the handled bubbling event has not even been raised. 此处理行为对于控件合成非常有用,因为在此情况下你可能希望所有基于命中测试的输入事件或者所有基于焦点的输入事件都由最终的控件(而不是它的复合部件)报告。This handling behavior is useful for control compositing, where you might want all hit-test based input events or focus-based input events to be reported by your final control, rather than its composite parts. 作为可支持控件类的代码的一部分,最后一个控件元素靠近合成中的根,因此将有机会首先对隧道事件进行类处理,或者有机会将该路由事件“替换”为更针对控件的事件。The final control element is closer to the root in the compositing, and therefore has the opportunity to class handle the tunneling event first and perhaps to "replace" that routed event with a more control-specific event, as part of the code that backs the control class.

为了说明输入事件处理的工作方式,请思考下面的输入事件示例。As an illustration of how input event processing works, consider the following input event example. 在下面的树插图中leaf element #2 , 是PreviewMouseDownMouseDown事件的源:In the following tree illustration, leaf element #2 is the source of both a PreviewMouseDown and then a MouseDown event:

事件路由示意图

事件的处理顺序如下所述:The order of event processing is as follows:

  1. 针对根元素处理 PreviewMouseDown(隧道)。PreviewMouseDown (tunnel) on root element.

  2. 针对中间元素 #1 处理 PreviewMouseDown(隧道)。PreviewMouseDown (tunnel) on intermediate element #1.

  3. 针对源元素 #2 处理 PreviewMouseDown(隧道)。PreviewMouseDown (tunnel) on source element #2.

  4. 针对源元素 #2 处理 MouseDown(浮升)。MouseDown (bubble) on source element #2.

  5. 针对中间元素 #1 处理 MouseDown(浮升)。MouseDown (bubble) on intermediate element #1.

  6. 针对根元素处理 MouseDown(浮升)。MouseDown (bubble) on root element.

路由事件处理程序委托提供对以下两个对象的引用:引发该事件的对象以及在其中调用处理程序的对象。A routed event handler delegate provides references to two objects: the object that raised the event and the object where the handler was invoked. 在其中调用处理程序的对象是由 sender 参数报告的对象。The object where the handler was invoked is the object reported by the sender parameter. 事件数据中的Source属性报告第一次引发事件的对象。The object where the event was first raised is reported by the Source property in the event data. 路由事件仍可由相同的对象引发和处理, 在这种情况下sender , Source和是相同的 (事件处理示例列表中的步骤3和4就是这种情况)。A routed event can still be raised and handled by the same object, in which case sender and Source are identical (this is the case with Steps 3 and 4 in the event processing example list).

由于隧道和冒泡, 父元素会接收输入事件, 其中Source , 是其子元素之一。Because of tunneling and bubbling, parent elements receive input events where the Source is one of their child elements. 如果知道源元素是什么, 则可以通过访问Source属性来标识源元素。When it is important to know what the source element is, you can identify the source element by accessing the Source property.

通常, 在标记Handled输入事件后, 不会调用进一步的处理程序。Usually, once the input event is marked Handled, further handlers are not invoked. 通常,一旦调用了用来对输入事件的含义进行特定于应用程序的逻辑处理的处理程序,就应当将输入事件标记为“已处理”。Typically, you should mark input events as handled as soon as a handler is invoked that addresses your application-specific logical handling of the meaning of the input event.

这种关于Handled状态的常规语句的例外情况是: 注册到特意忽略Handled事件数据状态的输入事件处理程序仍将在任一路由上调用。The exception to this general statement about Handled state is that input event handlers that are registered to deliberately ignore Handled state of the event data would still be invoked along either route. 有关详细信息,请参阅预览事件将路由事件标记为“已处理”和类处理For more information, see Preview Events or Marking Routed Events as Handled, and Class Handling.

通常,隧道事件和浮升事件之间的共享事件数据模型以及先引发隧道事件后引发浮升事件的顺序引发并非适用于所有的路由事件的概念。The shared event data model between tunneling and bubbling events, and the sequential raising of first tunneling then bubbling events, is not a concept that is generally true for all routed events. 该行为的实现取决于 WPFWPF 输入设备选择引发和连接输入事件对的具体方式。That behavior is specifically implemented by how WPFWPF input devices choose to raise and connect the input event pairs. 实现你自己的输入事件是一个高级方案,但是你也可以选择针对自己的输入事件遵循该模型。Implementing your own input events is an advanced scenario, but you might choose to follow that model for your own input events also.

一些类选择对某些输入事件进行类处理,其目的通常是重新定义用户驱动的特定输入事件在该控件中的含义并引发新事件。Certain classes choose to class-handle certain input events, usually with the intent of redefining what a particular user-driven input event means within that control and raising a new event. 有关详细信息,请参阅将路由事件标记为“已处理”和类处理For more information, see Marking Routed Events as Handled, and Class Handling.

有关输入以及在典型的应用程序方案中输入和事件如何交互的详细信息,请参阅输入概述For more information on input and how input and events interact in typical application scenarios, see Input Overview.

EventSetter 和 EventTriggerEventSetters and EventTriggers

在样式中, 可以通过XAMLXAML EventSetter使用在标记中包含一些预声明的事件处理语法。In styles, you can include some pre-declared XAMLXAML event handling syntax in the markup by using an EventSetter. 在应用样式时,所引用的处理程序会添加到带样式的实例中。When the style is applied, the referenced handler is added to the styled instance. 只能为路由事件EventSetter声明。You can declare an EventSetter only for a routed event. 下面是一个示例。The following is an example. 请注意,此处引用的 b1SetColor 方法位于代码隐藏文件中。Note that the b1SetColor method referenced here is in a code-behind file.

<StackPanel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="SDKSample.EventOvw2"
  Name="dpanel2"
  Initialized="PrimeHandledToo"
>
  <StackPanel.Resources>
    <Style TargetType="{x:Type Button}">
      <EventSetter Event="Click" Handler="b1SetColor"/>
    </Style>
  </StackPanel.Resources>
  <Button>Click me</Button>
  <Button Name="ThisButton" Click="HandleThis">
    Raise event, handle it, use handled=true handler to get it anyway.
  </Button>
</StackPanel>

这里获得的优势在于, 样式可能包含大量其他可应用于应用程序中任何按钮的信息, 并且EventSetter , 将作为该样式的一部分, 即使在标记级别也是如此。The advantage gained here is that the style is likely to contain a great deal of other information that could apply to any button in your application, and having the EventSetter be part of that style promotes code reuse even at the markup level. 此外, EventSetter抽象方法会从一般的应用程序和页标记中进一步进一步命名处理程序。Also, an EventSetter abstracts method names for handlers one step further away from the general application and page markup.

合并的路由事件和动画功能WPFWPF的另一个专用语法EventTrigger是。Another specialized syntax that combines the routed event and animation features of WPFWPF is an EventTrigger. EventSetter一样, 仅路由事件可用于EventTriggerAs with EventSetter, only routed events may be used for an EventTrigger. 通常, EventTrigger将声明为样式的一部分, EventTrigger但是也可以在页级Triggers元素上声明为集合的一部分, 或者在中ControlTemplate声明。Typically, an EventTrigger is declared as part of a style, but an EventTrigger can also be declared on page-level elements as part of the Triggers collection, or in a ControlTemplate. 利用, 您可以指定一个Storyboard每当路由事件在EventTrigger其路由中到达为该事件声明的元素时运行的。 EventTriggerAn EventTrigger enables you to specify a Storyboard that runs whenever a routed event reaches an element in its route that declares an EventTrigger for that event. 除了处理事件并EventTrigger导致其启动现有情节提要外, 的优点是EventTrigger可以更好地控制情节提要及其运行时行为。The advantage of an EventTrigger over just handling the event and causing it to start an existing storyboard is that an EventTrigger provides better control over the storyboard and its run-time behavior. 有关详细信息,请参阅在情节提要启动之后使用事件触发器来控制情节提要For more information, see Use Event Triggers to Control a Storyboard After It Starts.

有关路由事件的更多信息More About Routed Events

本主题主要从以下角度讨论路由事件:描述基本概念;就如何以及何时响应各种基元素和控件中已经存在的路由事件提供指南。This topic mainly discusses routed events from the perspective of describing the basic concepts and offering guidance on how and when to respond to the routed events that are already present in the various base elements and controls. 但是,你可以在自定义类上创建自己的路由事件以及所有必要的支持(如专用的事件数据类和委托)。However, you can create your own routed event on your custom class along with all the necessary support, such as specialized event data classes and delegates. 路由事件所有者可以是任何类, 但路由事件必须由UIElementContentElement派生类引发和处理才能发挥作用。The routed event owner can be any class, but routed events must be raised by and handled by UIElement or ContentElement derived classes in order to be useful. 有关自定义事件的详细信息,请参阅创建自定义路由事件For more information about custom events, see Create a Custom Routed Event.

请参阅See also