路由概念和路由事件 继承链路 RadioButton的事件解析

WPF 中的路由事件 (Routed Events) 是专门为树形 UI 结构设计的事件传递机制。在传统的 C# 开发(如 WinForms)中,事件是"点对点"的(即谁触发、谁响应);而在 WPF 中,所有的 UI 控件都是像树枝一样层层嵌套的。路由事件允许一个事件在 UI 树中上下穿梭,直到被某个节点处理为止。

一、 核心概念:为什么需要"路由"?

假设你的界面结构是这样的:<Window> -> <Grid> -> <Button> -> <Image>。 如果用户点击了最里面的 <Image>,传统 C# 事件只会触发 Image 自己的 Click 事件。但实际上,用户不仅是点击了图片,同时也点击了包含图片的 Button,点击了 Grid,甚至点击了整个 Window。

路由事件的意义就在于,它能让这个"点击动作"按照特定的路线,自动通知整个家族树上的相关成员,而不需要你手动去挨个绑定。

二、 路由的三大策略 (Routing Strategies)

WPF 的事件路由引擎提供了三种完全不同的"旅行路线":

1. 冒泡路由 (Bubbling) ------ 【自下而上】

  • 路线:像水里的泡泡一样,从触发事件的最底层元素开始,一级一级往上冒,直到最顶层的 Window。

  • 应用场景 :绝大多数普通的交互动作(如 Click、MouseDown、MouseUp)。

  • 触发顺序:

  • <Image> 触发 ➡<Button> 触发 ➡ <Grid> 触发 ➡<Window> 触发。

2. 隧道路由 (Tunneling) ------ 【自上而下】

  • 路线:像钻隧道一样,从最顶层的根节点 (Window) 开始,一级一级往下钻,直到找到真正触发事件的那个底层元素。

  • 命名规范 :WPF 中所有的隧道事件都严格以 Preview 开头(如 PreviewMouseDown、PreviewKeyDown)。

  • 触发顺序 :<Window> 触发 ➡<Grid> 触发 ➡ <Button> 触发 ➡<Image> 触发。

  • 核心作用:给上层父容器一个"提前拦截"或"预处理"的机会。

3. 直达路由 (Direct) ------ 【点对点】

  • 路线:不冒泡也不钻隧道,只有触发事件的元素本身能收到通知,行为和传统的 .NET CLR 事件一模一样。

  • 应用场景 :纯粹的局部状态变化(如 MouseEnter、MouseLeave)。因为鼠标进入某个按钮,并不代表鼠标也进入了按钮内部包裹的图片,这种事件不需要通知父级或子级。

三、 核心机制:事件的拦截与中断 (e.Handled)

当一个事件在树形结构中游走时(无论是冒泡还是隧道),任何一个节点只要觉得"这个事件我已经处理完毕,不需要其他人再插手了",就可以强行踩下刹车。

在事件处理函数的参数 RoutedEventArgs e 中,有一个极其重要的布尔属性:e.Handled。

复制代码
private void Button_MouseDown(object sender, MouseButtonEventArgs e)
{
    // 1. 执行你的业务逻辑
    Console.WriteLine("按钮处理了点击事件");
    
    // 2. 踩下刹车!告诉 WPF 路由引擎:事件到此为止,不要再往上冒泡了。
    e.Handled = true; 
}

继承链路剖析

FrameworkElement (框架元素):

定义了控件在屏幕上渲染所需的最基础属性,比如

Width、Height、Margin 以及数据绑定上下文 (DataContext)。但它本身不知道什么是"外观"。

Control (基础控件):

它是所有 UI (User Interface,用户界面) 控件的基类。它引入了 Template (控件模板) 属性,允许控件拥有自定义的"皮肤" (ControlTemplate)。但是,并不是所有的控件都有"内容"(比如 ProgressBar 只有进度条,没有文字)。

ContentControl (内容控件)

------ 【Content 属性的诞生地】 :微软发现,有很多控件都需要在内部装载"一个东西"(比如一段文字、一张图片)。于是单独抽离出了 ContentControl。它定义了极其核心的 Content 属性(类型为 System.Object),这意味着它不仅能装字符串,还能装任何复杂的 UI 元素。

ButtonBase (按钮基类) :它继承自 ContentControl,因此顺理成章地拥有了装载内容的 Content 属性。它专门负责处理鼠标按下、抬起等点击语义,并定义了 Click 路由事件。

Button (按钮实体) :最终的具体实现类。它拥有了祖先提供的所有能力:能设置大小(来自 FrameworkElement),能换皮肤(来自 Control),能装载任意内容(来自 ContentControl),能被点击(来自 ButtonBase)。

ContentControl vs ItemsControl :WPF 将控件严格分为了两派。只能装一个元素的,叫 ContentControl (如 Button, Window, Label, CheckBox);可以装一堆元素的,叫 ItemsControl (如 ListBox, ComboBox, TreeView),其核心属性不再是 Content,而是 Items 和 ItemsSource。

WPF内容模型WPF Content Model

里面分两大类控件:

ContentControl 内容控件

代表Button Label CheckBox Window

拥有Content属性单个对象,仅支持一个直接子元素。不能直接写多个标签。

Panel面板 (布局面板,继承自Panel)

代表:Grid StackPanel Canvas DockPanel WrapPanel

拥有Children(UIElementCollection,集合)可以放任意数量子控件。

RadioButton方法

private void RadioButton_Checked(Object sender,RouteEventArgs e)

1、sender:

是在XAML里,写了这个事件绑定的那个控件;简单的来讲:是谁挂了这个事件,谁就是sender。

<RadioButton Checked="RadioButton_Checked"/>

<RadioButton Checked="RadioButton_Checked"/>

两个单选框,全部绑定同一个RadioButton_Checked

触发的时候,sender就是当前点击的这个RadioButton,这种简单场景直接写

if(sender is RadioButton rb) 即可

2、e.OriginalSource(原始源)

WPF是路由事件(RoutedEvent)

控件内部是由很多更小的UI元素拼出来的,比如RadioButton里面包含圆形小圆圈+TextBlock文字

当你点击RadioButton的内部小圆点:

点击最先发生在"小圆点"这个最底层的元素,这个真正被送鼠标点击到的最底层UI对象,就是OriginalSource,然后事件向上冒泡,一路往上跑,跑到最外面的RadioButton,在跑到Grid,Window。

OriginSource=物理上真正产生这个事件的最原始元素。

什么时候sender!=e.OriginalSource

当把事件挂在外层父容器(比如StackPanel)上面!

<StackPanel Checked="spSex_Checked">

<RadioButton Content="男"/>

<RadioButton Content="女"/>

</StackPanel>

事件不是写在RadioButton身上!事件是写在StackPanel(SpSex)上面!

这是函数里面:

·sender永远等于StackPanel(因为是StackPanel挂载的方法,不会变)

sender拿不到到底里面哪一个RadioButton被点了!

那我们去哪里找真正触发动作的控件?就去读e.OriginalSource!

private void spSex_Checked(object sender,RoutedEventArgs e)

{

//sender 这里固定是StackPanel//

真正产生事件的控件,放在e.OriginalSource

if(e.OriginalSource is RadioButton rb)

{

rb就是被选中的那个单选按钮;

}

}

e.Source

还有一个属性叫e.Source(不带Original)

Source:路由冒泡过程中,当前这一跳的源。事件每往上走一层,Source就会被修改。

OriginalSource从头到尾永远不变,保存最开始那一个。

总结

OriginalSource :出生点,一辈子不变

Source:旅途上,当前站;冒泡每上一层就更新

sender:终点站,我们给它注册函数的哪一个对象。

相关推荐
samble8 小时前
从零搭建灌装监控系统(十八):自定义控件,三色灯与状态指示
c#·wpf·mvvm·modbus·工业控制
光泽雨12 小时前
ToolTip工具提示控件
wpf·控件按钮
Vae_Mars15 小时前
WPF中如何实现单位转换
wpf
samble1 天前
从零搭建灌装监控系统(十七):LiveCharts 实时温度曲线
c#·wpf·mvvm·modbus·工业控制
光泽雨2 天前
WPF中的依赖属性
wpf
海马2 天前
Spring Cloud 开发知识整理
spring·spring cloud·wpf
闻哥2 天前
分布式任务调度框架:XXL-Job / ElasticJob / DolphinScheduler / SchedulerX 架构对比与选型
分布式·架构·wpf
预立科技3 天前
CDN(内容分发网络)知识总结
网络·wpf·cdn
FuckPatience4 天前
WPF DataGrid 行根据某个属性循环变色动画
wpf