走進WPF之路由事件


為了降低由事件訂閱帶來的耦合度,和代碼量,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     }

自定義路由事件

創建自定義路由事件,步驟如下:

  1. 聲明並注冊路由事件。
  2. 為路由事件添加CLR事件包裝器。
  3. 創建可以激發事件的方法。

具體操作步驟如下:

首先創建的自定義控件,繼承自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路由事件的簡單用法,旨在拋磚引玉,共同學習,一起進步。

 

備注

題李凝幽居

【作者】賈島  【朝代】唐

閑居少鄰並,草徑入荒園。鳥宿池邊樹,僧敲月下門。

過橋分野色,移石動雲根。暫去還來此,幽期不負言。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM