创建自定义路由事件大体可以分为三个步骤:
①声明并注册路由事件。
②为路由事件添加CLR事件包装。
③创建可以激发路由事件的方法。
以ButtonBase类中代码为例展示这3个步骤:
public abstract class ButtonBase:ContentControl,ICommandSource
{
//声明并注册路由事件。
public static readonly RoutedEvent ClickEvent=EventManager.RegisterRoutedEvent("Click",RoutedStrategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase));
//为路由事件添加CLR事件包装器。
pubic event RoutedEventHandler Click
{
add{this.AddHandler(ClickEvent,Value);}
remove{this.REmoveHandler(ClickEvent,Value);}
}
//激发路由事件的方法,此方法在用户单击鼠标时会被Windows系统调用
protected virtual void OnClick()
{
RoutedEventArgs newEvent=new RoutedEventArgs(ButtonBase.ClickEvent,this);
this.RaiseEvent(new Event);
}
}
理解EventManager.RegisterRoutedEvent方法的四个参数:
第一个参数:
参数为String类型,被称为路由事件的名称。应与RoutedEvent变量的前缀和CLR事件包装器的名称一致。
第二个参数:
①Buddle,冒泡式:路由事件有事件的激发着出发向它的上级容器一层一层路由,直至最外层容器(Window或者Page)。因为是有树的底端向顶端移动,所以这种策略被形象的命名为“冒泡式”。
②Tunnel,隧道式:事件的路由方向正好与Bubble策略相反。
③Direct,直达式:模仿CLR直接事件,直接将事件消息送达事件处理器。
第三个参数:
用于指定事件处理器类型。事件处理器的返回值类型和参数列表必须与此参数指定的委托保持一致,不然会导致在编译时抛出异常。
第四个参数:
用于指定路由事件的宿主(拥有者)是哪儿个类型。
自己动手创建一个路由事件,这个事件的用途是报告事件发生的时间。
“兵马未动,粮草先行”。为了让事件消息能携带按钮被单击时的时间。先创建一个RoutedEventArgs类的派生类。
//用于承载时间消息的事件参数
class ReportTimeEventArgs:RoutedEventArgs
{
public ReportTimeEventArgs(RoutedEvent routedEvent,object source)
:base(routedEvent,source){}
public DateTime ClickTime{get;set;}
}
然后创建一个Button类的派生类并按前述步骤为其添加路由事件。
class TimeButton:Button
{
//声明和注册路由事件
public static Readonly RoutedEvent ReportTimeEvent=EventManager.RegisterManager
("ReportTime",RoutingStrategy.Bubbl,typeof(EventHandler<ReportTimeArgs>),typeof(TimeButton));
//为路由事件添加CLR事件包装器
public event RoutedEventHanler ReportTime
{
add{AddHandler(ReportTimeEvent,value);}
remove{RemoveHandler(ReportTimeEvent,value);}
}
//激发路由事件,借用Click事件的激发事件
protected override void OnClick()
{
base.OnClick();
ReportTimeEventArgs args=new ReportTimeEventArgs(ReportEvent,this);
args.ClickTime=DateTime.Now;
this.RaiseEvent(args);
}
}
//下面是程序界面XAML代码
<Window x:Class="Wpf8.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wpf8"
local:TimeButton.ReportTime="ReportTimeHandler"
Title="Window1" Height="300" Width="300">
<Grid x:Name="grid_1" local:TimeButton.ReportTime="ReportTimeHandler">
<Grid x:Name="grid_2" local:TimeButton.ReportTime="ReportTimeHandler">
<Grid x:Name="grid_3" local:TimeButton.ReportTime="ReportTimeHandler">
<StackPanel x:Name="stackPanel_1" local:TimeButton.ReportTime="ReportTimeHandler">
<ListBox x:Name="listBox"></ListBox>
<local:TimeButton x:Name="timeButton" Width="80" Height="80" Content="报时" local:TimeButton.ReportTime="ReportTimeHandler"></local:TimeButton>
</StackPanel>
</Grid>
</Grid>
</Grid>
</Window>
在UI界面上,以Window为根,套了三层Grid和一层StackPanel。最里面放置了一个ListBox和一个TimeButton。从最内层的TimeButton到最外层的Window都侦听着TimeButton.ReportTimeEvent这个路由事件。并用ReportTimeHandler方法来响应这个事件。ReportTimeHandler的代码如下:
//ReportTimeEvent路由事假处理器
private void ReportTimeHandler(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);
}
运行程序,单击按钮。
RoutedEvenArgs的Source与OriginalSource
路由事件的消息包含在RouteEventArgs实例中。RoutedEventArgs有两个属性Source和OriginalSource。这两个属性都表示路由事件传递的起点。只不过Source表示的是LogicalTree上的消息源头,而OriginalSource则表示VisualTree上的源头。
示例:
创建一个用户控件:
<UserControl x:Class="Wpf8.MyUserControl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Border BorderBrush="Orange" BorderThickness="3" CornerRadius="5">
<Button x:Name="innerButton" Width="80" Height="80" Content="OK"></Button>
</Border>
</UserControl>
在主窗体中添加用户控件。
<Window x:Class="Wpf8.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wpf8"
Title="Window2" Height="180" Width="300">
<Grid>
<local:MyUserControl x:Name="myUserControl" Margin="10"/>
</Grid>
</Window>
后台代码:
public partial class Window2 : Window
{
public Window2()
{
InitializeComponent();
//为主窗体添加对Button.Click事件的侦听。
this.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.Button_Click));
}
//路由事件处理器。
private void Button_Click(object sender, RoutedEventArgs e)
{
string strOriginalSource = string.Format("VisualTree start point:{0},type is {1}",
(e.OriginalSource as FrameworkElement).Name,e.OriginalSource.GetType().Name);
string strSource = string.Format("LogicalTree start point:{0},type is {1}",
(e.Source as FrameworkElement).Name,e.Source.GetType().Name);
MessageBox.Show(strOriginalSource+"\r\n"+strSource);
}
}
Button.Click路由事件是从MyUserControl的innerButton发出来的,在主窗体中,myUserControl是LogicalTree的末端结点,所以e.source就是myUserControl;而窗体的VisualTree则包含了myUserControl的内部结构。所以使用e.OriginalSource可以获得innerButton.