WPF新手村教程(三)—— 路由事件

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

      • 比如:PreviewMouseDownPreviewKeyDown
    • 经典场景:全局快捷键、输入过滤、行为拦截

    • 代码示例:

      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 来快速打开文档大纲视图

随笔参考:25.第5章_路由概念及路由事件_哔哩哔哩_bilibili

相关推荐
用户298698530144 小时前
程序员效率工具:Spire.Doc如何助你一键搞定Word表格排版
后端·c#·.net
mudtools1 天前
搭建一套.net下能落地的飞书考勤系统
后端·c#·.net
玩泥巴的2 天前
搭建一套.net下能落地的飞书考勤系统
c#·.net·二次开发·飞书
唐宋元明清21882 天前
.NET 本地Db数据库-技术方案选型
windows·c#
lindexi2 天前
dotnet DirectX 通过可等待交换链降低输入渲染延迟
c#·directx·d2d·direct2d·vortice
qq_454245032 天前
基于组件与行为的树状节点系统
数据结构·c#
bugcome_com2 天前
C# 类的基础与进阶概念详解
c#
雪人不是菜鸡2 天前
简单工厂模式
开发语言·算法·c#
铸人2 天前
大数分解的Shor算法-C#
开发语言·算法·c#