public class ButtonBase:ContentControl,ICommandSource
{
public static readonly RoutedEvent ClickEvent; //路由事件的定义
public event RoutedEventHandler Click //传统的事件包装器
{
add
{
base.AddHandler(ClickEvent, value);
}
remove
{
base.RemoveHandler(ClickEvent, value);
}
}
static ButtonBase() //事件的注册
{
ClickEvent = EventManager.RegisterRoutedEvent("Click".
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(ButtonBase));
}
}
同依赖属性一样,路由事件也需要注册,不同的是使用EventManager.RegisterRoutedEvent方法。传统的CLR事件关联和解除关联处理函数均通过委托来实现,而路由事件则通过AddHanlder和RemoveHandler方法实现。传统事件的触发往往是调用其委托(因为事件的本质是委托),而路由事件则是通过一个RaiseEvent方法触发,调用该方法之后,所有关联该事件的对象都会得到通知。在ButtonBase中用如下方法关联事件:
RoutedEventArgs e = new RoutedEventArgs(ClickEvent,this);
base.RaiseEvent(e);
路由事件通过EventManager.RegisterRoutedEvent方法来注册事件;通过AddHandler和RemoveHandler来关联和解除关联的事件处理函数;通过RaiseEvent方法来触发事件;通过传统的CLR事件封装后供用户调用,使得用户如同使用传统的CLR事件一样使用路由事件。
在路由事件的旅行中,一般只出现两种角色:一是事件源,由其触发事件,是路由事件的起点;二是事件监听者,通常针对监听的事件有一个相应的事件处理函数。当路由事件经过事件监听者,就好比经过一个客栈,要做短暂的停留,由事件处理函数来处理该事件。路由事件的策略有如下三种:
1.Bubbling:事件从事件源出发一路上溯直到根节点,很多路由事件使用该策略;
2.Direct:事件事件源出发,围绕事件源转一圈结束;
3.Tunneling:事件源触发事件后,事件从根节点出发下沉直到事件源。
上面的事件策略相当于是一个旅行计划,它并不是一成不变的,而是可能会受到外力(监听者)的干预,那么这个计划可能会有所改变。
那么改变这个策略,也就是旅行计划的有哪些因素呢?
1.事件处理函数
一个最基本的路由事件处理函数原型如下:
public delegate void RoutedEventHandler(object sender, RoutedEventArgs)
鼠标事件处理函数的原型代码如下:
public delegate void MouseEventHandler(object sender, MouseEventArgs)
这种事件处理函数有如下两个特点:一是返回原型为void;二是有两个参数,第1个是一个object类型的对象,表示拥有该事件处理函数的对象,第2个是RoutedEventArgs或者是RoutedEventArgs的派生类,带有其路由事件的信息。
RoutedEventArgs的结构包括以下几部分:
Source:表明触发事件的源,如当键盘事件发生时,触发事件的源当前获得焦点的对象,当鼠标事件发生时,触发事件的源是鼠标所在的最上层对象。
OriginalSource:表明触发事件的源,一般来说OriginalSoource和Source相同,区别在于Source表示逻辑树上的元素,OriginalSource是可视树中的元素。如单击窗口的边框,Source为Window,OriginalSource为Border。
RoutedEvent:路由事件的对象。
Handled:布尔值,为true,表示该事件已处理,这样就可以停止路由事件。
由上可知:Hanlded属性就是改变路由事件旅行的元凶,一旦在某个事件处理函数中将Handled的值设置为true,路由事件就阻止传递。一个事件被标记为已处理,事件处理函数则不可处理该事件。但也有一种例外,WPF还提供了一种机制,即使事件被标记为已处理,事件处理函数仍然可以处理,但是关联事件及其处理函数需要稍做处理。AddHandler重载了两个方法,其中之一如下所示,需要将第3个参数设置为true:
public void AddHandler(RoutedEvent routedEvent, Delegate handler, bool handledEventsToo)
换句话说,因事件标识为处理而终止只是一种假象路由事件的旅行仍然在继续,只不过普通的事件处理函数无法处理它。
2.类和实例事件处理函数
事件处理函数有两种类型:一是前面所说的普通事件处理函数,称为实例事件处理函数(Instance Handlers);二是通过EventManager.RegisterClassHandler方法将一个事件处理函数和一个类关联起来,这种事件处理函数,称为类事件处理函数(Class Handlers),其优先级高于前者,也就是说事件在旅行时,会先光临类事件处理函数,然后再光临实例事件处理函数。
下面是一个路由事件的示例:
<Window x:Class="MyCustomTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:MyCustomTest"
Title="MainWindow" Height="300" Width="300"
custom:MySimpleButton.CustomClick="InsertList" Loaded="Window_Loaded">
<Grid Margin="3" custom:MySimpleButton.CustomClick="InsertList" Name="grid1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<custom:MySimpleButton x:Name="simpleBtn" CustomClick="InsertList">
MySimpleButton
</custom:MySimpleButton>
<ListBox Margin="5" Name="lstMessage" Grid.Row="1"></ListBox>
<CheckBox Margin="5" Grid.Row="2" Name="chkHandle">Handle first event</CheckBox>
<Button Grid.Row="3" HorizontalAlignment="Right" Margin="5" Padding="3"
Click="cmdClear_Click">Clear List</Button>
</Grid>
</Window>
自定义的MySimpleButton类代码如下:
class MySimpleButton : Button
{
static MySimpleButton()
{
EventManager.RegisterClassHandler(typeof(MySimpleButton),
CustomClickEvent, new RoutedEventHandler(CustomClickClassHandler), false);
}
//创建和注册该事件,该事件路由为Bubble
public static readonly RoutedEvent CustomClickEvent =
EventManager.RegisterRoutedEvent("CustomClick", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(MySimpleButton));
//CLR事件的包装器
public event RoutedEventHandler CustomClick
{
add { AddHandler(CustomClickEvent, value); }
remove { RemoveHandler(CustomClickEvent, value); }
}
protected override void OnClick()
{
RaiseCustomClickEvent();
}
void RaiseCustomClickEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(MySimpleButton.CustomClickEvent);
RaiseEvent(newEventArgs);
}
public event EventHandler ClassHandlerProcessed;
public static void CustomClickClassHandler(object sender, RoutedEventArgs e)
{
MySimpleButton simpleBtn = sender as MySimpleButton;
EventArgs args = new EventArgs();
simpleBtn.ClassHandlerProcessed(simpleBtn, args);
}
}
C#代码如下:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.simpleBtn.ClassHandlerProcessed += new EventHandler(simpleBtn_RaisedClass);
}
protected int eventCounter = 0;
private void InsertList(object sender, RoutedEventArgs e)
{
eventCounter++;
string message = "#" + eventCounter.ToString() + ":\r\n" +
"InsertList\r\n" +
"Sender:" + sender.ToString() + "\r\n" +
"Source:" + e.Source + "\r\n" +
"Original Source:" + e.OriginalSource;
lstMessage.Items.Add(message);
e.Handled = (bool)chkHandle.IsChecked;
}
private void cmdClear_Click(object sender, RoutedEventArgs e)
{
eventCounter = 0;
lstMessage.Items.Clear();
}
private void simpleBtn_RaisedClass(object sender, EventArgs e)
{
eventCounter++;
string message = "#" + eventCounter.ToString() + "\r\n" +
"Windows Class Handler\r\n" + "Sender:" + sender.ToString();
lstMessage.Items.Add(message);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
grid1.AddHandler(MySimpleButton.CustomClickEvent, new RoutedEventHandler(ProcessHandlersToo), true);
}
private void ProcessHandlersToo(object sender,RoutedEventArgs e)
{
eventCounter++;
string message = "#" + eventCounter.ToString() + ":\r\n" +
"ProcessHandlerToo\r\n" +
"Sender:" + sender.ToString() + "\r\n" +
"Source:" + e.Source + "\r\n" +
"Original Source:" + e.OriginalSource;
lstMessage.Items.Add(message);
}
}
在上面代码中,我们分别为Window、Grid和Button装配不同的事件处理函数,然后再单击按钮,观察路由和事件。代码效果图如下: