翼度科技»论坛 编程开发 .net 查看内容

10.路由事件

2

主题

2

帖子

6

积分

新手上路

Rank: 1

积分
6
先看一段代码:
  1. <Window x:Class="HelloWorld.MainWindow"
  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:HelloWorld"
  7.         xmlns:controls="clr-namespace:HelloWorld.Controls"
  8.         xmlns:helper="clr-namespace:HelloWorld.MVVM"
  9.         mc:Ignorable="d"
  10.         Title="WPF中文网 - www.wpfsoft.com" Height="350" Width="500">
  11.     <Window.DataContext>
  12.         <local:MainViewModel/>
  13.     </Window.DataContext>
  14.     <Border>
  15.         <Canvas>
  16.             <Button Content="确定" Width="80" Height="30" Canvas.Left="150" Canvas.Top="100"/>
  17.             <Button Content="取消" Width="80" Height="30" Canvas.Left="280" Canvas.Top="100"/>
  18.         </Canvas>
  19.     </Border>
  20. </Window>
复制代码
这个布局最终形成的逻辑树应该是下面这个样子
  1. <Window>
  2.     <Border>
  3.         <Canvas>
  4.             <Button/>
  5.             <Button/>
  6.         </Canvas>
  7.     </Border>
  8. </Window>
复制代码
在WPF的元素树中,若某一个元素引发了一个事件,那么这个事件会沿着整棵树进行传播,而开发者可以在事件传播的沿途进行侦听(有点像设立关卡打劫)。一旦侦听到这个事件,便可以执行事件的回调函数。当然,只有被声明为RoutedEvent路由事件才具备传播功能
隧道事件:
我们以上面的代码为例,假如用户单击了确定按钮,此时,从整个窗体的视角来看,窗体会说,哎呀,你把我给单击了,且单击的是我其中的确定按钮,于是,这个单击事件首先会经历第一个关卡——Window对象。如果开发者恰好订阅了Window对象的单击事件,那首先被执行的就是Window窗体的单击事件回调函数。紧接着单击事件会经历第二道关卡——Border对象,第三道关卡——Canvas对象,直到确定按钮为止。可见这个单击事件一路经历了4个控件,它们分别是Window->Border->Canvas->Button,开发者可以在这4个控件上去订阅单击事件。像这个从根目录一直路由到事件源对象的路由事件,我们称为隧道事件(预览事件),这类事件都是Preview单词开头
冒泡事件:
但是,从Button按钮的视角,用户肯定是先单击的我呀,我这里才是事件源,事件应该就像小孩向水中投石之后,平静的水面会泛起一圈圈的涟漪,最终消失在岸边——即最外层的Window窗体对象。那么,此时的事件路由方向就反过来了,Button->Canvas->Border->Window。像这种从事件源一直路由到元素树根的路由事件,我们称为冒泡事件。
直接事件:
除了这两种事件,WPF还支持直接事件,即只有事件源才能响应触发的事件,我们称为直接事件。三种事件由定义事件时通过RoutingStrategy 枚举进行标识。
  1. public enum RoutingStrategy
  2. {
  3.     //
  4.     // 摘要:
  5.     //     路由事件使用隧道策略,以便事件实例通过树向下路由(从根到源元素)。
  6.     Tunnel = 0,
  7.     //
  8.     // 摘要:
  9.     //     路由事件使用冒泡策略,以便事件实例通过树向上路由(从事件元素到根)。
  10.     Bubble = 1,
  11.     //
  12.     // 摘要:
  13.     //     路由事件不通过元素树路由,但支持其他路由的事件功能。
  14.     Direct = 2
  15. }
复制代码
我们为Window、Border、Canvas、Button的PreviewMouseUp隧道事件都订阅了回调函数,然后F5调试运行,并单击确定按钮,观察输出结果如下:
  1. Window对象的隧道事件PreviewMouseUp被触发
  2. Border对象的隧道事件PreviewMouseUp被触发
  3. Canvas对象的隧道事件PreviewMouseUp被触发
  4. Button确定按钮的隧道事件PreviewMouseUp被触发
复制代码
击的确定Button按钮,但是首先被触发的却是元素树的根元素Window对象,最后才是Button对象
 
我们为Window、Border、Canvas、Button的MouseUp冒泡事件都订阅了回调函数,然后F5调试运行,并单击确定按钮,观察输出结果如下
  1. Canvas对象的冒泡事件MouseUp被触发
  2. Border对象的冒泡事件MouseUp被触发
  3. Window对象的冒泡事件MouseUp被触发
复制代码
总结:第一点,从输出结果看,隧道事件是从根元素路由到事件源,冒泡事件是从事件源路由到根元素。第二点,如果同时订阅了隧道事件和冒泡事件,那么两条路由路线都将执行。谁先谁后?隧道事件先完成路由
 
路由事件实践:
注册一个路由事件,是向WPF的事件系统注册的,由静态类EventManager提供注册服务,具体则交给RegisterRoutedEvent方法成员完成。
  1. public static readonly RoutedEvent 路由事件名称 = EventManager.RegisterRoutedEvent(
  2.             name: "路由事件名称",
  3.             routingStrategy: 冒泡事件/隧道事件/直接事件,
  4.             handlerType: 路由事件委托的反射实例,
  5.             ownerType: 路由事件拥有者的反射实例;
复制代码
当一个路由事件注册到WPF的事件系统之后,还需要利用event关键词对其进行包装,包装成普通事件的样子,方便开发者调用。
  1. /// <summary>
  2. /// 通过event包装成普通事件的外观
  3. /// </summary>
  4. public event RoutedEventHandler 普通事件名称
  5. {
  6.     add { AddHandler(路由事件名称, value); }
  7.     remove { RemoveHandler(路由事件名称, value); }
  8. }
复制代码
最后,寻找合适的时候,利用UIElement基类中的RaiseEvent,触发这个路由事件即可。这样就完成了路由事件的注册流程。
  1. <Window x:Class="HelloWorld.MainWindow"
  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:HelloWorld"
  7.         xmlns:controls="clr-namespace:HelloWorld.Controls"
  8.         xmlns:helper="clr-namespace:HelloWorld.MVVM"
  9.         mc:Ignorable="d"
  10.         Title="WPF中文网 - www.wpfsoft.com" Height="350" Width="500">
  11.     <Window.DataContext>
  12.         <local:MainViewModel/>
  13.     </Window.DataContext>
  14.     <Border>
  15.         <Canvas>
  16.             <Button Content="确定" Width="80" Height="30" Canvas.Left="150" Canvas.Top="100"/>
  17.             <Button Content="取消" Width="80" Height="30" Canvas.Left="280" Canvas.Top="100"/>
  18.         </Canvas>
  19.     </Border>
  20. </Window><Window x:Class="HelloWorld.MainWindow"
  21.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  22.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  23.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  24.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  25.         xmlns:local="clr-namespace:HelloWorld"
  26.         xmlns:controls="clr-namespace:HelloWorld.Controls"
  27.         xmlns:helper="clr-namespace:HelloWorld.MVVM"
  28.         mc:Ignorable="d"
  29.         Title="WPF中文网 - www.wpfsoft.com" Height="350" Width="500">
  30.     <Window.DataContext>
  31.         <local:MainViewModel/>
  32.     </Window.DataContext>
  33.     <Border>
  34.         <Canvas>
  35.             <Button Content="确定" Width="80" Height="30" Canvas.Left="150" Canvas.Top="100"/>
  36.             <Button Content="取消" Width="80" Height="30" Canvas.Left="280" Canvas.Top="100"/>
  37.         </Canvas>
  38.     </Border>
  39. </Window><Window x:Class="HelloWorld.MainWindow"
  40.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  41.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  42.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  43.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  44.         xmlns:local="clr-namespace:HelloWorld"
  45.         xmlns:controls="clr-namespace:HelloWorld.Controls"
  46.         xmlns:helper="clr-namespace:HelloWorld.MVVM"
  47.         mc:Ignorable="d"
  48.         Title="WPF中文网 - www.wpfsoft.com" Height="350" Width="500">
  49.     <Window.DataContext>
  50.         <local:MainViewModel/>
  51.     </Window.DataContext>
  52.     <Border>
  53.         <Canvas>
  54.             <Button Content="确定" Width="80" Height="30" Canvas.Left="150" Canvas.Top="100"/>
  55.             <Button Content="取消" Width="80" Height="30" Canvas.Left="280" Canvas.Top="100"/>
  56.         </Canvas>
  57.     </Border>
  58. </Window>            
复制代码
Widget自定义控件的后端代码
  1. /// <summary>
  2. /// Widget.xaml 的交互逻辑
  3. /// </summary>
  4. public partial class Widget : UserControl
  5. {  
  6.     public Widget()
  7.     {
  8.         InitializeComponent();
  9.         DataContext = this;
  10.     }
  11.     /// <summary>
  12.     /// 注册RoutedEvent路由事件
  13.     /// </summary>
  14.     public static readonly RoutedEvent <strong>CompletedEvent</strong> = EventManager.RegisterRoutedEvent(
  15.         name: "CompletedEvent",
  16.         routingStrategy: RoutingStrategy.Bubble,
  17.         handlerType: typeof(RoutedEventHandler),
  18.         ownerType: typeof(Widget));
  19.     /// <summary>
  20.     /// 通过event包装成普通事件的外观
  21.     /// </summary>
  22.     public event RoutedEventHandler <strong>Completed</strong>
  23.     {
  24.         add { AddHandler(CompletedEvent, value); }
  25.         remove { RemoveHandler(CompletedEvent, value); }
  26.     }
  27.     /// <summary>
  28.     /// 触发路由事件
  29.     /// </summary>
  30.     void<strong> RaiseRoutedEvent</strong>()
  31.     {
  32.         RoutedEventArgs routedEventArgs = new RoutedEventArgs(<strong>CompletedEvent</strong>, this);
  33.         RaiseEvent(routedEventArgs);
  34.     }
  35.     public string Icon
  36.     {
  37.         get { return (string)GetValue(IconProperty); }
  38.         set { SetValue(IconProperty, value); }
  39.     }
  40.     public static readonly DependencyProperty IconProperty =
  41.         DependencyProperty.Register("Icon", typeof(string), typeof(Widget),
  42.             new PropertyMetadata("❤"));
  43.     public string Title
  44.     {
  45.         get { return (string)GetValue(TitleProperty); }
  46.         set { SetValue(TitleProperty, value); }
  47.     }
  48.     public static readonly DependencyProperty TitleProperty =
  49.         DependencyProperty.Register("Title", typeof(string), typeof(Widget),
  50.             new PropertyMetadata("请输入标题"));
  51.     /// <summary>
  52.     /// 销售目标
  53.     /// </summary>
  54.     public double Target
  55.     {
  56.         get { return (double)GetValue(TargetProperty); }
  57.         set { SetValue(TargetProperty, value); }
  58.     }
  59.     public static readonly DependencyProperty TargetProperty =
  60.         DependencyProperty.Register("Target", typeof(double), typeof(Widget),
  61.             new PropertyMetadata(0.0));
  62. //依赖属性
  63.     public double Value
  64.     {
  65.         get { return (double)GetValue(ValueProperty); }
  66.         set { SetValue(ValueProperty, value); }
  67.     }
  68. /*当Value大于Target时,触发Completed事件*/
  69.     public static readonly DependencyProperty ValueProperty =
  70.         DependencyProperty.Register("Value", typeof(double), typeof(Widget),
  71.             new PropertyMetadata(0.0,new PropertyChangedCallback(<strong>OnValuePropertyChangedCallback</strong>)));
  72.     private static void<strong> OnValuePropertyChangedCallback</strong>(DependencyObject d,
  73.         DependencyPropertyChangedEventArgs e)
  74.     {
  75.         if(d is Widget control && e.NewValue is double value )
  76.         {
  77.             if (value >= control.Target && control.Target != 0)//当业绩大于100万美元时
  78.             {
  79.                 control.Icon = "☻";                    
  80.                 control.<strong>RaiseRoutedEvent</strong>();//触发路由事件,完成销售目标
  81.             }
  82.             else
  83.             {
  84.                 control.Icon = "❤";
  85.             }
  86.         }
  87.     }
  88. }
复制代码
我们在ValueProperty 的回调函数中去判断销售业务,如果完成销售目标,就引发完成事件。接下来演示怎么使用这个控件。首先在主窗体中实例化Widget,并将其Value绑定到一个Slider的Value
  1. <Window x:Class="HelloWorld.MainWindow"
  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:HelloWorld"
  7.         xmlns:controls="clr-namespace:HelloWorld.Controls"
  8.         xmlns:helper="clr-namespace:HelloWorld.MVVM"
  9.         mc:Ignorable="d"
  10.         Title="WPF中文网 - www.wpfsoft.com" Height="350" Width="500">
  11.     <Window.DataContext>
  12.         <local:MainViewModel/>
  13.     </Window.DataContext>
  14.     <Border>
  15.         <Canvas>
  16.             <Button Content="确定" Width="80" Height="30" Canvas.Left="150" Canvas.Top="100"/>
  17.             <Button Content="取消" Width="80" Height="30" Canvas.Left="280" Canvas.Top="100"/>
  18.         </Canvas>
  19.     </Border>
  20. </Window>                                                
复制代码
当用户拖动Slider时,Widget的Value值就跟着改变,并判断当前销售业绩与目标业绩,我们像订阅普通事件一样,Completed="Widget_Completed"表示订阅完成事件。
  1. private void Widget_Completed(object sender, RoutedEventArgs e)
  2. {
  3.     Widget widget = sender as Widget;
  4.     listBox.Items.Insert(0, $"完成目标销售额:{widget.Value}");
  5. }
复制代码
CompletedEvent路由事件在注册时被注册成RoutingStrategy.Bubble类型,即冒泡事件,如果想注册成隧道事件,建议取名为:PreviewCompletedEvent,并使用PreviewCompleted进行包装。
 

来源:https://www.cnblogs.com/MingQiu/p/17999408
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具