WPF路由事件

一、路由事件的核心概念

路由事件(Routed Event)是 WPF 引入的事件系统,与传统 CLR 事件的主要区别在于:它可以在元素树中 "传播",允许多个元素对同一个事件进行处理。
 

元素树:WPF 的 UI 控件通过嵌套形成树形结构(如Grid包含StackPanelStackPanel包含Button),称为元素树。

事件源:触发事件的原始元素(如用户点击的Button)。

传播路径:事件从源元素出发,沿元素树向上或向下传播的路径。

二、路由事件的三种传播策略

路由事件有三种传播方式,决定了事件在元素树中的传播路径:
 
传播类型说明典型应用
冒泡(Bubble) 从事件源向上传播至元素树的根节点(由内向外) 普通交互事件(如ClickMouseDown
隧道(Tunnel) 从元素树的根节点向下传播至事件源(由外向内) 预览事件(如PreviewMouseDownPreviewKeyDown),通常用于提前拦截事件
直接(Direct) 仅在事件源上触发,不传播 类似传统 CLR 事件,如MouseEnter

简单理解 冒泡:最里面的控件传播至外面 例如Grid包含StackPanelStackPanel包含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事件";
        }
    }
}

从上到下控件一级一级的冒泡

posted @ 2025-09-24 18:53  灰色淡季  阅读(23)  评论(0)    收藏  举报