WPF 路由事件
一、什么是路由事件? 根据MSDN定义: 功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件。 实现定义:路由事件是由 类的实例支持的 CLR 事件, RoutedEvent 由事件 Windows Presentation Foundation (WPF) 系统处理。 典型的 WPF 应用程序中包含许多元素。 无论这些元素是在代码中创建还是在 XAML 中声明,它们存在于彼此关联的元素树关系中。 二、路由策略 路由事件使用以下三种路由策略之一: Bubbling【冒泡】: 调用事件源上的事件处理程序。 路由事件随后会路由到后续的父级元素,直到到达元素树的根。 大多数路由事件都使用冒泡路由策略。 冒泡路由事件通常用于报告来自不同控件或其他 UI 元素的输入或状态变化。 Direct【直接】: 只有源元素本身才有机会调用处理程序以进行响应。 这类似于窗体用于事件的Windows路由"。 但是,与标准 CLR 事件不同,直接路由事件支持类处理 (类处理在即将发布的) 节中进行了说明,并且 和 可以使用 EventSetter或 EventTrigger 。 Tunneling【隧道】: 最初将调用元素树的根处的事件处理程序。 随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。 合成控件的过程中通常会使用或处理隧道路由事件,通过这种方式,可以有意地禁止复合部件中的事件,或者将其替换为特定于整个控件的事件。 冒泡策略 事件的冒泡策略,就像水里的泡泡一样,从底往上,逐级触发。路由事件随后会路由到后续的父级元素,直到到达元素树的根。 大多数路由事件都使用冒泡路由策略。 冒泡路由事件通常用于报告来自不同控件或其他 UI 元素的输入或状态变化。
<Window x:Class="WpfApp1.OneWindow" 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:WpfApp1" mc:Ignorable="d" x:Name="w1" Title="OneWindow" Height="350" Width="500" Button.Click="btn1_Click"> <Grid x:Name="gdOuter" Button.Click="btn1_Click"> <StackPanel x:Name="sp1" Button.Click="btn1_Click"> <Grid x:Name="gd1" Button.Click="btn1_Click"> <Button x:Name="btn1" Content="点我" Click="btn1_Click" Margin="5" Padding="5" FontSize="18"></Button> </Grid> <RichTextBox x:Name="txtInfo" Margin="5" MinHeight="100" ></RichTextBox> </StackPanel> </Grid> </Window>
隧道策略
隧道策略事件,与冒泡策略刚好相反, 最初将调用元素树的根处的事件处理程序。 随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。
所有的隧道策略模式事件,都是以Preview开头。隧道事件有时又称作预览事件,这是由该对所使用的命名约定决定的。
隧道策略由顶至下,逐层下探,直至最好一个元素,如下所示:
<Window x:Class="WpfApp1.TwoWindow" 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:WpfApp1" mc:Ignorable="d" Name="w1" Title="TwoWindow" Height="350" Width="500" Button.PreviewMouseDown="btn1_PreviewMouseDown"> <Grid> <Grid x:Name="gdOuter" Button.PreviewMouseDown="btn1_PreviewMouseDown"> <StackPanel x:Name="sp1" Button.PreviewMouseDown="btn1_PreviewMouseDown"> <Grid x:Name="gd1" Button.PreviewMouseDown="btn1_PreviewMouseDown"> <Button x:Name="btn1" Content="点我" PreviewMouseDown="btn1_PreviewMouseDown" Margin="5" Padding="5" FontSize="18"></Button> </Grid> <RichTextBox x:Name="txtInfo" Margin="5" MinHeight="100" ></RichTextBox> </StackPanel> </Grid> </Grid> </Window>
注意:在 WPF 中提供的输入事件通常是以隧道/浮升对实现的。
事件阻止
在实际应用中,如果不想事件采用冒泡或隧道策略,向上或向下执行,则需要设置e.Handled=true即可,如下所示:
private void btn1_Click(object sender, RoutedEventArgs e) { this.txtInfo.AppendText(string.Format("当前响应事件对象:{0},响应事件原始对象:{1}\r\n", (sender as FrameworkElement).Name, e.OriginalSource)); e.Handled = true; }
三、后台添加路由事件
路由事件既可以通过XAML的方式,进行设置,也可以通过后台代码的方式进行设置,如下所示:
/// <summary> /// ThreeWindow.xaml 的交互逻辑 /// </summary> public partial class ThreeWindow : Window { public ThreeWindow() { InitializeComponent(); this.btn1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click)); this.gd1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click)); this.sp1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click)); this.gdOuter.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click)); this.w1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click)); } private void btn1_Click(object sender, RoutedEventArgs e) { this.txtInfo.AppendText(string.Format("当前响应事件对象:{0},响应事件原始对象:{1}\r\n", (sender as FrameworkElement).Name, e.OriginalSource)); //e.Handled = true; } }
四、自定义路由事件 创建自定义路由事件,步骤如下: 1、声明并注册路由事件。 2、为路由事件添加CLR事件包装器。 3、创建可以激发事件的方法。 具体操作步骤如下: 首先创建的自定义控件,继承自Button按钮,如下所示:
namespace WpfApp1 { /// <summary> /// 自定义路由事件 /// </summary> public class TimeButton:Button { /// <summary> /// 声明和注册路由事件 /// </summary> public static readonly RoutedEvent TimeEvent = EventManager.RegisterRoutedEvent("Time", RoutingStrategy.Bubble, typeof(EventHandler<TimeEventArgs>), typeof(TimeButton)); /// <summary> /// 事件包装器 /// </summary> public event RoutedEventHandler Time{ add { this.AddHandler(TimeEvent, value); } remove { this.RemoveHandler(TimeEvent, value); } } /// <summary> /// 重写方法,激发事件 /// </summary> protected override void OnClick() { base.OnClick(); TimeEventArgs e = new TimeEventArgs(TimeEvent,this); e.ClickTime = DateTime.Now; this.RaiseEvent(e); } } public class TimeEventArgs : RoutedEventArgs { public TimeEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { } public DateTime ClickTime { get; set; } } }
在窗体中,创建自定义按钮实例。如下所示:
<Window x:Class="WpfApp1.A1Window" 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:WpfApp1" mc:Ignorable="d" Title="A1Window" Height="450" Width="800"> <Grid x:Name="gd1" local:TimeButton.Time="timeButton1_Time"> <StackPanel x:Name="sp1" local:TimeButton.Time="timeButton1_Time"> <local:TimeButton x:Name="timeButton1" Content="报时" Time="timeButton1_Time"></local:TimeButton> <RichTextBox x:Name="txtInfo" Margin="5" MinHeight="100" ></RichTextBox> </StackPanel> </Grid> </Window>
然后实现事件函数,如下所示: private void timeButton1_Time(object sender, TimeEventArgs e) { this.txtInfo.AppendText(string.Format("当前响应事件对象:{0},响应事件时间为:{1}\r\n", (sender as FrameworkElement).Name, e.ClickTime.ToString("yyyy-MM-dd hh:mm:ss.fff"))); }
来源:https://blog.csdn.net/weixin_48083386/article/details/139350212