事件路由允许源自某个元素的事件由另一个元素引发。
定义、注册和包装路由事件
public class MyWindow : Window
{
/// <summary>
/// 定义和注册路由事件
/// </summary>
public static readonly RoutedEvent MyRoutedEvent = EventManager.RegisterRoutedEvent("MyEvent",
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyWindow));
/// <summary>
/// 包装路由事件
/// </summary>
public event RoutedEventHandler MyRouted
{
add
{
base.AddHandler(MyRoutedEvent, value);
}
remove
{
base.RemoveHandler(MyRoutedEvent, value);
}
}
}
共享路由事件
与依赖项属性一样,可以在类之间共享路由事件的定义。例如,两个基类UIElement和ContentElement类,都使用了MouseUp事件。MouseUp事件是在Windows.Input.Mouse类中定义,通过RoutedEvent.AddOwer()方法重用MouseUp事件。
UIElement.MouseUpEvent = Mouse.MouseUpEvent.AddOwner(Typeof(UIElement));
触发路由事件
路由事件的引发不是传统的.NET事件包装器引发,而是使用RaiseEvent()方法引发事件,说有UIElement类继承了该方法。
RoutedEventArgs e = new RoutedEventArgs(MyRoutedEvent, this);
base.RaiseEvent(e);
处理路由事件
XAML标记添加事件特性
<local:MyWindow ...
MyRouted="MyWindow_MyRouted"
... />
通过代码连接事件
this.MyRouted += MainWindow_MyRouted;
///WPF事件参数类都是继承自RoutedEventArgs类,可自定义事件参数类来传递更多的信息
private void MainWindow_MyRouted(object sender, RoutedEventArgs e)
{
//do something...
}
还可以通过调用AddHandler()方法链接事件处理器。
AddHandler(MyRoutedEvent, new RoutedEventHandler((s,e)=>
{
//do something...
}));
或
AddHandler(MyRoutedEvent, new RoutedEventHandler(Eventhandler));
private void Eventhandler(object o, RoutedEventArgs e)
{
//do something...
}
//使用RemoveHandler()方法来移除事件处理器 注:匿名委托不适用
//当程序多次附加事件处理器时,执行RemoveHandler()方法只删除一次连接。例如,连接了两次事件处理程序,删除了一次连接,触发事件时事件处理程序执行一次。 连接了三次事件处理程序,删除了一次连接,触发事件时事件处理程序执行两次。
RemoveHandler(MyRoutedEvent, new RoutedEventHandler(eventhandler));
或
MyRouted -= Eventhandler;
事件路由
WPF窗体中的所有要素都一定程度上继承自UIElement类,WPF中的许多空间都是内容控件,继承自ContentControl,可以在其中多次重复嵌套。
WPF路由事件模型的三种方式:
- 直接路由事件(Direct Event):与.NET事件类似,它起源于一个元素,并且不传递给其他元素。例如,MouseEnter事件是一个直接路由事件。
- 冒泡路由事件(Bubbling Event):在包含层次中向上传递。例如,MouseDown事件就是冒泡路由事件。冒泡顺序首先由被单击的元素引发,接下来被该元素的父元素引发,依次类推,直到WPF元素树的顶部为止。
- 隧道路由事件(Tunneling Event):在包含层次中向下传递。隧道路由事件在事件到达恰当的控件之前为预览事件(或者可能终止事件)提供机会。例如,通过PreviewKeyDown事件可以截获是否按下了一个键,首先在窗口级别上,然后是更具体的容器,直到到达按下键时具有焦点的元素。
路由事件的行为在注册路由事件的EventManager.RegisterEvent()中定义,传递为RoutingStrategy枚举值。
RoutedEventArgs类
WPF事件参数类都是继承自RoutedEventArgs类,可自定义事件参数类来传递更多的信息。RoutedEventArgs类的属性:
冒泡路由事件
<Label MouseUp="Label_MouseUp">
<Border MouseUp="Border_MouseUp">
<StackPanel MouseUp="StackPanel_MouseUp">
<Button Width="100" Height="30" MouseUp="Button_MouseUp"/>
</StackPanel>
</Border>
</Label>
MessageBox.Show("btn"+"\n"+sender.ToString()+ "\n" + e.Source.ToString()+ "\n" + e.OriginalSource.ToString());
在一个容器中放置下列元素,分别给每一个元素的MouseUp事件处理器添加以上代码。窗体打开后右键点击按钮就会依次弹出消息框,通过消息框信息可以看到事件的触发顺序依次是Button、StackPanel、Border、Label。若在StackPanel的MouseUp事件处理器中将e.Handled设置为true时,事件将不会继续传播,程序将不会在进入Boder和Label中的MouseUp事件处理器中。
e.Handled = true;
当事件处于挂起时,还可以通过AddHandler()方法,AddHandler()方法提供一个重载版本,在之前的基础上在传递一个bool类型的参数,将参数设置为true时,即使Handled属性被设置为true也可以接收到事件。
lab.AddHandler(UIElement.MouseUpEvent, new MouseButtonEventHandler(Label_MouseUp), true);
在设计器中删除Label元素的MouseUp事件特性,在初始化时通过以上代码连接事件处理器。当触发MouseUp时,Label消息框就会出现。
附加事件
<StackPanel Name="stpBtn" Grid.Row="2" ButtonBase.Click="DoSomething">
<Button Width="100" Height="30" />
<Button Width="100" Height="30" Margin="0,10" />
<Button Width="100" Height="30" />
</StackPanel>
以上代码在StackPanel元素中关联Button元素的点击事件,当点击其中按钮时就会进图DoSomething()方法,通过类名.事件命关联事件。Click事件是在ButtonBase中定义,Button继承自ButtonBase。附加事件还可以通过以下方式连接:
//关联附加事件只能用UIElement.AddHandler()方法 不能用 += 运算符
stpBtn.AddHandler(Button.ClickEvent, new RoutedEventHandler(DoSomething));
隧道事件
隧道路由事件的工作方式和冒泡路由事件相同,但方向相反。隧道路由事件一般有Preview开头,WPF通常成对的定义隧道路由事件通常和冒泡事件。如果将隧道路由事件标记为已处理过(e.Handled=true),那么就不会发生冒泡路由事件,这是因为两个事件共享RoutedEventArgs的同一个实例。
WPF事件WPF元素提供了许多事件,但最重要的事件通常包括5类,生命周期事件、鼠标事件、键盘事件、手写笔事件、多点触控事件。