引用:
WPF中隧道路由和冒泡路由事件的示例分析 - 开发技术 - 亿速云 (yisu.com)
事件最基本的用法
在基于事件驱动的开发中,把代码放在响应注册的事件的处理函数内,比如Click事件、MouseDown事件、MouseUp事件等等。每个控件响应自己的注册事件,有很多如果在事件上有相互关联和影响的事件,就要在一个业务逻辑里写比较多的代码。而路由事件主要的优势就是路由事件可以在元素树上进行传递,并且沿着元素树的传播途径被事件处理程序处理。这样我们写代码的过程中时就可以更好的组织代码到合适的位置。
WPF事件模型和WPF属性模型非常类似,与依赖项属性一样,路由事件由只读的静态字段表示,在静态构造函数中注册,并通过标准的.NET事件定义进行封装。这里我们只讲如何更好的使用。原理部分请看源码。比如ButtonBase提供的Click事件。
功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件。
实现定义:路由事件是由 类的实例支持的 CLR 事件, RoutedEvent 由事件 Windows Presentation Foundation (WPF) 系统处理。
典型的 WPF 应用程序中包含许多元素。 无论这些元素是在代码中创建还是在 XAML 中声明,它们存在于彼此关联的元素树关系中。 根据事件的定义,事件路由可以按两种方向之一传播,但是通常会在元素树中从源元素向上“浮升”,直到它到达元素树的根
<Button Content="事件处理程序" Click="Button_Click"/>
private void Button_Click(object sender, RoutedEventArgs e) { //这是Click事件处理程序代码部分。 }
在注册事件后,在事件处理程序中第一个参数 sender提供引发该事件的对象,第二个参数是EventArgs对象。在WPF中如果事件不需要传递额外的信息,可以使用RoutedEventArgs类,如果需要传递额外的信息,就要是有继承自RoutedEventArgs的对象。
注册事件的几种写法:
1)在XAML代码中<Button x:Name="EventMessageButton" Content="事件处理程序" MouseUp="EventMessageButton_MouseUp"/> 2)在cs代码中 EventMessageButton.MouseUp += EventMessageButton_MouseUp; 3)在cs代码中 EventMessageButton.MouseUp += new MouseButtonEventHandler(EventMessageButton_MouseUp); private void EventMessageButton_MouseUp(object sender, MouseButtonEventArgs e) { //我是处理程序。 }
第一种写法:我们使用XAML文件中在Button元素内使用MouseUp来创建后台事件处理代码 Btn_eventMessge_MouseUp
第二种写法:我们在后台代码中使用MouseUp+=的方式注册。一种是New MouseButtonEventHandler传入方法名。一种是匿名的直接传入方法名,这三种注册方式达成的效果是一样的。
而这三种实际上使用的是事件封装器。另一种方式是通过使用UIElement.AddHandler来直接连接事件。这里看个人习惯把。但是各种写法主要解决的问题还是解耦,因为这些会关联到后面的命令,动画。模板。触发器。MVVM下的使用,等等。这是个比较长久的问题。所以在这里,能够使用,看得明白,目前这个阶段就可以了。
我们继续往下。解除关联
在注册事件的时候,最好先使用-=来解除关联,避免多次触发不合符预期的监听事件。断开使用-=或者使用UIElement.RemoveHandler来解除关联。 因为事件在多次+=注册事件处理程序是可行的。而事件的多词解除关系不会引发任何问题,因此不要担心+=和-=不匹配的问题。
public MainWindow() { InitializeComponent();
EventMessageButton.MouseUp -= EventMessageButton_MouseUp; EventMessageButton.MouseUp += EventMessageButton_MouseUp; }
理解路由事件
我们知道了事件可以在元素上注册事件处理程序,那么我们知道内容控件是可以相互嵌套各种奇奇怪怪的组合以达到自己想要的效果,在这种情况下我们假设一个比较常见的场景。我们有一个标签,标签中包含一个StackPanel面板,面板中包含一幅图片和2个文本。
<Label BorderBrush="Black" BorderThickness="1"> <StackPanel> <TextBlock Margin="3"> 我是图片标题 </TextBlock> <Image Source="1.png" Stretch="None"/> <TextBlock Margin="3"> 我是图片正文 </TextBlock> </StackPanel> </Label>
我们的控件来回嵌套内容结构很复杂了。但是我们想在用户点击时只在一个地方响应我们的代码。如果为每个元素都关联同一个事件处理程序,代码会很乱。而且难以维护。而路由事件就是为了解决这个问题的。路由事件分为三种:
1)和普通的.NET事件类似,直接路由事件(direct event) 他们源于一个元素,不传递给其他元素,比如MouseEnter事件,是直接路由事件。
2)向上传递的冒泡路由事件(bubbling event)比如MouseDown事件,是冒泡路由事件,该事件先由被单击的元素引发,接下来被该元素的父元素引发,然后被父元素的父元素引发,以此类推。直到元素树的顶部。
3)向下传递的隧道路由事件(tunneling event) 比如PreviewKeyDown事件,隧道路由事件在事件到达恰当的空间之前为预览事件和终止事件提供了机会,比如PreviewKeyDown事件可以截获是否按下了某个键,首先在窗口级别上,然后是更具体的容器,直到当按下时具有焦点的元素。
当使用EventManager.RegisterEvent()发给发注册路由事件时,需要传递一个RoutingStrategy枚举,指示希望用于事件的事件行为。
sender 属性是Source 属性是引发事件的的对象。 OriginalSource是最初是什么对象引发了事件。RoutedEvent为触发的事件提供的RoutedEvent对象。里面是需要用到的当前的参数,比如鼠标坐标,touch等等。Handled属性的作用是终止事件是否继续传递。
案例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WPFEvent { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } protected int eventCounter = 0;//事件计数 private void EventResponseProcess_MouseUp(object sender, MouseButtonEventArgs e) { eventCounter++; string message = $"#{ eventCounter}: Sender: {sender} Source: {e.Source} Original Source: {e.OriginalSource}"; MessageListBox.Items.Add(message); e.Handled = (bool) HandlerCheckBox.IsChecked; } private void ClearButton_Click(object sender, RoutedEventArgs e) { eventCounter = 0; MessageListBox.Items.Clear(); } } }
<Window x:Class="WPFEvent.MainWindow" 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:WPFEvent" mc:Ignorable="d" MouseUp="EventResponseProcess_MouseUp" Title="MainWindow" Height="450" Width="800"> <Grid Margin="3" MouseUp="EventResponseProcess_MouseUp"> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Label Margin="5" Grid.Row="0" HorizontalAlignment="Left" Background="AliceBlue" BorderBrush="Black" BorderThickness="1" MouseUp="EventResponseProcess_MouseUp"> <StackPanel MouseUp="EventResponseProcess_MouseUp"> <TextBlock Margin="3" MouseUp="EventResponseProcess_MouseUp"> 我是图片标题 </TextBlock> <Image Source="1.png" Stretch="None" MouseUp="EventResponseProcess_MouseUp" Height="53"/> <TextBlock Margin="3"> 我是图片正文 </TextBlock> </StackPanel> </Label> <ListBox Grid.Row="1" Margin="5" Name="MessageListBox"></ListBox> <CheckBox Grid.Row="2" Margin="5" Name="HandlerCheckBox"> Handle first event </CheckBox> <Button Grid.Row="3" Margin="5" Padding="3" HorizontalAlignment="Right" Name="ClearButton" Click="ClearButton_Click">Clear List</Button> </Grid> </Window>
如下面的代码,在StackPanel中不存在Button的Click事件,但是可以通过ButtonBase.Click获取按钮的点击事件,此事件将会在StackPanel容器里面的任意按钮被点击时触发。
<StackPanel ButtonBase.Click="StackPanel_Click" Margin="5"> <Button Content="按钮A" Click="Button_Click"/> <Button Content="按钮B" Click="Button_Click"/> <Button Content="按钮C" Click="Button_Click"/> </StackPanel>
小问题汇总解决:
Image控件里的Source属性在设置了相对路径, 程序却无法显示图片, 改成绝对路径后就能做出显示了,
步骤如下:
右键项目->添加->现有项, 把图片添加到项目中
然后设置图片属性:
生成操作:Resource,
复制到输出目录:始终复制.
然后重新生成解决方案。