WPF个人文档(三)------ 路由事件
一.路由事件
先来说一下分类,参考某位博主以及博主参考的对应资料分类,先留个印象,然后我们逐步讲解
-
路由事件:冒泡事件、隧道事件(预览事件)、直接事件(直达事件)
-
事件(从作用角度划分):生命周期事件、输入事件(鼠标事件、键盘输入事件、触控事件)
1.路由 ------ 在既定结构中,按照规则传播信号的路径
路径 → 静态结构
规则 → 传播策略
信号 → 事件
-
路由 :网络工程术语,指分组从源到目的地时,决定端到端路径的网络范围的进程
-
路由的本质:信息从哪里来,要往哪里去,经过哪些节点
-
1.最原始的路由:网络
- 在互联网里,一个数据包从电脑中发出去,不是直线飞到服务器
- 它要经过多个路由器,每个路由器根据规则决定"下一跳"
- 不是简单传递,而是"根据结构和规则决定传播路径"
-
2.抽象到程序中
-
当一个事件发生时,它不一定只通知一个对象,它可能沿着某种结构传播
-
而这整条"传播路径"就是我们所说的路由
shell# 比如 UI 是一棵树 Window └── Grid └── Button-
当 Button 被点击时,事件可以:
- 只在 Button 内处理(直达)
- 往上通知 Grid → Window(冒泡)
- 或者从 Window 先往下检查(隧道)
-
-
-
2.路由事件
前面我们已经提到过路由事件的分类,现在我们对其进行较为详细的 吐槽 讲解
-
路由事件:冒泡事件、隧道事件(预览事件)、直接事件(直达事件)
-
1.🌱冒泡事件 → 由里往外
-
传播方向:子 → 父
- 典型例子:MouseDown、KeyDown、Click
-
使用场景:子控件触发行为,但由父容器统一处理
- 统一日志
- 权限判断
- 容器级行为控制
- MVVM 中命令转发
-
代码示例:
c#// 只要窗口里任何 Button 被点击,Window 都能收到 this.AddHandler(Button.ClickEvent, new RoutedEventHandler(Window_Click)); -
优点:解耦
-
缺点:调试时要小心,可能在半路被
e.Handled = true截断
-
-
2.🌱隧道事件 → 从外往里
-
传播方向:父 → 子
- 事件先从 Window 往下走到最底层控件,然后才进入冒泡阶段
- 即:父级可以"预先审查"事件
-
典型事件格式:
PreviewXXX- 比如:
PreviewMouseDown、PreviewKeyDown
- 比如:
-
经典场景:全局快捷键、输入过滤、行为拦截
-
代码示例:
c#// 行为拦截:子控件接收不到按键 F5 private void Window_PreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.F5) e.Handled = true; // e.Handled = true 表示这个按键的路由到此结束 // 这个事件已经被处理完了,不需要继续传播 // 1.阻止事件继续沿当前路由传播 // 2.阻止对应的后续阶段执行 }
-
-
3.🌱直达事件 → 不传播
-
传播方向:只在当前控件触发,不向上也不向下
-
典型例子:MouseEnter、MouseLeave、Loaded
-
!IMPORTANT
- 为什么它只在当前控件触发,不向上不向下?
- 因为语义上不适合传播
- 鼠标进入 Button,不等于进入它父 Grid
- 如果冒泡,会产生逻辑污染
- 为什么它只在当前控件触发,不向上不向下?
-
直达事件适合:
- 局部状态改变
- 单控件行为
- 不需要结构参与的逻辑
- 总结
- 🌱冒泡事件:我干了件事,要向上级汇报。
- 🌱隧道事件:我要干件事,谁敢不同意的?
- 🌱直达事件:我自己的事,谁也管不了我!
类型 传播方向 典型事件 核心作用 适用场景 优点 风险点 🌱冒泡事件 子 → 父 MouseDown KeyDown Click 子控件触发,上层统一处理 1.统一日志 2.权限判断 3,命令转发 4.容器控制 解耦、集中管理 可能被 e.Handled 中途截断,调试链路复杂 🌱隧道事件 父 → 子 PreviewMouseDown PreviewKeyDown 事件发生前的"预审查"机制 1.全局快捷键 2.输入过滤 3.行为拦截 可提前控制 滥用会造成隐藏拦截逻辑 🌱直达事件 不传播 MouseEnter MouseLeave Loaded 仅当前控件自身处理 1.局部状态变化 2.生命周期控制 语义清晰 不能参与结构级控制 -
二.普通事件
回顾之前的分类
-
事件(从作用角度划分):生命周期事件、输入事件(鼠标事件、键盘输入事件、触控事件)
- 换句话说,是根据 事件在干什么 分类的
1.生命周期事件 → 控件自己的阶段变化
要理解生命周期事件,需要考虑一件事情:这个控件现在处于什么阶段? ,它描述的是"状态变化"
-
常见生命周期事件:Initialized、Loaded、Unloaded、SizeChanged
-
代码示例:通常为直达事件
c#// 控件初始化 public MyControl() { InitializeComponent(); this.Initialized += (s, e) => { Debug.WriteLine("控件初始化完成"); }; } // 控件在视觉树中的加载 private void UserControl_Loaded(object sender, RoutedEventArgs e) { // 这里可以安全访问 ActualWidth Debug.WriteLine(this.ActualWidth); } // 控件离开视觉树 private void UserControl_Unloaded(object sender, RoutedEventArgs e) { // 释放资源 timer.Stop(); } // 布局尺寸发生变化 private void Grid_SizeChanged(object sender, SizeChangedEventArgs e) { Debug.WriteLine($"新尺寸: {e.NewSize}"); }
2.输入事件 → 用户或外部行为驱动
要理解输入事件,需要考虑一件事情:用户做了什么? ,来源是操作系统输入系统 ,本质是用户行为推动系统状态变化
-
常见输入事件:MouseDown / MouseMove、KeyDown / KeyUp
-
代码示例:
c#// 键盘输入 private void Window_KeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) { Submit(); } } // 隧道路由 -> 数据拦截 private void Window_PreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.F5) { e.Handled = true; // 阻断传播 } } // 鼠标输入 private void Button_MouseDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine("鼠标按下"); }
| 维度 | 生命周期事件 | 输入事件 |
|---|---|---|
| 核心问题 | 控件现在处于什么阶段? | 用户做了什么? |
| 时间属性 | 内部时间 | 外部时间 |
| 触发来源 | 控件状态变化 | 操作系统输入 |
| 是否依赖用户 | 否 | 是 |
| 典型事件 | Loaded / Unloaded / SizeChanged | MouseDown / KeyDown / TextInput |
| 路由类型 | 多为 Direct | 多为 Bubble + Tunnel |
| 常见用途 | 初始化、释放、布局响应 | 行为驱动、交互控制 |
| 滥用风险 | 状态逻辑与业务耦合 | 传播链复杂、拦截难追踪 |
三.路由事件 VS 普通事件
-
普通事件 是 对象之间的通信
-
普通事件 强调 对象之间的关系
-
点对点:A → B
-
-
路由事件 是 结构中的信号传播
-
路由事件 强调 层级结构的控制权
-
A → 父 → 祖父 → ...
或者
祖父 → 父 → A路径由"结构"决定,而不是写代码时手动指定
-
| 维度 | 普通事件 | 路由事件 |
|---|---|---|
| 传播方式 | 点对点 | 沿视觉树传播 |
| 是否有传播路径 | 没有 | 有(冒泡 / 隧道 / 直达) |
| 是否可拦截 | 不支持 | 支持 e.Handled |
| 结构参与决策 | 不参与 | 由 UI 树结构决定 |
| 典型场景 | 业务逻辑通知 | 输入系统、控件行为管理 |
| 复杂度 | 简单 | 较复杂 |
四.UI与树状结构 ------ 以 "空间和归属" 思考UI结构
在路由事件中,我们可以将UI 的可视元素(Window、Grid、Button、TextBox......)看作一棵树(树状结构)
shell
Window
├─ Grid
│ ├─ Button
│ └─ TextBox
└─ StatusBar
-
这棵树告诉我们:
-
父级和子级的关系
-
控件层级和布局关系
-
事件传播路径(冒泡/隧道)
-
-
树结构决定了结构相关的行为:
-
路由事件传播
-
父控件统一拦截子控件行为
-
布局和绘制顺序
-
-
!NOTE
- 如何在Visual Studio中查看WPF中UI的树状结构?
- 只需要打开文档大纲即可,文档大纲在项目中默认显示在左侧
- 如果不小心关闭,使用快捷键 Ctrl+Alt+T 来快速打开文档大纲视图


- 如何在Visual Studio中查看WPF中UI的树状结构?