WPF-事件系统底层原理.
目录
一、整体架构总览
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 → InstanceHandlerTunnel(隧道) - 从上往下
执行顺序:
1. Window 的 ClassHandler → InstanceHandler
2. Grid 的 ClassHandler → InstanceHandler
3. StackPanel 的 ClassHandler → InstanceHandler
4. Button 的 ClassHandler → InstanceHandlerDirect( 直达) - 只在源
执行顺序:
1. Button 的 ClassHandler → InstanceHandler4.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 机制控制传播 │
└───────────────────────────────┘