一、什么是路由事件
路由事件是一種可以針對元素樹中的多個偵聽器而不是僅僅針對引發該事件的對象調用處理程序的事件。路由事件是一個CLR事件。
路由事件與一般事件的區別在於:路由事件是一種用於元素樹的事件,當路由事件觸發后,它可以向上或向下遍歷可視樹和邏輯樹,他用一種簡單而持久的方式在每個元素上觸發,而不需要任何定制的代碼(如果用傳統的方式實現一個操作,執行整個事件的調用則需要執行代碼將事件串聯起來)。
路由事件的路由策略:
所謂的路由策略就是指:路由事件實現遍歷元素的方式。
路由事件一般使用以下三種路由策略:1) 冒泡:由事件源向上傳遞一直到根元素。2) 直接:只有事件源才有機會響應事件。3) 隧道:從元素樹的根部調用事件處理程序並依次向下深入直到事件源。一般情況下,WPF提供的輸入事件都是以隧道/冒泡對實現的。隧道事件常常被稱為Preview事件。
1、冒泡
XAML代碼如下:
1 <Window x:Class="WpfRouteEventByBubble.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 Title="MainWindow" Height="190" Width="246" WindowStartupLocation="CenterScreen"> 5 <Grid x:Name="GridRoot" Background="Lime"> 6 <Grid x:Name="GridA" Margin="10" Background="Blue"> 7 <Grid.ColumnDefinitions> 8 <ColumnDefinition></ColumnDefinition> 9 <ColumnDefinition></ColumnDefinition> 10 </Grid.ColumnDefinitions> 11 <Canvas x:Name="CanvasLeft" Grid.Column="0" Background="Red" Margin="10"> 12 <Button x:Name="ButtonLeft" Width="65" Height="100" Margin="10" Content="Left"></Button> 13 </Canvas> 14 <Canvas x:Name="CanvasRight" Grid.Column="1" Background="Yellow" Margin="10"> 15 <Button x:Name="ButtonRight" Width="65" Height="100" Margin="10" Content="Right"></Button> 16 </Canvas> 17 </Grid> 18 </Grid> 19 </Window>
運行效果如下所示:
當單擊Left按鈕的時候,Button.Click事件被觸發,並且沿着ButtonLeft→CanvasLeft→GridA→GridRoot→Window這條路線向上傳遞,當單擊Right按鈕就會沿着ButtonRight→CanvasRight→GridA→GridRoot→Window這條路線向上傳遞,這里還沒有添加監聽器,所以是沒有反應的。
如何加入監聽器,我們可以再XAML中添加,XAML代碼如下:
1 <Window x:Class="WpfRouteEventByBubble.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 Title="MainWindow" Height="190" Width="246" WindowStartupLocation="CenterScreen"> 5 <Grid x:Name="GridRoot" Background="Lime" Button.Click="Button_Click"> 6 <Grid x:Name="GridA" Margin="10" Background="Blue" Button.Click="Button_Click"> 7 <Grid.ColumnDefinitions> 8 <ColumnDefinition></ColumnDefinition> 9 <ColumnDefinition></ColumnDefinition> 10 </Grid.ColumnDefinitions> 11 <Canvas x:Name="CanvasLeft" Grid.Column="0" Background="Red" Margin="10" Button.Click="Button_Click"> 12 <Button x:Name="ButtonLeft" Width="65" Height="100" Margin="10" Content="Left" Button.Click="Button_Click"></Button> 13 </Canvas> 14 <Canvas x:Name="CanvasRight" Grid.Column="1" Background="Yellow" Margin="10" Button.Click="Button_Click"> 15 <Button x:Name="ButtonRight" Width="65" Height="100" Margin="10" Content="Right" Button.Click="Button_Click"></Button> 16 </Canvas> 17 </Grid> 18 </Grid> 19 </Window>
我們在XAML代碼中添加了Button.Click="Button_Click"這個事件處理器,就是監聽器,並且事件處理交由Button_Click負責,后台Button_Click代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Windows; 7 using System.Windows.Controls; 8 using System.Windows.Data; 9 using System.Windows.Documents; 10 using System.Windows.Input; 11 using System.Windows.Media; 12 using System.Windows.Media.Imaging; 13 using System.Windows.Navigation; 14 using System.Windows.Shapes; 15 16 namespace WpfRouteEventByBubble 17 { 18 /// <summary> 19 /// MainWindow.xaml 的交互邏輯 20 /// </summary> 21 public partial class MainWindow : Window 22 { 23 public MainWindow() 24 { 25 InitializeComponent(); 26 } 27 28 private void Button_Click(object sender, RoutedEventArgs e) 29 { 30 MessageBox.Show("我到達了:" + (sender as FrameworkElement).Name); 31 } 32 } 33 }
我們分析一下,那兩個參數到底是什么呢?




我們會發現,當點擊button按鈕時,ButtonLeft、CanvasLeft、GridA、GridRoot中的事件都會觸發,這就是冒泡路由策略的功能所在,事件首先在源元素上觸發,然后從每一個元素向上沿着樹傳遞,直到到達根元素為止(或者直到處理程序把事件標記為已處理為止),從而調用這些元素中的路由事件。
如果把Button_Click事件修改為:
1 private void Button_Click(object sender, RoutedEventArgs e) 2 { 3 MessageBox.Show("我到達了:" + (sender as FrameworkElement).Name); 4 e.Handled = true;//讓事件停止冒泡 5 }
則以上事件就不會沿着ButtonLeft→CanvasLeft→GridA→GridRoot→Window這條路線傳遞下去,只會執行ButtonLeft的事件。
二、管道
事件首先是從根元素上被觸發,然后從每一個元素向下沿着樹傳遞,直到到達根元素為止(或者直到到達處理程序把事件標記為已處理為止),他的執行方式正好與冒泡策略相反。
XAML代碼如下;
1 <Window x:Class="Wpf路由事件管道策略.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 Title="MainWindow" Height="350" Width="525" WindowStartupLocation="CenterScreen" PreviewMouseDown="Window_PreviewMouseDown"> 5 <Grid x:Name="grid" PreviewMouseDown="grid_PreviewMouseDown"> 6 <Button Height="30" Width="100" Content="點擊我" PreviewMouseDown="Button_PreviewMouseDown"></Button> 7 </Grid> 8 </Window>
后台代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Windows; 7 using System.Windows.Controls; 8 using System.Windows.Data; 9 using System.Windows.Documents; 10 using System.Windows.Input; 11 using System.Windows.Media; 12 using System.Windows.Media.Imaging; 13 using System.Windows.Navigation; 14 using System.Windows.Shapes; 15 16 namespace Wpf路由事件管道策略 17 { 18 /// <summary> 19 /// MainWindow.xaml 的交互邏輯 20 /// </summary> 21 public partial class MainWindow : Window 22 { 23 public MainWindow() 24 { 25 InitializeComponent(); 26 } 27 28 private void Window_PreviewMouseDown(object sender, MouseButtonEventArgs e) 29 { 30 MessageBox.Show("windows被點擊"); 31 } 32 33 private void grid_PreviewMouseDown(object sender, MouseButtonEventArgs e) 34 { 35 MessageBox.Show("grid被點擊"); 36 } 37 38 private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e) 39 { 40 MessageBox.Show("button被點擊"); 41 } 42 } 43 }
程序運行效果:
特別值得注意的是:管道事件按照慣例,他們的名字中都有一個preview前綴,一般來說管道事件都有他的配對的冒泡事件,例如:PreviewMouseDown和MouseDown就是配對事件,如果同時存在的話,那么就會先執行管道事件然后才執行配對的冒泡事件。當然e.Handled=true,依然能夠阻斷事件。
三、直接策略
事件僅僅在源元素上觸發,這個與普通的.Net事件的行為相同,不同的是這樣的事件仍然會參與一些路由事件的特定機制,如事件觸發器等。
該事件唯一可能的處理程序是與其掛接的委托。
路由事件的事件處理程序的簽名(即方法的參數):
他與通用的.net事件處理程序的模式一致,也有兩個參數:第一個為:System.Object對象,名為sender,第二個參數(一般名為e)是一個派生於System.EventArgs的類。sender參數就是該處理程序被添加的元素,參數e是RoutedEventArgs的一個實例提供了4個有用的屬性:
Source---邏輯樹中開始觸發該事件的的元素。
originalSource--可視樹中一開始觸發該事件的元素。
handled---布爾值,設置為true表示事件已處理,在這里停止。
RoutedEvent---真正的路由事件對象,(如Button.ClickEvent)當一個事件處理程序同時用於多個路由事件時,它可以有效地識別被出發的事件。