↓ 跳过正文

WPF-事件系统底层原理.

·7 分钟
作者
Hexin
在确定的逻辑与不确定的现实之间,做一名耐心的构建者。
目录

一、整体架构总览
#

WPF事件系统由 三层 组成:

┌─────────────────────────────────────────────┐
│              XAML 层 (声明式绑定)              │
│         <Button Click="Handler"/>            │
├─────────────────────────────────────────────┤
│           RoutedEvent 层 (路由传播)           │
│    EventManager / EventRoute / RaiseEvent    │
├─────────────────────────────────────────────┤
│             CLR Event 层 (委托机制)           │
│          delegate / multicast delegate       │
└─────────────────────────────────────────────┘

二、RoutedEvent 的注册机制
#

2.1 EventManager.RegisterRoutedEvent
#

每个路由事件都通过 EventManager 注册到一个 全局静态表 中。

public static readonly RoutedEvent ClickEvent =
    EventManager.RegisterRoutedEvent(
		"Click",						// 事件名
		RoutingStrategy.Bubble,			// 路由策略
		typeof(RoutedEventHandler),		// 委托类型
		typeof(ButtonBase));			// 拥有者类型

2.2内部数据结构
#

EventManager 内部维护:

GlobalEventManager
├── _routedEvents: List<RoutedEvent>       // 所有已注册的路由事件
├── _eventMap: Dictionary<int, RoutedEvent> // GlobalIndex → RoutedEvent
└── _classHandlers: Hashtable              // 类级别处理器映射
       ├── Key: (Type, RoutedEvent)
       └── Value: RoutedEventHandlerInfoList

每个 RoutedEvent 对象包含:

public sealed class RoutedEvent
{
    public string Name { get; }
    public RoutingStrategy RoutingStrategy { get; }
    public Type HandlerType { get; }
    public Type OwnerType { get; }
    internal int GlobalIndex { get; }		// 全局唯一索引,用于快速查找
}

“关键点: GlobalIndex 是一个递增整数,用于在数组中 O(1) 定位事件。”

三、事件处理器的存储机制
#

3.1 两种级别的处理器
#

┌─────────────────────────────────────────────┐
│           Class Handler (类级别)             │
│   通过 RegisterClassHandler 注册             │
│   存储在 EventManager 全局表中               │
│   优先执行,所有实例共享                      │
├─────────────────────────────────────────────┤
│         Instance Handler (实例级别)          │
│   通过 AddHandler / += 注册                  │
│   存储在每个 UIElement 实例的 EventHandlersStore │
│   后执行                                     │
└─────────────────────────────────────────────┘

3.2 类级别处理器注册
#

// 在静态构造函数中注册
static MyButton()
{
    EventManager.RegisterClassHandler(
    	typeof(MyButton),
    	ButtonBase.ClickEvent,
    	new RoutedEventHandler(OnClickClassHandler),
    	handledEventsToo: false);		// 是否处理已标记 Handled 的事件
}

private static void OnClickClassHandler(object sender, RoutedEventArgs e)
{
    // 类级别处理,优先于实例处理器
}

3.3 实例级别处理器存储
#

每个 UIElement 内部有一个 EventHandlersStore :

// UIElement 内部简化:
public class UIElement : Visual
{
    private EventHandlersStore _eventHandlersStore;

    public void AddHandler(RoutedEvent routedEvent, Delegate handler, bool handledEventsToo)
    {
        if (_eventHandlersStore == null)
            _eventHandlersStore = new EventHandlersStore();

        _eventHandlersStore.AddRoutedEventHandler(routedEvent, handler, handledEventsToo);
    }
}

EventHandlersStore 内部结构:

EventHandlersStore
└── _entries: FrugalMap (轻量字典)
        Key   = RoutedEvent.GlobalIndex (int)
        Value = RoutedEventHandlerInfo[]

RoutedEventHandlerInfo
├── Handler: Delegate          // 实际处理器
└── InvokeHandledEventsToo: bool // 是否在 Handled=true 时仍执行

“为什么用 FrugalMap? 大部分控件只订阅少量事件,FrugalMap 对1-6 个元素有机制优化(避免Dictionary 的哈希开销)。”

四、事件路由传播机制
#

4.1 RaiseEvent 入口
#

// UIElement.RaiseEvent 简化逻辑
public void RaiseEvent(RoutedEventArgs e)
{
    e.Source = this;

    // 1. 构建事件路由
    EventRoute route = new EventRoute(e.RoutedEvent);

    // 2. 沿可视树构建路由路径
    BuildRoute(route);

    // 3. 按路由策略调用处理器
    route.InvokeHandlers(e);

    e.Source = e.OriginalSource;
}

4.2 BuildRoute - 构建路由路径
#

// 简化的路由构建过程
internal void BuildRoute(EventRoute route)
{
    // 从当前元素向上遍历可视树
    UIElement current = this;

    while (current != null)
    {
        // 将每个节点的处理器信息加入路由表
        current.AddToEventRoute(route);

        // 向上走到父元素
        current = VisualTreeHelper.GetParent(current) as UIElement;
    }
}

4.3 EventRoute 内部结构
#

internal sealed class EventRoute
{
    private RoutedEvent _routedEvent;
    private List<RouteItem> _routeItemList;

    internal struct RouteItem
    {
        internal object Target; 	// 目标要素
        internal RoutedEventHandlerInfo Handler;	// 处理器信息
    }
}

4.4三种路由策略的执行顺序
#

假设可视树:
    Window
      └── Grid
            └── StackPanel
                  └── Button (事件源)

Bubble(冒泡) - 从下往上

执行顺序:
  1. Button      的 ClassHandler → InstanceHandler
  2. StackPanel  的 ClassHandler → InstanceHandler
  3. Grid        的 ClassHandler → InstanceHandler
  4. Window      的 ClassHandler → InstanceHandler

Tunnel(隧道) - 从上往下

执行顺序:
  1. Window      的 ClassHandler → InstanceHandler
  2. Grid        的 ClassHandler → InstanceHandler
  3. StackPanel  的 ClassHandler → InstanceHandler
  4. Button      的 ClassHandler → InstanceHandler

Direct( 直达) - 只在源

执行顺序:
  1. Button      的 ClassHandler → InstanceHandler

4.5 调用顺序的完整规则
#

对于路由上的每一个节点:

┌──────────────────────────────────┐
│ 1. Class Handlers (类级别)       │ ← 优先!无法被实例移除
│    按继承链从基类到派生类执行      │
│    UIElement → Control → Button  │
├──────────────────────────────────┤
│ 2. Instance Handlers (实例级别)  │ ← 按 AddHandler 注册顺序执行
└──────────────────────────────────┘

然后移动到路由上的下一个节点...

五、Handled 机制
#

5.1基本行为
#

private void Button_Click(object sender, RoutedEventArgs e)
{
    e.Handled = true; // 标记已处理
}
Button (Handled = true) -> StackPanel (跳过) -> Grid (跳过) -> Window (跳过)

5.2 穿透 Handled 的方法
#

// 方法一: AddHandler 指定 handledEventsToo = true
grid.AddHandler(
	ButtonBase.ClickEvent,
	new RoutedEventHandler(Grid_Click),
	handledEventsToo: true);	//  <- 即使 Handled = true 也执行

// 方法二: 类级别注册时指定
EventManager.RegisterClassHandler(
	typeof(MyGrid),
	ButtonBase.ClickEvent,
	new RoutedEventHandler(OnClick),
	handledEventsToo: true);

5.3 内部判断逻辑
#

// EventRoute.InvokeHandlers 简化逻辑
foreach (RouteItem item in _routeItemList)
{
    if (!e.Handled || item.Handler.InvokeHandledEventsToo)
    {
        item.Handler.InvokeHandler(item.Target, e);
    }
}

六、Tunnel + Bubble 配对机制
#

6.1 Preview 事件
#

WPF 中大部分事件成对出现:

Tunnel(Preview) Bubble
PreviewMouseDown MouseDown
PreviewKeyDown KeyDown
PreviewTextInput TextInput

6.2执行顺序
#

用户点击 Button:

                    Tunnel 阶段 (Preview)
                    ─────────────────────
Step 1: Window.PreviewMouseDown     ↓
Step 2: Grid.PreviewMouseDown       ↓
Step 3: StackPanel.PreviewMouseDown ↓
Step 4: Button.PreviewMouseDown     ↓

                    Bubble 阶段
                    ─────────────────────
Step 5: Button.MouseDown            ↑
Step 6: StackPanel.MouseDown        ↑
Step 7: Grid.MouseDown              ↑
Step 8: Window.MouseDown            ↑

6.3 底层实现
#

// 在输入系统中 (MouseDevice / KeyboardDevice)
// 简化的鼠标按下处理:

internal void ReportMouseDown(MouseButton button)
{
    // 1. 先触发 Tunnel 事件
    var previewArgs = new MouseButtonEventArgs(...);
    previewArgs.RoutedEvent = Mouse.PreviewMouseDownEvent;
    target.RaiseEvent(previewArgs);

    // 2. 再触发 Bubble 事件
    var args = new MouseButtonEventArgs(...);
    args.RoutedEvent = Mouse.MouseDownEvent;

    // 如果 Preview 阶段 Handled,Bubble 也跳过
    if (!previewArgs.Handled)
    {
        target.RaiseEvent(args);
    }
}

七、附加事件(Attached Event)
#

7.1概念
#

允许一个类定义事件,但由 其它类 触发或处理。

// Mouse 类定义了 MouseDown, 但任何 UIElement 都能使用
public static class Mouse
{
    public static readonly RoutedEvent MouseDownEvent =
        EventManager.RegisterRoutedEvent(
    		"MouseDown",
    		RoutingStrategy.Bubble,
    		typeof(MouseButtonEventHandler),
    		typeof(Mouse));		// 拥有者是 Mouse 类, 不是 UIElement

    // 附加的 Add/Remove 方法
    public static void AddMouseDownHandler(DependencyObject d, MouseButtonEventHandler handler)
    {
        if (d is UIElement uie)
        {
            uie.AddHandler(MouseDownEvent, handler);
        }
    }

    public static void RemoveMouseDownHandler(DependencyObject d, MouseButtonEventHandler handler)
    {
        if (d is UIElement uie)
        {
            uie.RemoveHandler(MouseDownEvent, handler);
        }
    }
}

7.2 XAML 中使用
#

<Grid Mouse.MouseDown="Grid_MouseDown">
    <!-- 子元素 -->
</Grid>

八、CLR 事件包装器
#

8.1 标准模式
#

RoutedEvent 通常会提供一个 CLR 事件包装器:

public class ButtonBase : ContentControl
{
    // 1. RoutedEvent 注册
    public static readonly RoutedEvent ClickEvent =
        EventManger.RegisterRoutedEvent("Click", ...);

    // 2. CLR 事件包装器(XAML 解析器需要)
    public event RoutedEventHandler Click
    {
        add 	{ AddHandler(ClickEvent, value); }
        remove  { RemoveHandler(ClickEvent, value); }
    }

    // 触发事件
    protected virtual void OnClick()
    {
        RaiseEvent(new RoutedEventArgs(ClickEvent, this));
    }
}

8.2 为什么需要CLR包装器?
#

┌──────────────────────────────────────────────┐
│  XAML 解析器 (XamlParser)                     │
│  遇到 <Button Click="Handler"/>              │
│                                              │
│  1. 反射查找 "Click" CLR 事件                 │
│  2. 从 CLR 事件的 add 访问器                  │
│     内部调用 AddHandler(ClickEvent, handler)  │
│  3. 最终注册到 RoutedEvent 系统               │
└──────────────────────────────────────────────┘

九、完整事件触发流程图
#

用户鼠标点击屏幕
        │

┌──────────────────┐
│   Win32 消息循环   │  WM_LBUTTONDOWN
│   (HwndSource)    │
└────────┬─────────┘
         │

┌──────────────────┐
│   InputManager    │  WPF 输入管理器
│  (Dispatcher 线程) │
└────────┬─────────┘
         │

┌──────────────────┐
│   MouseDevice     │  确定命中测试目标
│   HitTest         │  → 找到 Button
└────────┬─────────┘
         │
         ├──→ 构建 PreviewMouseDownEvent 的 EventRoute
         │        收集路由上所有节点的 Handler
         │        Window → Grid → StackPanel → Button
         │
         ├──→ Tunnel: 从 Window 到 Button 依次调用
         │        每个节点: ClassHandler → InstanceHandler
         │        检查 e.Handled
         │
         ├──→ 构建 MouseDownEvent 的 EventRoute
         │
         ├──→ Bubble: 从 Button 到 Window 依次调用
         │        每个节点: ClassHandler → InstanceHandler
         │        检查 e.Handled
         │
         ├──→ ButtonBase 内部逻辑判断 Click
         │
         ├──→ 构建 ClickEvent 的 EventRoute
         │
         └──→ Bubble: 从 Button 到 Window 依次调用
                 ClickEvent 的所有处理器

十、性能优化设计
#

优化点 实现方式
GlobalIndex 每个 RoutedEvent 分配唯一整数索引,数组 O(1) 查找
FrugalMap 小集合优化,避免 Dictionary 开销
EventRoute 复用 EventRoute 对象可被回收复用
ClassHandler共享 类处理器存全局表,不重复存储
延迟创建 EventHandlerStore 没有订阅事件的元素不创建 Store

十一、总结
#

                    WPF RoutedEvent 系统

  ┌──────────── 注册层 ────────────┐
  │  EventManager (全局注册表)      │
  │  RoutedEvent (事件标识)         │
  │  GlobalIndex (快速索引)         │
  └───────────────────────────────┘
                  │
  ┌──────────── 存储层 ────────────┐
  │  ClassHandler (全局共享)        │
  │  EventHandlersStore (实例私有)  │
  │  FrugalMap (轻量存储)           │
  └───────────────────────────────┘
                  │
  ┌──────────── 路由层 ────────────┐
  │  EventRoute (路由路径)          │
  │  BuildRoute (沿可视树构建)      │
  │  Tunnel / Bubble / Direct      │
  └───────────────────────────────┘
                  │
  ┌──────────── 调用层 ────────────┐
  │  InvokeHandlers                │
  │  ClassHandler 优先于 Instance   │
  │  Handled 机制控制传播           │
  └───────────────────────────────┘