一、路由事件的核心概念
路由事件(Routed Event)是 WPF 引入的事件系统,与传统 CLR 事件的主要区别在于:它可以在元素树中 "传播",允许多个元素对同一个事件进行处理。
元素树:WPF 的 UI 控件通过嵌套形成树形结构(如Grid
包含StackPanel
,StackPanel
包含Button
),称为元素树。
事件源:触发事件的原始元素(如用户点击的Button
)。
传播路径:事件从源元素出发,沿元素树向上或向下传播的路径。
二、路由事件的三种传播策略
路由事件有三种传播方式,决定了事件在元素树中的传播路径:
传播类型 | 说明 | 典型应用 |
---|---|---|
冒泡(Bubble) | 从事件源向上传播至元素树的根节点(由内向外) | 普通交互事件(如Click 、MouseDown ) |
隧道(Tunnel) | 从元素树的根节点向下传播至事件源(由外向内) | 预览事件(如PreviewMouseDown 、PreviewKeyDown ),通常用于提前拦截事件 |
直接(Direct) | 仅在事件源上触发,不传播 | 类似传统 CLR 事件,如MouseEnter |
简单理解 冒泡:最里面的控件传播至外面 例如Grid
包含StackPanel
,StackPanel
包含Button,Button点击-->StackPanel-->Grid
隧道:点击Button后 Grid-->StackPanel-->Button
三、实战:使用WPF内置路由事件
<Window x:Class="RoutedEventDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="路由事件演示" Height="300" Width="400"Button.Click="Window_Click"> <!-- 窗口处理Button的Click事件 --><Grid Button.Click="Grid_Click"> <!-- Grid处理Button的Click事件 --><StackPanel Margin="50" Button.Click="StackPanel_Click"> <!-- StackPanel处理Button的Click事件 --><Button Content="点击我" Width="100" Height="30" Click="Button_Click"/> <!-- Button自身处理Click事件 --></StackPanel></Grid> </Window>
using System.Windows;namespace RoutedEventDemo {public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}// Button自身的Click事件处理private void Button_Click(object sender, RoutedEventArgs e){MessageBox.Show("Button处理了Click事件");// 若设置e.Handled = true,事件将停止传播(Grid和Window不会收到)// e.Handled = true; }// StackPanel处理Button的Click事件(冒泡到这里)private void StackPanel_Click(object sender, RoutedEventArgs e){MessageBox.Show("StackPanel处理了Click事件");}// Grid处理Button的Click事件(继续冒泡)private void Grid_Click(object sender, RoutedEventArgs e){MessageBox.Show("Grid处理了Click事件");}// Window处理Button的Click事件(最终冒泡到根元素)private void Window_Click(object sender, RoutedEventArgs e){MessageBox.Show("Window处理了Click事件");}} }
从上面例子可以看出每个树级别的元素都有一个Button.Click,他都可以相应这个事件,可以在后台代码不同或者相同的方法中处理。
四、自定义路由事件
1.自定义控件,有点像依赖属性
using System.Windows; using System.Windows.Controls; namespace 自定义路由事件 {// 自定义控件,作为路由事件的所有者public class CustomButton : Button{// 声明并注册路由事件public static readonly RoutedEvent CustomClickEvent =EventManager.RegisterRoutedEvent("CustomClick",// 事件名称RoutingStrategy.Bubble,// 传播策略(冒泡)typeof(RoutedEventHandler),// 事件处理程序类型typeof(CustomButton));// 所有者类型// 2. 创建CLR事件包装器(供XAML绑定)public event RoutedEventHandler CustomClick{add { AddHandler(CustomClickEvent, value); }remove { RemoveHandler(CustomClickEvent, value); }}// 重写基础按钮的点击事件protected override void OnClick(){base.OnClick(); // 确保基础按钮的功能正常// 3. 创建已注册和包装的本身的路由事件实例,并引发它RoutedEventArgs args = new RoutedEventArgs(CustomClickEvent, this);RaiseEvent(args);// 引发自定义事件 }} }
2.界面代码
<Window x:Class="自定义路由事件.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:自定义路由事件"mc:Ignorable="d"Title="自定义路由事件示例" Height="300" Width="400"><StackPanel local:CustomButton.CustomClick="StackPanel_CustomClick" Margin="10"><Border BorderBrush="Black" BorderThickness="2" Padding="10" local:CustomButton.CustomClick="Border_CustomClick"><local:CustomButton Content="点击我" CustomClick="CustomButton_CustomClick"Width="100" Height="30"/></Border><TextBlock x:Name="statusText" Margin="0,20,0,0"/></StackPanel> </Window>
从上面代码可以看出 StackPanel ,Border, local:CustomButton 都绑定了自定义的路由事件
3.界面后台代码
using System.Windows; namespace 自定义路由事件 {public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}// 按钮自身的事件处理private void CustomButton_CustomClick(object sender, RoutedEventArgs e){statusText.Text = "按钮自身处理了CustomClick事件";}// 边框的事件处理private void Border_CustomClick(object sender, RoutedEventArgs e){statusText.Text += "\n边框也处理了CustomClick事件";// 如果想阻止事件继续冒泡,可以取消事件// e.Handled = true; }// StackPanel的事件处理private void StackPanel_CustomClick(object sender, RoutedEventArgs e){statusText.Text += "\nStackPanel也处理了CustomClick事件";}} }
从上到下控件一级一级的冒泡