WPF中使用路由事件升级了传统应用开发中的事件,在WPF中使用路由事件能更好的处理事件相关的逻辑,咱们这篇开始整理事件的用法和什么是直接路由,什么是冒泡路由,什么是隧道路由。express
在基于事件驱动的开发中,把代码放在响应注册的事件的处理函数内,好比Click事件、MouseDown事件、MouseUp事件等等。每一个控件响应本身的注册事件、有不少若是在事件上有相互关联和影响的事件,就要在一个业务逻辑里写比较多的代码。而路由事件主要的优点就是路由事件能够在元素树上进行传递,而且沿着元素树的传播途径被事件处理程序处理。这样咱们写代码的过程当中时就能够更好的组织代码到合适的位置。canvas
WPF事件模型和WPF属性模型很是相似,与依赖项属性同样,路由事件由只读的静态字表示,在静态构造函数中注册,并经过标准的.NET事件定义进行封装。这里咱们只讲如何更好的使用。原理部分请看源码。好比ButtonBase。函数
<Button Content="事件处理程序" Click="Button_Click"/>
private void Button_Click(object sender, RoutedEventArgs e) { //这是Click事件处理程序代码部分。 }
在注册事件后,在事件处理程序中第一个参数 sender提供引起该事件的对象,第二个参数是EventArs对象。在WPF中若是事件不须要传递额外的系列,可使用RoutedEventArgs类,若是须要传递额外的信息,就要是有继承自RoutedEventArgs的对象。好比处理inkcanvas墨迹绘制的。好比处理多点触控的。这些都是变相继承RoutedEventArgs类。里面会包含在这种场景下更加多的信息。 动画
注册事件的几种写法:编码
1)在XAML代码中<Button x:Name="btn_eventMessge" Content="事件处理程序" Click="Btn_eventMessge_MouseUp"/>
2)在cs代码中 btn_eventMessge.MouseUp += Btn_eventMessge_MouseUp;
3)在cs代码中 btn_eventMessge.MouseUp += new MouseButtonEventHandler(Btn_eventMessge_MouseUp);
private void Btn_eventMessge_MouseUp(object sender, MouseButtonEventArgs e)
{
//我是处理程序。
}spa
第一种写法:咱们使用XAML文件中在Button元素内使用Click来建立后台事件处理代码 cmdOK_Clickcode
第二种写法:咱们在后台代码中使用MouseUp+=的方式注册。一种是New MouseButtonEventHandler传入方法名。一种是匿名的直接传入方法名,这三种注册方式达成的效果是同样的。orm
而这三种实际上使用的是事件封装器。经过使用UIElement.AddHandler来直接链接事件。这里看我的习惯把。可是各类写法主要问题仍是解耦,由于这些会关联到后面的命令,动画。模板。触发器。MVVM下的使用,等等。这是个比较长久的问题。因此在这里,可以使用,看得明白,目前这个阶段就能够了。xml
咱们继续往下。解除关联对象
在注册以后,必定要记得断开。断开使用-=或者使用UIElement.RemoveHandler来解除关联。
注册以前最好-=一下。或者在不用的地方。直接-=。由于在技术上,为一个事件屡次注册事件处理程序是可行的。可是不少时候都是编码失误。致使了一次触发,响应屡次。
咱们知道了事件能够在元素上注册事件处理程序,那么咱们知道内容控件是能够相互嵌套各类奇奇怪怪的组合以达到本身想要的效果,在这种状况下咱们假设一个比较常见的场景。咱们有一个标签,标签中包含一个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枚举,指示但愿用于事件的事件行为。
MouseUP和MouseDown事件都是冒泡路由事件,所以当在上面的图片中按下鼠标左键后顺序触发MouseDown事件的顺序是冒泡的。咱们使用软件抓取一下过程:
从图中咱们看到首先触发的是PreviewMouseDown的隧道路由。他可让咱们有机会预览事件或终止事件。咱们看到了从MainWindow开始到最终的Image结束。咱们没有终止路由。因此进行了下一轮的冒泡路由。从Image开始。带MainWindow。
整个流程就结束了。咱们看到路由事件提供了对事件处理很是丰富的功能。具体的隧道或冒泡行为能够参考RoutedEventArgs中的内容。
Source 属性是引起事件的的对象。 OriginalSource是最初是什么对象引起了事件。RoutedEvent为触发的事件提供的RoutedEvent对象。里面是须要用到的当前的参数,好比鼠标坐标,touch等等。Handled属性的做用是终止事件是否继续传递。
咱们如今开始在这个例子上添加代码。用于演示咱们怎么处理冒泡路由。
<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="SomethingClicked" Title="MainWindow" Height="450" Width="800"> <Grid Margin="3" MouseUp="SomethingClicked"> <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="SomethingClicked"> <StackPanel MouseUp="SomethingClicked"> <TextBlock Margin="3" MouseUp="SomethingClicked"> 我是图片标题 </TextBlock> <Image Source="1.png" Stretch="None" MouseUp="SomethingClicked"/> <TextBlock Margin="3"> 我是图片正文 </TextBlock> </StackPanel> </Label> <ListBox Grid.Row="1" Margin="5" Name="lstMessages"></ListBox> <CheckBox Grid.Row="2" Margin="5" Name="chkHandle"> Handle first event </CheckBox> <Button Grid.Row="3" Margin="5" Padding="3" HorizontalAlignment="Right" Name="cmdClear" Click="cmdClear_Click">Clear List</Button> </Grid> </Window>
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 SomethingClicked(object sender, MouseButtonEventArgs e) { eventCounter++; string message = "#" + eventCounter.ToString() + ":\r\n" + " Sender: " + sender.ToString() + "\r\n" + " Source: " + e.Source + "\r\n" + " Original Source: " + e.OriginalSource; lstMessages.Items.Add(message); e.Handled = (bool)chkHandle.IsChecked; } private void cmdClear_Click(object sender, RoutedEventArgs e) { } } }
和上图同样,咱们尝试观察执行过程。看下触发过程。就知道工做原理了。这里写例子。主要是熟悉对于事件传入参数OriginalSource的使用。勾选chkhandle能够终止冒泡。只触发第一个Image的事件,能够本身写代码尝试一下。
有个方法能够接收终止的事件消息,使用AddHandler()重载的方法。
这里还有一个经常使用的技巧,事件的附加。
一个元素自己没得事件。能够经过类名.事件名得方式关联。
<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>
隧道路由这里就不写了,前面已经讲过。隧道路由命名都是Preview开头的。隧道路由所有结束了,同级的冒泡路由才开始。主要是作预先处理,能够中止路由事件。
只是入门,因此理解什么是事件,就能够了。后面会讲到Window类的生命周期。等等。如今能理解什么是路由事件就行。