介绍
与依赖项属性一样,路由事件是WPF对于传统.NET事件的升级,使得事件拥有更强的传播能力。
定义,注册和包装
// 我们来看一个Click事件定义的例子
public abstract class ButtonBase : ContentControl
{
// 定义路由事件
public static readonly RoutedEvent ClickEvent;
// 注册路由事件
static ButtonBase()
{
ButtonBase.ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ButtonBase));
}
// 普通事件包装路由事件
public event RoutedEventHandler Click
{
add
{
// AddHandler 和 RemoveHandler都是再FrameworkElement中定义的
base.AddHandler(ButtonBase.ClickEvent, value);
}
remove
{
base.RemoveHandler(ButtonBase.ClickEvent, value);
}
}
}
共享
通过上面的定义我们可以看到,与依赖项属性一样,路由事件也是静态定义,包装为普通事件使用,那么我们自然就可以推测我们可以像依赖项属性一样,将别的类的路由事件作为己用,这里我们需要使用 RoutedEvent.AddOwner()方法。
// UIElement类添加Mouse类的MouseUp事件
UIElement.MouseUpEvent = Mouse.MouseUpEvent.AddOwner(typeof(UIElement));
使用
路由事件的引发
使用RaiseEvent方法引发路由事件
RoutedEventArgs e = new RoutedEventArgs(ButtonBase.ClickEvent, this);
base.RaiseEvent(e);
路由事件的处理
监听事件
// 在xaml中直接处理
<button Click="cmdOK_Click">OK</Button>
// 后台代码连接事件
img.MouseUp += new MouseButtonEventHandler(img_MouseUp);
// 甚至可以简化代码
img.MouseUp += img_MouseUp;
// 直接调用AddHandler方法,不通过事件包装器绑定
img.AddHandler(Image.MouseUpEvent, new MouseButtonEventHandler(image_MouseUp));
// 由于我们之前讲过的,这是一个共享事件,UIElement Image Mouse中的MouseUp方法都是共享的,也可以AddHandler到UIElement中处理,这两种是等价的。唯一的区别就是不写Image不太容易看出MouseUp是由Image引发的
img.AddHandler(UIElement.MouseUpEvent, new MouseButtonEventHandler(image_MouseUp));
事件处理程序都有一个Args的参数,Args都继承自RoutedEventArgs,包含下面的属性
断开路由事件
断开路由事件不能在xaml中实现,必须使用代码 -=
// 使用-=运算符
img.MouseUp -= img_MouseUp;
// 直接使用RemoveHandler方法
img.RemoveHandler(Image.MouseUpEvent, new MouseButtonEventHandler(image_MouseUp));
路由事件的分类
分类描述
- 直接路由事件 起源于一个元素,不会传递给下一个元素
- 冒泡路由事件 沿着元素树向上传递(MouseUp事件),如果不处理的话,一直传递到元素树最上层元素
- 隧道路由事件 沿着元素树向下传递(KeyDown事件),隧道事件为提前处理事件提供了机会,比如PreviewKeyDown事件
设置事件种类
在使用EventManager.RegistEvent方法注册一个事件的时候需要传递一个RoutingStrategy的枚举值,设置事件的种类。
1. 冒泡路由事件
xaml代码:
<Window x:Class="Charles.WPF.View.TestBubblingEvent"
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:Charles.WPF.View"
mc:Ignorable="d"
Title="TestBubblingEvent" Height="359" Width="329" MouseUp="SomethingClick">
<Grid>
<Grid Margin="3" MouseUp="SomethingClick">
<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="SomethingClick">
<StackPanel MouseUp="SomethingClick">
<TextBlock Margin="3" MouseUp="SomethingClick">Image and text label</TextBlock>
<Image Source="/Image/1.jpg" Stretch="None" MouseUp="SomethingClick" Width="30" Height="30"></Image>
<TextBlock Margin="3" MouseUp="SomethingClick">Courtesy of the StackPanel.</TextBlock>
</StackPanel>
</Label>
<ListBox Grid.Row="1" Margin="5" Name="lstMessage"></ListBox>
<CheckBox Grid.Row="2" Margin="5" Padding="3" HorizontalAlignment="Right"
Name="chk_Handle">Handle first event</CheckBox>
<Button Grid.Row="3" Margin="5" Padding="3" HorizontalAlignment="Right" Name="cmd_Clear" Click="cmd_Clear_Click">
Clear List
</Button>
</Grid>
</Grid>
</Window>
后台代码:
public partial class TestBubblingEvent : Window
{
protected int eventCount = 0;
public TestBubblingEvent()
{
InitializeComponent();
}
private void SomethingClick(object sender, MouseButtonEventArgs e)
{
eventCount++;
string message = "#" + eventCount.ToString() + ":\r\n" +
" Sender: " + sender.ToString() + ":\r\n" +
" Source: " + e.Source + ":\r\n" +
" Original Source: " + e.OriginalSource;
lstMessage.Items.Add(message);
e.Handled = (bool)chk_Handle.IsChecked;
}
private void cmd_Clear_Click(object sender, RoutedEventArgs e)
{
lstMessage.Items.Clear();
}
}
这个例子说明了冒泡事件的事件传递顺序,点击图片的时候,事件由Image触发,层层向上传递,不碰到e.Handled = True不会终止传递。
一些冒泡事件的技巧:
- Sender是当前事件触发的控件,Source是触发源
- 事件的处理使用 MouseButtonEventArgs 和 RoutedEventArgs都是可以的
- 我们也监听了窗口的MouseUp事件,这就让我们在窗口的任意空白位置点击之后都会触发MouseUp事件,但是我们发现,点击按钮的时候不触发窗口的MouseUp事件,而是触发Button的Click事件,这是因为Button的源代码中挂起了MouseUp事件,并且引发了一个更高级的Click事件
- WinForm中,大多数控件都拥有Click事件,在WPF中,只有少数控件拥有Click事件
- 有一种方法可以监听到Handled为True的事件,虽然这是不推荐的,但是是可以实现的,通过传递最后一个参数true,可以接收到挂起的事件(不推荐)
cmdClear.AddHandler(UIElement.MouseUpEvent, new MouseButtonEventHandler(cmdClear_MouseUp), true);
- 上面我们讲到,大部分控件都没有Click事件,那Click事件如何冒泡呢?事实上,Click事件支持冒泡,但是处理Click事件需要一些特殊的技巧
这样写会报错,因为StackPanel并没有Click事件,这时候我们想要监听所有Button的Click事件,需要用到一个附件事件的技巧// StatckPanel 没有Click但是要监听里面Button的Click // 用下面的方法是不行的 <StackPanel Click="DoSomething"> <Button/> <Button/> <Button/> </StackPanel>
使用类名加上事件可以获取StackPanel中Button的Click事件,这就是附加属性的使用。在代码中,需要注意不能使用 += 的方法进行事件处理方法的绑定,因为+=默认就绑定到StackPanel上了,需要使用AndHandler方法<StackPanel Button.Click="DoSomething"> <Button/> <Button/> <Button/> </StackPanel>
要确定是StackPanel中哪个按钮引发了事件,可以使用下面的方法// StackPanel 的名字为 pnlButtons pnlButtons.AddHandler(Button.Click, new RoutedEventHandler(DoSomething));
// 方法1. 使用控件的x:Name属性 private void DoSomething(object sender, RoutedEventArgs args) { // cmd1是按钮控件1的Name属性 if(sender == cmd1) { // 触发的是第一个按钮 } else if(sender == cmd1) ... } // 方法2. 给按钮添加Tag <Button Tag="first button"/> object tag = ((FrameworkElement)sender).Tag;
2. 隧道路由事件
- 隧道路由事件的工作方式和冒泡路由事件相同,但是方向相反
- 隧道路由事件都是以Preview开头的事件
- 隧道路由事件和冒泡路由事件公用同一个RoutedEventArgs,所以如果把隧道路由事件标记为已处理,冒泡路由事件就不会触发,这个属性很适合用于做预处理
- 隧道事件总是在冒泡事件之前触发
- 从下图可以看到,事件的触发先下去后上来,所以在任意中间元素中让e.Handler=true则冒泡事件都不会触发
来源:CSDN
作者:聂14昊51
链接:https://blog.csdn.net/nxy_wuhao/article/details/104511964