WPF 路由事件2

news/2024/11/10 15:04:27/文章来源:https://www.cnblogs.com/ywtssydm/p/18381107

1什么是路由事件

简单说,路由事件可以沿着视觉树VisualTree进行传递,在这视觉树中的所有对象都可以收到这个事件。前提是添加了检测。

1.1什么是逻辑树LogicalTree

简单理解:逻辑树就是我们在xaml中写的布局逻辑

如xaml代码:

复制代码
<Grid><StackPanel Orientation="Vertical"><DockPanel ButtonBase.Click="Button_Click"><Button Click="Button_Click" Content="right" DockPanel.Dock="Right"/><Button Content="left" DockPanel.Dock="Left"/><Button Content="top" DockPanel.Dock="Top"/><Button Content="bottom" DockPanel.Dock="Bottom"/></DockPanel></StackPanel>
</Grid>
复制代码

逻辑就是Grid--StackPanel--CockPanel--Button

1.2什么是视觉树VisualTree

 视觉树更复杂,在逻辑树上又添加了更详细的布局,包含看不见的对象,和控件模板内部的对象,如Button的控件模板内部有一个Border--ConentPresenter--TextBlock

 在视觉树上,红色框是逻辑树上有的,蓝色框是button内部的控件

在这个视觉树上,顶部元素是window(最外边),最内部是TextBlock,那就会有2个方向,从最外到最内,从最内到最外,

从内到外叫 冒泡。

从外到内叫 隧道

wpf中一个事件以Preview开头的都是隧道事件,如PreviewDrop是隧道事件,不带Preview的Drop事件是冒泡事件。

一个完整的路由事件的执行顺序是先隧道从window传递到内部,再冒泡从最内传递到最外

比如Button的Click事件,是先由窗体接收到鼠标的点击操作,先隧道传到Button,在冒泡传回窗体,在这个过程中,视觉树VisualTree中的任何对象都可以接收到Button的Click事件,前提是已添加了接收的操作

简单来说,逻辑树是在xaml中我们布局时的逻辑,视觉树是在逻辑树的基础上还包含了看不见的对象和控件模板内的对象

一个路由事件可以使用e.Handl=True,结束传递。

2.如何定义

还是上面的例子,让Grid、StackPanel、DockPanel都接收Button的Click事件。

xaml中:

复制代码
 <Grid ButtonBase.Click="Button_Click" ><StackPanel ButtonBase.Click="Button_Click"  Orientation="Vertical"   ><DockPanel ButtonBase.Click="Button_Click" ><Button Click="Button_Click" Content="right" DockPanel.Dock="Right" /><Button Content="left" DockPanel.Dock="Left"/><Button Content="top" DockPanel.Dock="Top"/><Button Content="bottom" DockPanel.Dock="Bottom"/></DockPanel></StackPanel></Grid>
复制代码

Button_Click事件代码:

private void Button_Click(object sender, RoutedEventArgs e){Console.WriteLine("Button_Click由 " + sender.ToString()+" 触发"); }

输出结果:

或者在代码中添加:

复制代码
public MainWindow(){InitializeComponent();this.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click));this.grid.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click));this.stackpanel.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click));this.dockpanel.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click));this.button.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click));}
复制代码

输出结果:

3.代码例子

xaml:

复制代码
<Grid Name="grid"  ><StackPanel Name="stackpanel"  Orientation="Vertical"   ><DockPanel Name="dockpanel" ><Button Name="button"  Content="right" DockPanel.Dock="Right" /><Button Content="left" DockPanel.Dock="Left"/><Button Content="top" DockPanel.Dock="Top"/><Button Content="bottom" DockPanel.Dock="Bottom"/></DockPanel></StackPanel></Grid>
复制代码

Mainwindow.xaml.cs

复制代码
public MainWindow(){InitializeComponent();this.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreMouseLeftButtonDown), true);this.grid.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreMouseLeftButtonDown), true);this.stackpanel.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreMouseLeftButtonDown), true);this.dockpanel.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreMouseLeftButtonDown), true);this.button.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreMouseLeftButtonDown), true);this.AddHandler(Button.MouseLeftButtonDownEvent, new RoutedEventHandler(MouseLeftButtonDown), true);this.grid.AddHandler(Button.MouseLeftButtonDownEvent, new RoutedEventHandler(MouseLeftButtonDown), true);this.stackpanel.AddHandler(Button.MouseLeftButtonDownEvent, new RoutedEventHandler(MouseLeftButtonDown), true);this.dockpanel.AddHandler(Button.MouseLeftButtonDownEvent, new RoutedEventHandler(MouseLeftButtonDown), true);this.button.AddHandler(Button.MouseLeftButtonDownEvent, new RoutedEventHandler(MouseLeftButtonDown), true);}

    private new void PreMouseLeftButtonDown(object sender, RoutedEventArgs e)
    {
      Console.WriteLine("PreMouseLeftButtonDown由 " + sender.ToString() + " 触发");
    }

    private new void MouseLeftButtonDown(object sender, RoutedEventArgs e)
    {
      Console.WriteLine("MouseLeftButtonDownEvent由 " + sender.ToString() + " 触发");
    }

复制代码

结果:先隧道后冒泡

 

这两句代码作用是一样的,当发生 PreviewMouseLeftButtonDown事件时执行PreMouseLeftButtonDown方法

Xmal中:

 <Button Name="button" PreviewMouseLeftButtonDown="PreMouseLeftButtonDown"  Content="right" DockPanel.Dock="Right" />

后台代码:

this.button.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreMouseLeftButtonDown), true);

4.自定义路由事件

wpf已经有很多路由事件了,为什么还要自定义?作用是什么?

想一下已存在的事件有单击Click,双击DoubleClick,左键点击MouseLeftButtonDown,右键单击MouseRightButtonDown,那我们想实现这样一个事件,右键点击3次事件,是不是没有?这时候需要我们自己去定义完成此功能的路由事件,我们自定义的路由事件是和wpf中的其他事件是同一个级别的。

假如右键3次单击的事件 名叫ThreeRightClick,那么ThreeRightClick和MouseLeftButtonDown、MouseRightButtonDown、Click等是一个级别的,也可以是冒泡的隧道的。在调用时的形式和他们没有区别

调用方式1
<local:Mybutton ThreeRightClick="OnThreeRightClick" PreviewMouseLeftButtonDown="PreMouseLeftButtonDown" Height="30" Width="50" Content="右键3击" />
调用方式2this.AddHandler(Mybutton.ThreeRightClickEvent, new RoutedEventHandler(OnThreeRightClick));

4.1如何自定义?

复制代码
public class Mybutton:Button{public Mybutton(){// 给Mybutton添加内置的MouseRightButtonDownEvent事件,用于触发自定义路由事件ThreeRightClickthis.AddHandler(Button.MouseRightButtonDownEvent, new RoutedEventHandler(OnRightClick));// 给Mybutton添加内置的PreviewMouseRightButtonDownEvent事件,用于触发自定义路由事件PreviewThreeRightClickthis.AddHandler(Button.PreviewMouseRightButtonDownEvent, new RoutedEventHandler(OnPreviewRightClick));// 给Mybutton添加自定义的ThreeRightClickEvent事件,//this.AddHandler(Mybutton.ThreeRightClickEvent, new RoutedEventHandler(OnThreeRightClick));}private int cnt1 = 0;//PreviewMouseRightButtonDownEvent的回调函数,在里面去触发自定义路由事件PreviewThreeRightClickprivate void OnPreviewRightClick(object sender, RoutedEventArgs e){cnt1++;if (cnt1 == 3){                                                                  //自定义路由事件PreviewThreeRightClick 隧道形式RoutedEventArgs routedEventArgs = new RoutedEventArgs(Mybutton.PreviewThreeRightClickEvent);this.RaiseEvent(routedEventArgs);cnt1 = 0;}}private int cnt = 0;//MouseRightButtonDownEvent的回调函数,在里面去触发自定义路由事件ThreeRightClickprivate void OnRightClick(object sender, RoutedEventArgs e){cnt++;if(cnt==3){                                                                //自定义路由事件PreviewThreeRightClick 冒泡形式RoutedEventArgs routedEventArgs = new RoutedEventArgs(Mybutton.ThreeRightClickEvent);this.RaiseEvent(routedEventArgs);cnt=0;}}//此自定义路由事件是冒泡的public static readonly RoutedEvent ThreeRightClickEvent = EventManager.RegisterRoutedEvent("ThreeRightClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Mybutton));public event RoutedEventHandler ThreeRightClick{add { AddHandler(ThreeRightClickEvent, value); }remove{RemoveHandler(ThreeRightClickEvent, value);}}//此自定义路由事件是隧道的public static readonly RoutedEvent PreviewThreeRightClickEvent = EventManager.RegisterRoutedEvent("PreviewThreeRightClick", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(Mybutton));public event RoutedEventHandler PreviewThreeRightClick{add { AddHandler(PreviewThreeRightClickEvent, value); }remove{RemoveHandler(PreviewThreeRightClickEvent, value);}}}
复制代码

4.2如何使用自定义路由事件?

xaml中:

<Grid Name="grid" local:Mybutton.ThreeRightClick="OnThreeRightClick" local:Mybutton.PreviewThreeRightClick="OnPreviewThreeRightClick"><local:Mybutton x:Name="mybutton" ThreeRightClick="OnThreeRightClick" PreviewThreeRightClick="OnPreviewThreeRightClick" Height="30" Width="50" Content="右键3击"  /></Grid>

或者代码中:

复制代码
public MainWindow(){InitializeComponent();this.AddHandler(Mybutton.ThreeRightClickEvent, new RoutedEventHandler(OnThreeRightClick));this.AddHandler(Mybutton.PreviewThreeRightClickEvent, new RoutedEventHandler(OnPreviewThreeRightClick));this.grid.AddHandler(Mybutton.ThreeRightClickEvent, new RoutedEventHandler(OnThreeRightClick));this.grid.AddHandler(Mybutton.PreviewThreeRightClickEvent, new RoutedEventHandler(OnPreviewThreeRightClick));this.mybutton.AddHandler(Mybutton.ThreeRightClickEvent, new RoutedEventHandler(OnThreeRightClick));this.mybutton.AddHandler(Mybutton.PreviewThreeRightClickEvent, new RoutedEventHandler(OnPreviewThreeRightClick));}
复制代码

 

 

 可以看到我们自定义的路由事件ThreeRightClick和PreviewThreeRightClick遵循冒泡和隧道顺序。

自定义的路由事件,再复杂的,也是由一个简单的事件引起的。这里是MouseRightButtonDown。需要注意简单事件的路由规则和自定义的路由事件的路由规则要对应。

画个图整理一下其中的逻辑是怎样的:

 

 就是说,注册路由事件,和路由事件触发时执行什么操作不是同一个概念。注册路由事件是向wpf系统中注册,而路由事件被触发时执行的操作是开发者的逻辑代码。

就像click事件执行时,我们需要编写一个方法去执行开发者的逻辑。注册路由事件的含义就是开发者根据wpf的语法,去注册一个类似于click事件的路由事件

来源:https://www.cnblogs.com/1024E/p/15752713.html

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/787551.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法与数据结构——栈

栈 栈(stack)是一种遵循先入后出逻辑的线性数据结构。如图所示,我们将堆叠元素的顶部称为“栈顶”,底部称为“栈底”。将吧元素添加到栈顶的操作叫做“入栈”,删除栈顶的操作叫做“出栈”。 栈的常用操作方法 描述 时间复杂度push() 元素入栈(添加至栈顶) O(1)pop() 栈顶…

LuCI Themes

OpenWrt 2020BootstrapBootstrap LightBootstrap DarkMaterialOpenWrt

指挥网络

树形图的定义:没有环,每个点(除了根节点)的入度都是\(1\),根节点的入度为\(0\) 朱刘算法的过程见OI-wiki;当没有环的时候,就满足了树形图的定义,于是可以结束;否则的话就将所有环缩点(注意此时由于每个点的入度都是\(1\),所以不可能存在两个环有公共点和公共边)得到…

处理异常

2、.map 没用用吗1、pdb、看完后 反汇编

CSS处理font-weight不生效问题

如何解决 Android 系统中文字体字重问题 翻找了几天资料,发现Noto Sans SC字体能支持中文/数字/英文字体 100,300,400,500,700,900 的字重,具体如下图所示:<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans+SC:300,400,…

处理font-weight不生效问题

如何解决 Android 系统中文字体字重问题 翻找了几天资料,发现Noto Sans SC字体能支持中文/数字/英文字体 100,300,400,500,700,900 的字重,具体如下图所示:<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans+SC:300,400,…

Kubernetes API资源规范

1. 资源类型和资源对象 1.1 资源类型 (Resource Types) 1.1.1 核心资源类型Kubernetes API Primitive用于描述在Kubernetes上运行应用程序的基本组件,即俗称的Kubernetes对象(Object) 它们持久存储于API Server上,用于描述集群的状态依据资源的主要功能作为分类标准,Kuber…

一本通信奥解题: 1251:仙岛求药

1251:仙岛求药 时间限制: 1000 ms 内存限制: 65536 KB提交数:31087 通过数: 13638 【题目描述】少年李逍遥的婶婶病了,王小虎介绍他去一趟仙灵岛,向仙女姐姐要仙丹救婶婶。叛逆但孝顺的李逍遥闯进了仙灵岛,克服了千险万难来到岛的中心,发现仙药摆在了迷阵的深处…

TwinCAT3 - 实现CiA402

目录1,起缘2,想办法3,开搞3.1,CANOpen通信3.1.1 对象字典3.1.2 通信建立3.2,CiA402伺服状态机3.3,伺服运行3.3.1 操作模式3.3.2 轮廓位置模式3.3.3 轮廓速度模式3.3.4 其他4,用起来 1,起缘 在TwinCAT3项目中涉及到轴运动时,通常做法都是在PLC中安装TC1250或者TF5000,…

地理:美国各州首府系列

America1.botson文化重镇波士顿 2.annapolis 3.弗吉尼亚州首府里士满 4.罗得岛州的普罗维登斯 5.加利福尼亚州的萨克拉门托 6.纽约州奥尔巴尼市 7.阿拉巴马州蒙哥马利市 8.alascap 9.arizona 10.little阿肯色州首府小石城 11.康涅狄格州哈特福德市 12.特拉华州多佛市 13.印第安…

利用kafka和kafka connect插件debezium实现oracle表同步

1.kafka安装 1.1.java安装 openjdk下载,建议使用17,至少应该高于版本11 # 进入家目录,解压下载的java包,配置环境变量 tar vxf openjdk-20.0.1_linux-x64_bin.tar.gz -C /usr/local/ vi .bash_profile # 注意要把JAVA的目录放到$PATH之前 export JAVA_HOME=/usr/local/jdk-…

WPF中如何根据数据类型使用不同的数据模板

我们在将一个数据集合绑定到列表控件时,有时候想根据不同的数据类型,显示为不同的效果。 例如将一个文件夹集合绑定到ListBox时,系统文件夹显示为不同的效果,就可以使用模板选择器功能。 WPF提供了一个模板选择器类型DataTemplateSelector,它可以根据数据对象和数据绑定元…