为了方便程序中对象之间的通信,通常需要我们自己定义一些路由事件。那么如何去创建自定义路由事件呢?下面通过一个例子来说明自定义路由事件的创建。
创建自定义路由事件大体来说分为三个步骤:
- 声明并注册路由事件
首先,定义路由事件与依赖属性的定义手法极为相似——申明一个由public static readonly修饰的RoutedEvent类型的字段,然后使用EventManager类的RegisterRoutedEvent方法进行注册。 完整的注册路由事件的代码如下:
//声明并注册路由事件
public static readonly RoutedEvent ClickEvent = EventManger.RegisterRoutedEvent<span style="font-family: Arial, Helvetica, sans-serif;">("Click",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase));</span>
我们来分析一下 EventManger.RegisterRoutedEvent这个方法的四个参数。
第一个参数是string类型的,被称为路由事件的名称。按照微软的建议,这个字符串RoutedEvent变量的前缀和CLR事件包装器的名称一致。再本例中,路由变量的名称
为ClickEvent,则此字符串为Click。
第二个参数为路由事件的策略,在WPF中,路由事件总共有三种策略:
- Bubble,冒泡式:路由事件从激发它的容器开始,一层层向上传递,直至最外层容器(Window或者Page)。
- Tunnel,隧道式:路由事件的传递方向和Bubble正好相反,是由UI树的树根向激发事件的控件移动
- Direct,直达式:模仿CLR直接事件,将消息直接送达事件处理器
第四个参数用于指明路由事件的宿主是哪个类型的。
2.为路由事件创建CLR事件包装
为路由事件添加CLR事件包装是为了把路由事件暴露的很像一个传统的直接事件,如果不关注底层实现,程序员是不会感觉到它与传统的直接事件的区别。
编程中仍然可以使用+=号为事件添加事件处理器和使用-=为事件移除不再使用的事件处理器。
3.创建可以激发路由事件的方法
激发路由事件的方法很简单,首先创建一个需要事件携带的消息并把它与路由事件相关联,然后调用元素的RaiseEvent方法将事件发送出去。
下面我们自己动手创建了一个路由事件,这个事件的用途是报告事件发生的时间。以此为例,说明自定义路由事件的创建方法。
在上文中提到过,创建自定义路由事件有三个步骤,分别是声明并注册路由事件、为路由事件添加CLR的包装、激发路由事件。在本例中,我们要激发路由事件,
首先得创建一个需要事件携带的消息。正所谓“兵马未动,粮草先行”,我们首先创建一个用于承载消息的事件参数。
//用于承载时间消息的事件参数
class ReportTimeEventArgs : RoutedEventArgs
{
public DateTime ClickTime { get; set; }
public ReportTimeEventArgs(RoutedEvent routedEvrent,object source):base(routedEvrent,source)
{
}
}
然后,在创建一个Button类的派生类并按前述步骤为其添加路由事件:
class TimeButton : Button
{
//声明、注册路由事件
public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent
("ReportTime", RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton));
//CLR事件包装器
public event RoutedEventHandler ReportTime
{
add { this.AddHandler(ReportTimeEvent, value); }
remove { this.RemoveHandler(ReportTimeEvent,value); }
}
//激发路由事件,借用Click事件的激发方法
protected override void OnClick()
{
base.OnClick();
ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent, this);
args.ClickTime = DateTime.Now;
this.RaiseEvent(args);
}
}
下面是程序的界面XAML代码:
<Window x:Class="_02_自定义路由事件.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:_02_自定义路由事件"
mc:Ignorable="d"
Title="Routed Event" Height="300" Width="300" Name="window_1"
local:TimeButton.ReportTime="ReportTimeHandle">
<Grid Name="grid_1" local:TimeButton.ReportTime="ReportTimeHandle">
<Grid Name="grid_2" local:TimeButton.ReportTime="ReportTimeHandle">
<Grid Name="grid_3" local:TimeButton.ReportTime="ReportTimeHandle">
<StackPanel Name="stackPanel_1" local:TimeButton.ReportTime="ReportTimeHandle">
<ListBox Name="listBox" Margin="10"></ListBox>
<local:TimeButton x:Name="timeButton" Width="80" Height="80"
Content="报时" local:TimeButton.ReportTime="ReportTimeHandle">
</local:TimeButton>
</StackPanel>
</Grid>
</Grid>
</Grid>
</Window>
在UI界面上,以Window为根,套了三层Grid和一层StackPanel(在里面都设置了Name属性),在最内部的StackPanel里面放置了一个ListBox和TimeButton(也就是上面刚刚创建的Button类的派生类)。我们可以看出,从最外层的Window到最内层的TimeButton,都在监听者ReportTimeEvent 这个路由事件,并用ReportTimeHandle方法来响应这个事件。ReportTimeHandle的代码如下:
//ReportTimeEvent路由事件处理器
private void ReportTimeHandle(object sender, ReportTimeEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
string timeStr = e.ClickTime.ToLongTimeString();
string content = string.Format("{0}到达{1}", timeStr, element.Name);
this.listBox.Items.Add(content); 9 }
运行程序,效果如下图所示:
在这个例子中我们采用的路由事件的策略为冒泡式(bubble),我们对程序做个简单的修改,在声明和注册路由事件时,将其事件的策略改为隧道式(tunnel):
//声明、注册路由事件
public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent
("ReportTime", RoutingStrategy.Tunnel, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton));
对比两张效果图,我们可以很清楚的看到,两种不同的策略所带来不同,借此也能更好的理解之前所说的内容。
这时候,大家有个疑问,如果让一个路由事件在某个地方被处理之后不再向后传呢?很简单,在RoutedEventArgs类或者其派生类的实例中,其具有一个bool类型的属性Handeled,一旦这个属性被设置为true,那么路由事件就不会再往下传递了。对应于我们这个例子,则需要做以下修改:
//ReportTimeEvent路由事件处理器
private void ReportTimeHandle(object sender, ReportTimeEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
string timeStr = e.ClickTime.ToLongTimeString();
string content = string.Format("{0}到达{1}", timeStr, element.Name);
this.listBox.Items.Add(content);
if (element == this.grid_2)
{
e.Handled = true;
}
}
To Be Continue!