RoutedEvent
CLR Event(直接事件)
- 事件拥有者:消息的发送者,事件被触发则消息被发送
- 事件响应者:消息的接收,处理者,通过事件处理器对事件做出响应
- 事件订阅关系:使事件和事件处理器关联起来
- 事件:使用 event 关键字修饰的委托类成员变量
- 事件处理器:函数
在这种模型中,事件的响应者通过订阅关系直接关联在事件拥有者的事件上,这种模型称为CLR事件模型。
但这种模型存在弊端:每对消息都是发送–>响应关系,必须建立点对点的订阅关系,而且事件拥有者必须能够访问事件响应者,否则无法建立订阅关系,这就引出了接下来的重点,Routed Event。
RoutedEvent(路由事件)
什么是RoutedEvent
- 功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件。
- 实现定义:路由事件是一个 CLR 事件,由 RoutedEvent 类的实例提供支持并由 Windows Presentation Foundation (WPF) 事件系统处理。
- 类似.Net的事件,如按键、点击、鼠标移动等,但又不相同。
- 如果接收路由事件的元素没有处理事件,则路由事件将传播到该元素的子元素或祖先元素(取决于路由事件的路由策略)。
- 只要被路由的事件没有被标记为已处理,它就会被“路由”,如果一个元素将一个被路由的事件标记为已处理,则该被路由的事件将不会传播到下一个元素。
Routed Strategy
- Bubble(浮升):调用事件源上的事件处理程序。 路由事件随后会路由到后续的父级元素,直到到达元素树的根。字面意思,上浮,就是不断调用父级的事件处理器,直到被处理。
- Direct(直接):只有源元素本身才有机会调用处理程序以进行响应。但是,与标准 CLR 事件不同的是,直接路由事件支持类处理,并且可供 EventSetter 和 EventTrigger 使用。
- Tunnel(隧道):最初将调用元素树的根处的事件处理程序。 随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。字面意思,就是不断调用子级的事件处理器,直到被处理。
EventSetter 和 EventTrigger
- EventSetter:在样式中,可以通过使用 EventSetter 在标记中添加一些预先声明的 XAML 事件处理语法。 在应用样式时,所引用的处理程序会添加到带样式的实例中。
<StackPanel
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.EventOvw2"
Name="dpanel2"
Initialized="PrimeHandledToo"
>
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="b1SetColor"/>
</Style>
</StackPanel.Resources>
<Button>Click me</Button>
<Button Name="ThisButton" Click="HandleThis">
Raise event, handle it, use handled=true handler to get it anyway.
</Button>
</StackPanel>
CS
private void HandleThis(object sender, RoutedEventArgs e)
{
// TODO: Add event handler implementation here.
}
private void b1SetColor(object sender, RoutedEventArgs e)
{
// TODO: Add event handler implementation here.
}
这样做的好处在于,样式有可能包含大量可应用于应用程序中任何按钮的其他信息,将 EventSetter 添加到样式可以提高代码的重用率
- EventTrigger:将 WPF 的路由事件和动画功能结合在一起的专用语法。可以指定当路由事件到达其路由中的某个元素(这个元素针对该事件声明了 EventTrigger)时将运行的 Storyboard。
RoutedEventArgs
- Handled:获取或设置一个 bool 值,该值指示针对路由事件(在其经过路由时)的事件处理的当前状态。
- OriginalSource:在父类进行任何可能的 Source 调整之前,获取由纯命中测试确定的原始报告源。在可视化树中事件源元素。
- RoutedEvent:获取或设置与此 RoutedEventArgs 实例关联的 RoutedEvent。
- Source:获取或设置对引发事件的对象的引用。在逻辑树中的事件源元素。
Logical Tree
代表UI的基本结构,非常匹配在 xaml 文件中声明的元素。它被用来确定一些事情:比如依赖属性值继承、资源解析等。逻辑树是静态的,在代码编写完以后,不需要程序员的干预。
Visual Tree
可视化树描述由 Visual 基类表示的可视化对象的结构。即UI中呈现给输出设备的所有元素。它被用于许多事情:如呈现、事件路由、定位资源(如果元素没有逻辑父元素)等。可视化树可以在用户切换到不同的窗口主题而简单改变。
怎么声明一个路由事件
- RoutedEvent 使用 RegisterRoutedEvent 该方法注册命名 xxx,并在注册期间指定路由策略。
- 在类的静态构造函数执行期间注册路由事件。
- 定义 CLR 添加 和 删除 事件访问器。
- 创建可以激发路由事件的方法。
代码实现
1.新建用户控件 RouteEventControl ,添加一个Button按钮,添加按钮的Click事件,XAML代码如下:
<UserControl x:Class="WpfRoutedEventDemo.RouteEventControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfRoutedEventDemo"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button Height="30" Width="100" Content="调用路由事件" Click="Button_Click"></Button>
</Grid>
</UserControl>
2.在用户控件的隐藏文件中创建自定义路由事件,C#代码如下:
public partial class RouteEventControl : UserControl
{
public RouteEventControl()
{
InitializeComponent();
}
//1、声明并注册路由事件,使用浮升策略
public static readonly RoutedEvent MyClientEvent = EventManager.RegisterRoutedEvent("MyClick",
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(RouteEventControl));
//2、定义 CLR 添加 和 删除 事件访问器。
public event RoutedEventHandler MyClick
{
add
{
AddHandler(MyClientEvent, value);
}
remove
{
RemoveHandler(MyClientEvent, value);
}
}
// 3、创建可以激发路由事件的方法。
private void Button_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs arg = new RoutedEventArgs();
arg.RoutedEvent = MyClientEvent;
RaiseEvent(arg);
}
}
3.在主界面中引入新创建的用户控件,使用自定义的路由事件MyClick,并为MyClick事件编写调用的方法,XAML代码如下:
<Window x:Class="WpfRoutedEventDemo.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:WpfRoutedEventDemo"
mc:Ignorable="d"
Title="RoutedEventDemo" Height="350" Width="525">
<Grid>
<local:RouteEventControl MyClick="RouteEventControl_MyClick"></local:RouteEventControl>
</Grid>
</Window>
4.在主界面的隐藏文件中创建RouteEventControl_MyClick方法,C#代码如下:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void RouteEventControl_MyClick(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hello:" + e.Source.ToString());
}
}
为什么要使用路由事件
路由事件在以下场景将得到充分发挥:
- 公用根处定义公用处理程序,例如一个标签下面放多个button标签,在父级标签上使用路由事件更加方便。
- 合成自己的控件或者定义自己的自定义控件类。
- 某些 WPF 样式和模板功能(如 EventSetter 和 EventTrigger)要求被引用的事件是路由事件。
- 路由事件支持类处理机制,类可以通过该机制来指定静态方法,这些静态方法能够在任何已注册的实例处理程序访问路由事件之前,处理这些路由事件。 这在控件设计中非常有用,因为类可以强制执行事件驱动的类行为,以防它们在处理实例上的事件时被意外禁止。
AttachedEvent
- 在 WPF 中,附加事件由 RoutedEvent 字段支持,并在引发后通过树进行路由
- 通常,附加事件的源(引发该事件的对象)是系统或服务源,所以运行引发该事件的代码的对象并不是元素树的直接组成部分
附加事件的简单理解:
- 路由事件的宿主全是拥有可视化实体的界面元素,附加事件需借助界面元素去与其他对象进行沟通
- 附加事件声明的类并不是 UIElment 类的派生类,因此不具备 AddHandler 和 RemoveHandler 方法,需要手动实现