WPF 路由事件
一、什么是路由事件
1.功能定义
路由事件是一种可以针对 元素树 中的多个 侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件。
2. 实现定义
路由事件是由类的实例支持的 CLR 事件,由 WPF 事件系统进行处理。
二、路由策略
1.冒泡
调用事件源上的事件处理程序,随后会路由到后续的父级元素,直到到达元素树的根,其中路由方向是从应用程序引发事件的元素传到根,并在事件数据中报告为源。
2.预览
预览事件,也称为隧道事件,是路由事件,其中路由的方向从应用程序根传到引发事件的元素,并在事件数据中报告为源。
3.直接
只有源元素本身才有机会调用处理程序以进行响应。
三、路由事件的使用
1.定义一个路由事件
// 这是一个常规路由事件的写法
public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
"Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));
// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
add { AddHandler(TapEvent, value); }
remove { RemoveHandler(TapEvent, value); }
}
// 这是一个附加路由事件的写法
internal class MyRoutedEvents
{
public static readonly RoutedEvent NeedsCleaningEvent = EventManager.RegisterRoutedEvent("NeedsCleaning", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(MyRoutedEvents));
public static void AddNeedsCleaningHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
{
uie.AddHandler(MyRoutedEvents.NeedsCleaningEvent, handler);
}
}
public static void RemoveNeedsCleaningHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
{
uie.RemoveHandler(MyRoutedEvents.NeedsCleaningEvent, handler);
}
}
}
其实两者也没多大差别,路由事件只是对常规的路由事件做了一个简单的封装而已。
2.引发路由事件
凡是继承自 UIElement 的元素都可以引发一个路由事件。可以调用UIElement.RaiseEvent(RoutedEventArgs e)
方法引发一个路由事件,举个栗子:
// 主函数
internal class MainClass
{
[STAThread]
private static void Main()
{
var application = new Application();
application.Run(new MainWindow());
}
}
// 主窗口,接收冒泡的路由事件。
internal class MainWindow : Window
{
public MainWindow()
{
Width = 400;
Height = 647;
WindowStartupLocation = WindowStartupLocation.CenterScreen;
this.Loaded += MainWindow_Loaded;
// 订阅路由事件。
this.AddHandler(MyUserControl.TapEvent, new RoutedEventHandler((sender, arg) =>
{
MessageBox.Show("Receive event as MainWindow.");
}));
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
this.AddChild(new MyUserControl());
}
}
// 用户控件,用于触发路由事件
internal sealed class MyUserControl : UserControl
{
// 这是一个常规路由事件的写法
public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
"Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyUserControl));
// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
add => AddHandler(TapEvent, value);
remove => RemoveHandler(TapEvent, value);
}
public MyUserControl()
{
var myButton = new Button
{
Content = "Toggle Event",
Width = 200,
Height = 20
};
myButton.Click += MyButton_Click;
this.AddChild(myButton);
// 自己也订阅一个Tap事件
this.AddHandler(TapEvent, new RoutedEventHandler((s, args) =>
{
MessageBox.Show("Receive event as myself.");
}));
}
private void MyButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.RaiseEvent(new RoutedEventArgs(TapEvent, this));
}
}
在这个例子钟,RoutingStrategy.Bubble
参数是指定这个事件的路由策略,点击 Toggle Event
按钮,首先弹出 “Receive event as myself.”,然后再弹出 “Receive event as MainWindow.”,如果将 Bubble 改为 Tunnel 则弹出对话框的顺序相反。
刚才的栗子是一个常规路由事件的例子,那现在举一个附加事件的例子吧!如下:
// 这是一个附加路由事件的写法
internal class MyRoutedEvents
{
public static readonly RoutedEvent NeedsCleaningEvent = EventManager.RegisterRoutedEvent("NeedsCleaning", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyRoutedEvents));
public static void AddNeedsCleaningHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
{
uie.AddHandler(MyRoutedEvents.NeedsCleaningEvent, handler);
}
}
public static void RemoveNeedsCleaningHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
{
uie.RemoveHandler(MyRoutedEvents.NeedsCleaningEvent, handler);
}
}
}
// 用于触发事件的用户控件。
internal sealed class MyUserControl : UserControl
{
public MyUserControl()
{
var myButton = new Button
{
Content = "Toggle Event",
Width = 200,
Height = 20
};
myButton.Click += MyButton_Click;
this.AddChild(myButton);
// 自己也订阅一个NeedsCleaning事件
MyRoutedEvents.AddNeedsCleaningHandler(this, new RoutedEventHandler((s, args) =>
{
MessageBox.Show("Receive event as myself.");
}));
}
private void MyButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.RaiseEvent(new RoutedEventArgs(MyRoutedEvents.NeedsCleaningEvent, this));
}
}
// 主窗口,用于接收用户控件。
internal class MainWindow : Window
{
public MainWindow()
{
Width = 400;
Height = 647;
WindowStartupLocation = WindowStartupLocation.CenterScreen;
this.Loaded += MainWindow_Loaded;
// 自己也订阅一个NeedsCleaning事件
MyRoutedEvents.AddNeedsCleaningHandler(this, new RoutedEventHandler((s, args) =>
{
MessageBox.Show("Receive event as MainWindow.");
}));
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
this.AddChild(new MyUserControl());
}
}
// 主函数
internal class MainClass
{
[STAThread]
private static void Main()
{
var application = new Application();
application.Run(new MainWindow());
}
}
同样,点击 Toggle Event
按钮,首先弹出 “Receive event as myself.”,然后再弹出 “Receive event as MainWindow.”,如果将 Bubble 改为 Tunnel 则弹出对话框的顺序相反。
四、路由事件实现原理
// Constructor for a RoutedEvent (is internal
// to the EventManager and is onvoked when a new
// RoutedEvent is registered)
internal RoutedEvent(
string name,
RoutingStrategy routingStrategy,
Type handlerType,
Type ownerType)
{
_name = name;
_routingStrategy = routingStrategy;
_handlerType = handlerType;
_ownerType = ownerType;
_globalIndex = GlobalEventManager.GetNextAvailableGlobalIndex(this);
}
// _globalIndexToEventMap 是一个用于存储 RoutedEvent 的一个数组,路由事件的 _globalIndex 索引就是路由事件在 _globalIndexToEventMap 数组中的索引。
internal static int GetNextAvailableGlobalIndex(object value)
{
int index;
lock (Synchronized)
{
// Prevent GlobalIndex from overflow. RoutedEvents are meant to be static members and are to be registered
// only via static constructors. However there is no cheap way of ensuring this, without having to do a stack walk. Hence
// concievably people could register RoutedEvents via instance methods and therefore cause the GlobalIndex to
// overflow. This check will explicitly catch this error, instead of silently malfuntioning.
if (_globalIndexToEventMap.Count >= Int32.MaxValue)
{
throw new InvalidOperationException(SR.Get(SRID.TooManyRoutedEvents));
}
index = _globalIndexToEventMap.Add(value);
}
return index;
}
路由事件通常都是静态的,存储在 GlobalEventManager 类型的 _globalIndexToEventMap 字段里。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗