zoukankan      html  css  js  c++  java
  • 走进WPF之路由事件

    为了降低由事件订阅带来的耦合度,和代码量,WPF推出了路由事件机制。路由事件与直接事件的区别在于,直接事件激发时,发送者直接将消息通过事件订阅者交给事件响应者,事件响应者对事件的发生做出响应。路由事件的订阅者和响应者之间没有直接显式的订阅关系,事件的拥有者只负责激发事件,事件由谁响应它并不知道,事件响应者通过事件侦听器进行侦听。本文以一些简单的小例子,简述路由事件的基本使用,仅供学习分享使用,如有不足之处,还请指正。

    什么是路由事件?

    根据MSDN定义:

    • 功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件。
    • 实现定义:路由事件是由 类的实例支持的 CLR 事件, RoutedEvent 由事件 Windows Presentation Foundation (WPF) 系统处理。

    典型的 WPF 应用程序中包含许多元素。 无论这些元素是在代码中创建还是在 XAML 中声明,它们存在于彼此关联的元素树关系中。 

    路由策略

    路由事件使用以下三种路由策略之一:

    • Bubbling【冒泡】: 调用事件源上的事件处理程序。 路由事件随后会路由到后续的父级元素,直到到达元素树的根。 大多数路由事件都使用冒泡路由策略。 冒泡路由事件通常用于报告来自不同控件或其他 UI 元素的输入或状态变化。

    • Direct【直接】: 只有源元素本身才有机会调用处理程序以进行响应。 这类似于窗体用于事件的Windows路由"。 但是,与标准 CLR 事件不同,直接路由事件支持类处理 (类处理在即将发布的) 节中进行了说明,并且 和 可以使用 EventSetter或 EventTrigger 。

    • Tunneling【隧道】: 最初将调用元素树的根处的事件处理程序。 随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。 合成控件的过程中通常会使用或处理隧道路由事件,通过这种方式,可以有意地禁止复合部件中的事件,或者将其替换为特定于整个控件的事件。

    冒泡策略

    事件的冒泡策略,就像水里的泡泡一样,从底往上,逐级触发。路由事件随后会路由到后续的父级元素,直到到达元素树的根。 大多数路由事件都使用冒泡路由策略。 冒泡路由事件通常用于报告来自不同控件或其他 UI 元素的输入或状态变化。

    路由事件会像泡泡一样,逐层响应,示例如下所示:

     示例源码

     1 <Window x:Class="WpfApp1.OneWindow"
     2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     6         xmlns:local="clr-namespace:WpfApp1"
     7         mc:Ignorable="d"
     8         x:Name="w1"
     9         Title="OneWindow" Height="350" Width="500" Button.Click="btn1_Click">
    10     <Grid x:Name="gdOuter" Button.Click="btn1_Click">
    11         <StackPanel x:Name="sp1" Button.Click="btn1_Click">
    12             <Grid x:Name="gd1" Button.Click="btn1_Click">
    13                 <Button x:Name="btn1" Content="点我" Click="btn1_Click" Margin="5" Padding="5" FontSize="18"></Button>
    14             </Grid>
    15             <RichTextBox x:Name="txtInfo" Margin="5" MinHeight="100"  ></RichTextBox>
    16         </StackPanel>
    17     </Grid>
    18 </Window>

    隧道策略

    隧道策略事件,与冒泡策略刚好相反, 最初将调用元素树的根处的事件处理程序。 随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。

    所有的隧道策略模式事件,都是以Preview开头。隧道事件有时又称作预览事件,这是由该对所使用的命名约定决定的。

    隧道策略由顶至下,逐层下探,直至最好一个元素,如下所示:

     示例源码

     1 <Window x:Class="WpfApp1.TwoWindow"
     2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     6         xmlns:local="clr-namespace:WpfApp1"
     7         mc:Ignorable="d"
     8         Name="w1"
     9         Title="TwoWindow" Height="350" Width="500" Button.PreviewMouseDown="btn1_PreviewMouseDown">
    10     <Grid>
    11         <Grid x:Name="gdOuter" Button.PreviewMouseDown="btn1_PreviewMouseDown">
    12             <StackPanel x:Name="sp1" Button.PreviewMouseDown="btn1_PreviewMouseDown">
    13                 <Grid x:Name="gd1" Button.PreviewMouseDown="btn1_PreviewMouseDown">
    14                     <Button x:Name="btn1" Content="点我" PreviewMouseDown="btn1_PreviewMouseDown" Margin="5" Padding="5" FontSize="18"></Button>
    15                 </Grid>
    16                 <RichTextBox x:Name="txtInfo" Margin="5" MinHeight="100"  ></RichTextBox>
    17             </StackPanel>
    18         </Grid>
    19     </Grid>
    20 </Window>

    注意:在 WPF 中提供的输入事件通常是以隧道/浮升对实现的。

    事件阻止

    在实际应用中,如果不想事件采用冒泡或隧道策略,向上或向下执行,则需要设置e.Handled=true即可,如下所示:

     示例源码:

    1 private void btn1_Click(object sender, RoutedEventArgs e)
    2 {
    3       this.txtInfo.AppendText(string.Format("当前响应事件对象:{0},响应事件原始对象:{1}
    ", (sender as FrameworkElement).Name, e.OriginalSource));
    4       e.Handled = true;
    5 }

    后台添加路由事件

    路由事件既可以通过XAML的方式,进行设置,也可以通过后台代码的方式进行设置,如下所示:

     后台设置路由事件,如下所示:

     1     /// <summary>
     2     /// ThreeWindow.xaml 的交互逻辑
     3     /// </summary>
     4     public partial class ThreeWindow : Window
     5     {
     6         public ThreeWindow()
     7         {
     8             InitializeComponent();
     9             this.btn1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click));
    10             this.gd1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click));
    11             this.sp1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click));
    12             this.gdOuter.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click));
    13             this.w1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click));
    14         }
    15 
    16 
    17         private void btn1_Click(object sender, RoutedEventArgs e)
    18         {
    19             this.txtInfo.AppendText(string.Format("当前响应事件对象:{0},响应事件原始对象:{1}
    ", (sender as FrameworkElement).Name, e.OriginalSource));
    20             //e.Handled = true;
    21         }
    22     }

    自定义路由事件

    创建自定义路由事件,步骤如下:

    1. 声明并注册路由事件。
    2. 为路由事件添加CLR事件包装器。
    3. 创建可以激发事件的方法。

    具体操作步骤如下:

    首先创建的自定义控件,继承自Button按钮,如下所示:

     1 namespace WpfApp1
     2 {
     3     /// <summary>
     4     /// 自定义路由事件
     5     /// </summary>
     6     public class TimeButton:Button
     7     {
     8         /// <summary>
     9         /// 声明和注册路由事件
    10         /// </summary>
    11         public static readonly RoutedEvent TimeEvent = EventManager.RegisterRoutedEvent("Time", RoutingStrategy.Bubble, typeof(EventHandler<TimeEventArgs>), typeof(TimeButton));
    12 
    13         /// <summary>
    14         /// 事件包装器
    15         /// </summary>
    16         public event RoutedEventHandler Time{
    17             add { this.AddHandler(TimeEvent, value); }
    18             remove
    19             {
    20                 this.RemoveHandler(TimeEvent, value);
    21             }
    22         }
    23 
    24         /// <summary>
    25         /// 重写方法,激发事件
    26         /// </summary>
    27         protected override void OnClick()
    28         {
    29             base.OnClick();
    30             TimeEventArgs e = new TimeEventArgs(TimeEvent,this);
    31             e.ClickTime = DateTime.Now;
    32             this.RaiseEvent(e);
    33         }
    34         
    35     }
    36 
    37     public class TimeEventArgs : RoutedEventArgs {
    38         public TimeEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { 
    39         
    40         }
    41 
    42         public DateTime ClickTime { get; set; }
    43     }
    44 }

    在窗体中,创建自定义按钮实例。如下所示:

     1 <Window x:Class="WpfApp1.A1Window"
     2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     6         xmlns:local="clr-namespace:WpfApp1"
     7         mc:Ignorable="d"
     8         Title="A1Window" Height="450" Width="800">
     9     <Grid x:Name="gd1" local:TimeButton.Time="timeButton1_Time">
    10         <StackPanel x:Name="sp1" local:TimeButton.Time="timeButton1_Time">
    11             <local:TimeButton x:Name="timeButton1" Content="报时" Time="timeButton1_Time"></local:TimeButton>
    12             <RichTextBox x:Name="txtInfo" Margin="5" MinHeight="100"  ></RichTextBox>
    13         </StackPanel>
    14     </Grid>
    15 </Window>

    然后实现事件函数,如下所示:

    1 private void timeButton1_Time(object sender, TimeEventArgs e)
    2 {
    3       this.txtInfo.AppendText(string.Format("当前响应事件对象:{0},响应事件时间为:{1}
    ", (sender as FrameworkElement).Name, e.ClickTime.ToString("yyyy-MM-dd hh:mm:ss.fff")));
    4 }

    自定义路由事件,示例截图如下:

     以上就是WPF路由事件的简单用法,旨在抛砖引玉,共同学习,一起进步。

    备注

    题李凝幽居

    【作者】贾岛 【朝代】唐

    闲居少邻并,草径入荒园。鸟宿池边树,僧敲月下门。

    过桥分野色,移石动云根。暂去还来此,幽期不负言。


    作者:Alan.hsiang
    出处:http://www.cnblogs.com/hsiang/
    本文版权归作者和博客园共有,写文不易,支持原创,欢迎转载【点赞】,转载请保留此段声明,且在文章页面明显位置给出原文连接,谢谢。
    关注个人公众号,定时同步更新技术及职场文章

  • 相关阅读:
    RABC权限管理
    七牛云上传
    支付宝沙箱支付(Django端)超适合小白哦~
    ModelSerialzier + ModelViewSet基础使用
    微博三方登录
    Celery梳理
    ios 动画
    ios 贝塞尔动画
    ios 贝塞尔
    ios Masonry 开发细节
  • 原文地址:https://www.cnblogs.com/hsiang/p/15496604.html
Copyright © 2011-2022 走看看