為了降低由事件訂閱帶來的耦合度,和代碼量,WPF推出了路由事件機制。路由事件與直接事件的區別在於,直接事件激發時,發送者直接將消息通過事件訂閱者交給事件響應者,事件響應者對事件的發生做出響應。路由事件的訂閱者和響應者之間沒有直接顯式的訂閱關系,事件的擁有者只負責激發事件,事件由誰響應它並不知道,事件響應者通過事件偵聽器進行偵聽。本文以一些簡單的小例子,簡述路由事件的基本使用,僅供學習分享使用,如有不足之處,還請指正。
什么是路由事件?
根據MSDN定義:
- 功能定義:路由事件是一種可以針對元素樹中的多個偵聽器(而不是僅針對引發該事件的對象)調用處理程序的事件。
- 實現定義:路由事件是由 類的實例支持的 CLR 事件, RoutedEvent 由事件 Windows Presentation Foundation (WPF) 系統處理。
典型的 WPF 應用程序中包含許多元素。 無論這些元素是在代碼中創建還是在 XAML 中聲明,它們存在於彼此關聯的元素樹關系中。
路由策略
路由事件使用以下三種路由策略之一:
-
Bubbling【冒泡】: 調用事件源上的事件處理程序。 路由事件隨后會路由到后續的父級元素,直到到達元素樹的根。 大多數路由事件都使用冒泡路由策略。 冒泡路由事件通常用於報告來自不同控件或其他 UI 元素的輸入或狀態變化。
-
Direct【直接】: 只有源元素本身才有機會調用處理程序以進行響應。 這類似於窗體用於事件的Windows路由"。 但是,與標准 CLR 事件不同,直接路由事件支持類處理 (類處理在即將發布的) 節中進行了說明,並且 和 可以使用 EventSetter或 EventTrigger 。
-
Tunneling【隧道】: 最初將調用元素樹的根處的事件處理程序。 隨后,路由事件將朝着路由事件的源節點元素(即引發路由事件的元素)方向,沿路由線路傳播到后續的子元素。 合成控件的過程中通常會使用或處理隧道路由事件,通過這種方式,可以有意地禁止復合部件中的事件,或者將其替換為特定於整個控件的事件。
冒泡策略
事件的冒泡策略,就像水里的泡泡一樣,從底往上,逐級觸發。路由事件隨后會路由到后續的父級元素,直到到達元素樹的根。 大多數路由事件都使用冒泡路由策略。 冒泡路由事件通常用於報告來自不同控件或其他 UI 元素的輸入或狀態變化。
路由事件會像泡泡一樣,逐層響應,示例如下所示:
示例源碼
1 <Window x:Class="WpfApp1.OneWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:WpfApp1" 7 mc:Ignorable="d" 8 x:Name="w1" 9 Title="OneWindow" Height="350" Width="500" Button.Click="btn1_Click"> 10 <Grid x:Name="gdOuter" Button.Click="btn1_Click"> 11 <StackPanel x:Name="sp1" Button.Click="btn1_Click"> 12 <Grid x:Name="gd1" Button.Click="btn1_Click"> 13 <Button x:Name="btn1" Content="點我" Click="btn1_Click" Margin="5" Padding="5" FontSize="18"></Button> 14 </Grid> 15 <RichTextBox x:Name="txtInfo" Margin="5" MinHeight="100" ></RichTextBox> 16 </StackPanel> 17 </Grid> 18 </Window>
隧道策略
隧道策略事件,與冒泡策略剛好相反, 最初將調用元素樹的根處的事件處理程序。 隨后,路由事件將朝着路由事件的源節點元素(即引發路由事件的元素)方向,沿路由線路傳播到后續的子元素。
所有的隧道策略模式事件,都是以Preview開頭。隧道事件有時又稱作預覽事件,這是由該對所使用的命名約定決定的。
隧道策略由頂至下,逐層下探,直至最好一個元素,如下所示:
示例源碼
1 <Window x:Class="WpfApp1.TwoWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:WpfApp1" 7 mc:Ignorable="d" 8 Name="w1" 9 Title="TwoWindow" Height="350" Width="500" Button.PreviewMouseDown="btn1_PreviewMouseDown"> 10 <Grid> 11 <Grid x:Name="gdOuter" Button.PreviewMouseDown="btn1_PreviewMouseDown"> 12 <StackPanel x:Name="sp1" Button.PreviewMouseDown="btn1_PreviewMouseDown"> 13 <Grid x:Name="gd1" Button.PreviewMouseDown="btn1_PreviewMouseDown"> 14 <Button x:Name="btn1" Content="點我" PreviewMouseDown="btn1_PreviewMouseDown" Margin="5" Padding="5" FontSize="18"></Button> 15 </Grid> 16 <RichTextBox x:Name="txtInfo" Margin="5" MinHeight="100" ></RichTextBox> 17 </StackPanel> 18 </Grid> 19 </Grid> 20 </Window>
注意:在 WPF 中提供的輸入事件通常是以隧道/浮升對實現的。
事件阻止
在實際應用中,如果不想事件采用冒泡或隧道策略,向上或向下執行,則需要設置e.Handled=true即可,如下所示:
示例源碼:
1 private void btn1_Click(object sender, RoutedEventArgs e) 2 { 3 this.txtInfo.AppendText(string.Format("當前響應事件對象:{0},響應事件原始對象:{1}\r\n", (sender as FrameworkElement).Name, e.OriginalSource)); 4 e.Handled = true; 5 }
后台添加路由事件
路由事件既可以通過XAML的方式,進行設置,也可以通過后台代碼的方式進行設置,如下所示:
后台設置路由事件,如下所示:
1 /// <summary> 2 /// ThreeWindow.xaml 的交互邏輯 3 /// </summary> 4 public partial class ThreeWindow : Window 5 { 6 public ThreeWindow() 7 { 8 InitializeComponent(); 9 this.btn1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click)); 10 this.gd1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click)); 11 this.sp1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click)); 12 this.gdOuter.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click)); 13 this.w1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click)); 14 } 15 16 17 private void btn1_Click(object sender, RoutedEventArgs e) 18 { 19 this.txtInfo.AppendText(string.Format("當前響應事件對象:{0},響應事件原始對象:{1}\r\n", (sender as FrameworkElement).Name, e.OriginalSource)); 20 //e.Handled = true; 21 } 22 }
自定義路由事件
創建自定義路由事件,步驟如下:
- 聲明並注冊路由事件。
- 為路由事件添加CLR事件包裝器。
- 創建可以激發事件的方法。
具體操作步驟如下:
首先創建的自定義控件,繼承自Button按鈕,如下所示:
1 namespace WpfApp1 2 { 3 /// <summary> 4 /// 自定義路由事件 5 /// </summary> 6 public class TimeButton:Button 7 { 8 /// <summary> 9 /// 聲明和注冊路由事件 10 /// </summary> 11 public static readonly RoutedEvent TimeEvent = EventManager.RegisterRoutedEvent("Time", RoutingStrategy.Bubble, typeof(EventHandler<TimeEventArgs>), typeof(TimeButton)); 12 13 /// <summary> 14 /// 事件包裝器 15 /// </summary> 16 public event RoutedEventHandler Time{ 17 add { this.AddHandler(TimeEvent, value); } 18 remove 19 { 20 this.RemoveHandler(TimeEvent, value); 21 } 22 } 23 24 /// <summary> 25 /// 重寫方法,激發事件 26 /// </summary> 27 protected override void OnClick() 28 { 29 base.OnClick(); 30 TimeEventArgs e = new TimeEventArgs(TimeEvent,this); 31 e.ClickTime = DateTime.Now; 32 this.RaiseEvent(e); 33 } 34 35 } 36 37 public class TimeEventArgs : RoutedEventArgs { 38 public TimeEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { 39 40 } 41 42 public DateTime ClickTime { get; set; } 43 } 44 }
在窗體中,創建自定義按鈕實例。如下所示:
1 <Window x:Class="WpfApp1.A1Window" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:WpfApp1" 7 mc:Ignorable="d" 8 Title="A1Window" Height="450" Width="800"> 9 <Grid x:Name="gd1" local:TimeButton.Time="timeButton1_Time"> 10 <StackPanel x:Name="sp1" local:TimeButton.Time="timeButton1_Time"> 11 <local:TimeButton x:Name="timeButton1" Content="報時" Time="timeButton1_Time"></local:TimeButton> 12 <RichTextBox x:Name="txtInfo" Margin="5" MinHeight="100" ></RichTextBox> 13 </StackPanel> 14 </Grid> 15 </Window>
然后實現事件函數,如下所示:
1 private void timeButton1_Time(object sender, TimeEventArgs e) 2 { 3 this.txtInfo.AppendText(string.Format("當前響應事件對象:{0},響應事件時間為:{1}\r\n", (sender as FrameworkElement).Name, e.ClickTime.ToString("yyyy-MM-dd hh:mm:ss.fff"))); 4 }
自定義路由事件,示例截圖如下:
以上就是WPF路由事件的簡單用法,旨在拋磚引玉,共同學習,一起進步。
備注
題李凝幽居
閑居少鄰並,草徑入荒園。鳥宿池邊樹,僧敲月下門。
過橋分野色,移石動雲根。暫去還來此,幽期不負言。