WPF 理解事件路由
路由事件的概念
对于标准的 .NET 时间来说,时间可以与一个或者多个元素相关联, 但是每个要关联的元素需要显示进行订阅,
否则 .NET 将忽视该对象, WPF 中的路由事件使用了一种不同的机制,
事件可以在 WPF 的元素树向上或者向下进行传递,无论是否显示地关联, 位于元素树上下级的元素都有机会处理事件.
EXAMPLE:
XAML
<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1"> <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler"> <Button Name="YesButton" Width="Auto" >Yes</Button> <Button Name="NoButton" Width="Auto" >No</Button> <Button Name="CancelButton" Width="Auto" >Cancel</Button> </StackPanel> </Border>
BACKGROUND CODE
private void CommonClickHandler(object sender, RoutedEventArgs e) { FrameworkElement feSource = e.Source as FrameworkElement; switch (feSource.Name) { case "YesButton": // do something here ... break; case "NoButton": // do something ... break; case "CancelButton": // do something ... break; } e.Handled=true; }
路由策略
路由事件使用以下三个路由策略之一:
● Bubbling 冒泡:
针对事件源调用事件处理程序。 路由事件随后会路由到后续的父元素,直到到达元素树的根。
大多数路由事件都使用冒泡路由策略。冒泡路由事件通常用来报告来自不同控件或其他 UI 元素的输入或状态变化。
● Direct 直接:
只有源元素本身才有机会调用处理程序以进行响应。 这与 Windows 窗体用于事件的“路由”相似。
但是,与标准 CLR 事件不同的是,直接路由事件支持类处理(类处理将在下一节中介绍)而且可以由 EventSetter 和 EventTrigger 使用。
● Tunneling 隧道:
最初将在元素树的根处调用事件处理程序。
随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。
在合成控件的过程中通常会使用或处理隧道路由事件,这样,就可以有意地禁止显示复合部件中的事件,或者将其替换为特定于整个控件的事件。
在 WPF 中提供的输入事件通常是以隧道/冒泡对实现的。 隧道事件有时又称作 Preview 事件,这是由隧道/冒泡对所使用的命名约定决定的。
EXAMPLE:
XAML
<Window x:Class="WPFlayout.ContentControl.UnderstandRoutedEvent" 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:WPFlayout.ContentControl" mc:Ignorable="d" Title="UnderstandRoutedEvent" Height="300" Width="300" ButtonBase.Click="button1_Click"> <!--为Window和Grid分别关联ButtonBase的Click事件--> <Grid Name="Grid" ButtonBase.Click="button1_Click"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBox Name="textBox1" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" /> <!--在按钮的Parent中添加ButtonBase的Click事件关联--> <StackPanel Grid.Row="1" Orientation="Horizontal" Name="stk" ButtonBase.Click="button1_Click"> <!--为按钮指定单击事件--> <Button Height="23" Name="button1" Click="button1_Click"> <Button.Content> <StackPanel Orientation="Horizontal"> <!--<Image Source="" Name="img"></Image>--> <TextBlock Text="点击触发单击事件" Name="txt"></TextBlock> </StackPanel> </Button.Content> </Button> <Button Name="button2" Click="button1_Click" Margin="10,0,0,0">点击触发单击事件</Button> </StackPanel> </Grid> </Window>
BACKGROUND CODE
namespace WPFlayout.ContentControl { /// <summary> /// UnderstandRoutedEvent.xaml 的交互逻辑 /// </summary> public partial class UnderstandRoutedEvent : Window { public UnderstandRoutedEvent() { InitializeComponent(); } int i = 0; private void button1_Click(object sender, RoutedEventArgs e) { ++i; StringBuilder eventstr = new StringBuilder(); //获取触发事件的元素 FrameworkElement fe = (FrameworkElement)sender; //显示触发的次序 eventstr.Append("触发时次序:" + i.ToString() + "\n"); eventstr.Append("触发事件的元素名:"); eventstr.Append(fe.Name); eventstr.Append("\n"); //获取事件源,也就是是由哪个元素所引发的事件。 FrameworkElement fe2 = (FrameworkElement)e.Source; eventstr.Append("事件源类型:"); eventstr.Append(e.Source.GetType().ToString()); eventstr.Append("\n"); eventstr.Append(" 名称:"); eventstr.Append(fe2.Name); eventstr.Append("\n"); //获知事件传递的方法 eventstr.Append("路由策略:"); eventstr.Append(e.RoutedEvent.RoutingStrategy); eventstr.Append("\n"); textBox1.Text += eventstr.ToString(); textBox1.ScrollToEnd(); } } }
illustrate
附加事件
在 WPF 中, 还可以定义与附加属性类似的附加事件. 比如, Button 控件的 Click 事件,
很多控件并没有定义这个事件. 附加事件可以像在附加属性一样, 为一个类指定在另外一个类中定义的事件.
注意:
Button 类的 Click 事件定义在基类 ButtonBase 中,但是 ButtonBase 是所有按钮控件的基类,
包括 RadioButton, CheckBox etc. 如果只想处理 Button 的单击事件, 则使用 Button.Click
EXAMPLE:
<Window x:Class="WPFlayout.ContentControl.AttachedEvents" 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:WPFlayout.ContentControl" mc:Ignorable="d" Title="AttachedEvents" Height="300" Width="300"> <StackPanel Name="stackpanel"> <Button Name="btn1">按钮1</Button> <Button Name="btn2">按钮2</Button> <Button Name="btn3">按钮3</Button> <Button Name="btn4">按钮4</Button> <Button Name="btn5">按钮5</Button> </StackPanel> </Window>
BACKGROUND CODE
注意:
附加事件的代码定义与标准 .NET 事件定义有些不用,
不能使用 +=, -= 来附加或者移除事件, 必须调用 AddHandler 方法,该方法定义在 UIElement 类中.
namespace WPFlayout.ContentControl { /// <summary> /// AttachedEvents.xaml 的交互逻辑 /// </summary> public partial class AttachedEvents : Window { public AttachedEvents() { InitializeComponent(); //使用AddHandler方法关联附加事件 stackpanel.AddHandler(Button.ClickEvent, new RoutedEventHandler(OnClick)); } private void OnClick(object sender, RoutedEventArgs e) { if (e.Source is Button) { MessageBox.Show(((Button)e.Source).Content.ToString()); } } } }