Wpf基础入门——事件(事件&路由事件)
本篇文章学习于: 刘铁猛老师《深入浅出WPF》
WPF 的树型结构#
路由(Route)一词的大意是这样:起点与终点间有若干个中转站,从起点出发后经过每个甲转站时要做出选择,最终以正确(比如最短或者最快)的路径到达终点。
WPF把这种直接消息模型升级为可传递的消息模型——前面我们已经知道WPF 的UI是由布局组件和控件构成的树形结构,当这棵树上的某个结点激发出某个事件时,程序员可以选择以传统的直接事件模式让响应者来响应之,也可以让这个事件在UI组件树沿着一定的方向传递且路过多个中转结点,并在这个路由过程中被恰当地处理。
你可以把WPF的路由事件看成是一只小蚂蚁,它可以从树的基部向顶部(或反向)目标爬行,每路过一个树枝的分叉点就会把消息带给这个分叉点。
WPF中有两种“树”:一种叫逻辑树(Logical Tree);一种叫可视元素树(Visual Tree )。
下图就是Logical Tree,它的每个结点不是布局组件就是控件。
那什么是Visual Tree 呢?我们知道,如果把一片树叶放在放大镜下观察,你会发现这片叶子也像一棵“树”一样——有自己的基部并向上生长出多级分叉。在WPF的 Logical Tree 上,充当叶子的一般都是控件,如果我们把WPF的控件也放在“放大镜”下去观察,你会发现每个WPF 控件本身也是一棵由更细微级别的组件(它们不是控件,而是一些可视化组件,派生自Visual类)组成的树。
如果想在Logical Tree 上导航或查找元素,可以借助LogicalTreeHelper类的static方法来实现:
- BringIntoView:把选定元素带进用户可视区域,经常用于可滚动的视图。
- FindLogicalNode:按给定名称(Name属性值)查找元素,包括子级树上的元素。GetChildren:获取所有直接子级元素。
- GetParent:获取直接父级元素。
现在我们已经知道,WPF 的UI可以表示为Logical Tree和Visual Tree,那么当一个路由事件被激发后是沿着Logical Tree传递还是沿着Visual Tree传递呢?答案是Visual Tree,只有这样,“藏”在Template里的控件才能把消息送出来。
事件详解#
事件的前身是消息,事件模型隐藏了消息机制的很多细节,让程序的开发变得简单。
烦琐的消息驱动机制在事件模型中被简化为3个关键点:
- 事件的拥有者:即消息的发送者。事件的宿主可以在某些条件下激发它拥有的事件,即事件被触发。事件被触发则消息被发送。
- 事件的响应者:即消息的接收者、处理者。事件接收者使用其事件处理器(Event Handler)对事件做出响应。
- 事件的订阅关系:事件的拥有者可以随时激发事件,但事件发生后会不会得到响应要看有没有事件的响应者,或者说要看这个事件是否被关注。如果对象A关注对象B的某个事件是否发生,则称A订阅了B的事件。更进一步讲,事件实际上是一个使用event关键字修饰的委托(Delegate)类型成员变量,事件处理器则是一个函数,说A订阅了 B的事件,本质上就是让B.Event 与 A.EventHandler关联起来。所谓事件激发就是B.Event被调用,这时,与其关联的A.EventHandler就会被调用。
示例:
这就是直接事件模型,这样至少有两个弊端:
(1)每对消息是“发送→响应”关系,必须建立显式的点对点订阅关系。
(2)事件的宿主必须能够直接访问事件的响应者,不然无法建立订阅关系。
路由事件的出现很好地解决了上述两种情况中出现的问题。
路由事件#
为了降低由事件订阅带来的耦合度和代码量,WPF推出了路由事件机制。
路由事件与直接事件的区别在于:
- 直接事件激发时,发送者直接将消息通过事件订阅交送给事件响应者,事件响应者使用其事件处理器方法对事件的发生做出响应、驱动程序逻辑按客户需求运行;
- 路由事件的事件拥有者和事件响应者之间则没有直接显式的订阅关系,事件的拥有者只负责激发事件,事件将由谁响应它并不知道,事件的响应者则安装有事件侦听器,针对某类事件进行侦听,当有此类事件传递至此时事件响应者就使用事件处理器来响应事件并决定事件是否可以继续传递。
示例:使用 WPF 内置路由事件
<Window x:Class="Demo4.Wpf路由事件.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Demo4.Wpf路由事件" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid x:Name="grdRoot" Button.Click="AllButtonClicked"> <Grid x:Name="grdSonRoot"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Canvas x:Name="canvasTop" Grid.Row="0"> <Button x:Name="btnTop" Content="上方的按钮"/> </Canvas> <Canvas x:Name="canvasButtom" Grid.Row="1"> <Button x:Name="btnButtom" Content="下方的按钮"/> </Canvas> </Grid> </Grid> </Window>
namespace Demo4.Wpf路由事件 { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); //this.grdRoot.AddHandler(Button.ClickEvent, new RoutedEventHandler(AllButtonClicked)); } private void AllButtonClicked(object sender, RoutedEventArgs e) { MessageBox.Show((e.OriginalSource as FrameworkElement).Name); } } }
示例:自定义路由事件
创建自定义路由事件大体可以分为三个步骤:
(1)声明并注册路由事件。(2)为路由事件添加CLR事件包装。(3)创建可以激发路由事件的方法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?