WPF路由事件

发布时间 2023-12-05 14:40:37作者: ZHIZRL

冒泡事件和隧道事件

WPF路由事件是一种特殊类型的事件,它提供了更强的传播能力。这种事件可以在元素树中向上冒泡和向下隧道传播,沿传播路径被事件处理程序处理。换句话说,路由事件是针对元素树中的多个侦听器(而不仅仅是引发该事件的对象)调用处理程序的事件。

逻辑数与可视化树

逻辑树由布局组件和控件组成,其最显著的特点是完全由布局组件和控件构成,包括列表类控件中的条目元素,换句话说,它的每个节点都是布局组件或控件。这种树形结构反映了界面的布局和控件的逻辑关系。例如,一个窗口可能包含多个面板,每个面板又包含多个按钮,这就是逻辑树的一种表达方式。

可视化树是用户界面的实际渲染结果,它是由可视化组件构成的。这些可视化组件可以是文本框、图片框、按钮等用户可以直接交互的界面元素。可视化树的构建是基于逻辑树的,但它会根据具体的视觉属性(如大小、位置等)进行渲染。

 

鼠标的冒泡事件与隧道事件

在WPF中带有Mouse字样的为鼠标事件

不带有Preview字样为鼠标冒泡事件

带有Preview字样为鼠标隧道事件

编写测试程序

可视化树上所有子节点添加鼠标点击的隧道事件与冒泡事件

<UserControl x:Class="Zhaoxi.EventLesson.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Zhaoxi.EventLesson"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             MouseLeftButtonDown="UserControl_MouseLeftButtonDown"
             PreviewMouseLeftButtonDown="UserControl_PreviewMouseLeftButtonDown">
    
    <Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown" Background="Transparent"
          ButtonBase.Click="Grid_Click"
          PreviewMouseLeftButtonDown="Grid_PreviewMouseLeftButtonDown"
          QueryCursor="Grid_QueryCursor">
        
        <Border Width="200" Height="40" Background="Orange"
                MouseLeftButtonDown="Border_MouseLeftButtonDown"
                PreviewMouseLeftButtonDown="Border_PreviewMouseLeftButtonDown"/>
    </Grid>
</UserControl>

 代码中添加相关事件的方法

        private void UserControl_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======UserControl_PreviewMouseLeftButtonDown=======");
        }
        private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======UserControl_MouseLeftButtonDown=======");
        }
        private void Grid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Grid_PreviewMouseLeftButtonDown=======");
        }
        private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Grid_MouseLeftButtonDown=======");
        }
        private void Border_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Border_PreviewMouseLeftButtonDown=======");
        }
        private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Border_MouseLeftButtonDown=======");
        }

运行后点击Border在输出窗口中打印结果如下,可以清晰看到事件的前后顺序,先隧道后冒泡

======UserControl_PreviewMouseLeftButtonDown=======
======Grid_PreviewMouseLeftButtonDown=======
======Border_PreviewMouseLeftButtonDown=======
======Border_MouseLeftButtonDown=======
======Grid_MouseLeftButtonDown=======
======UserControl_MouseLeftButtonDown=======

事件拦截

方法中使用e.Handled = true;进行事件拦截

在Grid_PreviewMouseLeftButtonDown处拦截

        private void Grid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Grid_PreviewMouseLeftButtonDown=======");
            e.Handled = true;
        }

运行后点击Border在输出窗口中打印结果如下

======UserControl_PreviewMouseLeftButtonDown=======
======Grid_PreviewMouseLeftButtonDown=======

事件传递在Grid_PreviewMouseLeftButtonDown处截至,后续的事件不再执行

在Grid_MouseLeftButtonDown处拦截

        private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Grid_MouseLeftButtonDown=======");
            e.Handled = true;
        }

运行后点击Border在输出窗口中打印结果如下

======UserControl_PreviewMouseLeftButtonDown=======
======Grid_PreviewMouseLeftButtonDown=======
======Border_PreviewMouseLeftButtonDown=======
======Border_MouseLeftButtonDown=======
======Grid_MouseLeftButtonDown=======

事件传递在Grid_MouseLeftButtonDown处截至,后续的事件不再执行

事件拦截后强制执行

取消WPF中UserControl的MouseLeftButtonDown="UserControl_MouseLeftButtonDown"

改用代码中使用AddHandler方法挂在UserControl的MouseLeftButtonDown事件,并在AddHandler的参数bool handledEventsToo传进true强制执行

public UserControl1()
        {
            InitializeComponent();

            // 强调:鼠标左键的冒泡事件
            // 事件消息被Handled的时候,依然执行
            this.AddHandler(
                MouseLeftButtonDownEvent,
                new MouseButtonEventHandler(UserControl_MouseLeftButtonDown),
                true);
        }

在Border_MouseLeftButtonDown处拦截

        private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Border_MouseLeftButtonDown=======");
            e.Handled = true;
        }

运行后点击Border在输出窗口中打印结果如下

======UserControl_PreviewMouseLeftButtonDown=======
======Grid_PreviewMouseLeftButtonDown=======
======Border_PreviewMouseLeftButtonDown=======
======Border_MouseLeftButtonDown=======
======UserControl_MouseLeftButtonDown=======

Grid_MouseLeftButtonDown被拦截,但UserControl_MouseLeftButtonDown被强制执行

特殊情况——Button_Click

编写WPF测试代码

<UserControl x:Class="Zhaoxi.EventLesson.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Zhaoxi.EventLesson"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             PreviewMouseLeftButtonDown="UserControl_PreviewMouseLeftButtonDown">
    
    <Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown" Background="Transparent"
          ButtonBase.Click="Grid_Click"
          PreviewMouseLeftButtonDown="Grid_PreviewMouseLeftButtonDown"
          QueryCursor="Grid_QueryCursor">
        
        <Button Width="200" Height="40" Background="Orange"
                MouseLeftButtonDown="Button_MouseLeftButtonDown"
                PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
                Click="Button_Click"/>

    </Grid>
</UserControl>

 代码中添加相关事件的方法

    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();

            this.Unloaded += UserControl1_Unloaded;

            // 强调:鼠标左键的冒泡事件
            // 事件消息被Handled的时候,依然执行
            this.AddHandler(
                MouseLeftButtonDownEvent,
                new MouseButtonEventHandler(UserControl_MouseLeftButtonDown),
                true);
        }
        private void UserControl1_Unloaded(object sender, RoutedEventArgs e)
        {

        }
        private void UserControl_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======UserControl_PreviewMouseLeftButtonDown=======");
        }
        private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======UserControl_MouseLeftButtonDown=======");
        }
        private void Grid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Grid_PreviewMouseLeftButtonDown=======");
        }
        private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Grid_MouseLeftButtonDown=======");
        }
        private void Border_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Border_PreviewMouseLeftButtonDown=======");
        }
        private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Border_MouseLeftButtonDown=======");
        }
        private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Button_PreviewMouseLeftButtonDown=======");
        }
        private void Button_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("======Button_MouseLeftButtonDown=======");
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Debug.WriteLine("======Button_Click=======");
        }
        private void Grid_Click(object sender, RoutedEventArgs e)
        {
            Debug.WriteLine("======Grid_Click=======");
        }
    }

运行后点击Button在输出窗口中打印结果如下

======UserControl_PreviewMouseLeftButtonDown=======
======Grid_PreviewMouseLeftButtonDown=======
======Button_PreviewMouseLeftButtonDown=======
======UserControl_MouseLeftButtonDown=======
======Button_Click=======
======Grid_Click=======

输出结果中Button_MouseLeftButtonDown和Grid_MouseLeftButtonDown没有执行但UserControl_MouseLeftButtonDown被执行

是因为UserControl_MouseLeftButtonDown是强制执行,说明Button_PreviewMouseLeftButtonDown执行完成后事件被拦截

Button_Click执行时机在鼠标点击抬起后先执行冒牌事件再执行单击事件

Grid_Click类似Button_Click的冒泡事件,但Grid中没有Button_Click事件所有需要使用ButtonBase.Click="Grid_Click"来侦听Button_Click的冒泡事件,Button_Click的定义再ButtonBase中

特殊情况——TextBox_TextInput

编写WPF测试代码

    <StackPanel>
        <TextBox TextInput="TextBox_TextInput"
                 PreviewTextInput="TextBox_PreviewTextInput"
                 TextChanged="TextBox_TextChanged"/>
        <TextBox/>
    </StackPanel>

 代码中添加相关事件的方法

    public partial class KeyboardEventWindows : Window
    {
        public KeyboardEventWindows()
        {
            InitializeComponent();
        }

        private void TextBox_TextInput(object sender, TextCompositionEventArgs e)
        {
            Debug.WriteLine("=================TextBox_TextInput================");
        }

        private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            Debug.WriteLine("=================TextBox_PreviewTextInput================");
        }

        private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            Debug.WriteLine("=================TextBox_TextChanged================");
        }
    }

输入字符后输出窗口中打印结果如下

TextBox_TextInput事件不会触发,但可以用TextBox_TextChanged替代

当输入回车时TextBox_PreviewTextInput会被触发但TextBox_TextChanged不会