1什么是路由事件
簡單說,路由事件可以沿着視覺樹VisualTree進行傳遞,在這視覺樹中的所有對象都可以收到這個事件。前提是添加了檢測。
1.1什么是邏輯樹LogicalTree
簡單理解:邏輯樹就是我們在xaml中寫的布局邏輯
如xaml代碼:
<Grid> <StackPanel Orientation="Vertical"> <DockPanel ButtonBase.Click="Button_Click"> <Button Click="Button_Click" Content="right" DockPanel.Dock="Right"/> <Button Content="left" DockPanel.Dock="Left"/> <Button Content="top" DockPanel.Dock="Top"/> <Button Content="bottom" DockPanel.Dock="Bottom"/> </DockPanel> </StackPanel> </Grid>
邏輯就是Grid--StackPanel--CockPanel--Button

1.2什么是視覺樹VisualTree
視覺樹更復雜,在邏輯樹上又添加了更詳細的布局,包含看不見的對象,和控件模板內部的對象,如Button的控件模板內部有一個Border--ConentPresenter--TextBlock

在視覺樹上,紅色框是邏輯樹上有的,藍色框是button內部的控件
在這個視覺樹上,頂部元素是window(最外邊),最內部是TextBlock,那就會有2個方向,從最外到最內,從最內到最外,
從內到外叫 冒泡。
從外到內叫 隧道
wpf中一個事件以Preview開頭的都是隧道事件,如PreviewDrop是隧道事件,不帶Preview的Drop事件是冒泡事件。
一個完整的路由事件的執行順序是先隧道從window傳遞到內部,再冒泡從最內傳遞到最外

比如Button的Click事件,是先由窗體接收到鼠標的點擊操作,先隧道傳到Button,在冒泡傳回窗體,在這個過程中,視覺樹VisualTree中的任何對象都可以接收到Button的Click事件,前提是已添加了接收的操作
簡單來說,邏輯樹是在xaml中我們布局時的邏輯,視覺樹是在邏輯樹的基礎上還包含了看不見的對象和控件模板內的對象
一個路由事件可以使用e.Handl=True,結束傳遞。
2.如何定義
還是上面的例子,讓Grid、StackPanel、DockPanel都接收Button的Click事件。
xaml中:
<Grid ButtonBase.Click="Button_Click" > <StackPanel ButtonBase.Click="Button_Click" Orientation="Vertical" > <DockPanel ButtonBase.Click="Button_Click" > <Button Click="Button_Click" Content="right" DockPanel.Dock="Right" /> <Button Content="left" DockPanel.Dock="Left"/> <Button Content="top" DockPanel.Dock="Top"/> <Button Content="bottom" DockPanel.Dock="Bottom"/> </DockPanel> </StackPanel> </Grid>
Button_Click事件代碼:
private void Button_Click(object sender, RoutedEventArgs e) { Console.WriteLine("Button_Click由 " + sender.ToString()+" 觸發"); }
輸出結果:

或者在代碼中添加:
public MainWindow() { InitializeComponent(); this.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click)); this.grid.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click)); this.stackpanel.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click)); this.dockpanel.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click)); this.button.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click)); }
輸出結果:

3.代碼例子
xaml:
<Grid Name="grid" > <StackPanel Name="stackpanel" Orientation="Vertical" > <DockPanel Name="dockpanel" > <Button Name="button" Content="right" DockPanel.Dock="Right" /> <Button Content="left" DockPanel.Dock="Left"/> <Button Content="top" DockPanel.Dock="Top"/> <Button Content="bottom" DockPanel.Dock="Bottom"/> </DockPanel> </StackPanel> </Grid>
Mainwindow.xaml.cs
public MainWindow() { InitializeComponent(); this.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreMouseLeftButtonDown), true); this.grid.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreMouseLeftButtonDown), true); this.stackpanel.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreMouseLeftButtonDown), true); this.dockpanel.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreMouseLeftButtonDown), true); this.button.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreMouseLeftButtonDown), true); this.AddHandler(Button.MouseLeftButtonDownEvent, new RoutedEventHandler(MouseLeftButtonDown), true); this.grid.AddHandler(Button.MouseLeftButtonDownEvent, new RoutedEventHandler(MouseLeftButtonDown), true); this.stackpanel.AddHandler(Button.MouseLeftButtonDownEvent, new RoutedEventHandler(MouseLeftButtonDown), true); this.dockpanel.AddHandler(Button.MouseLeftButtonDownEvent, new RoutedEventHandler(MouseLeftButtonDown), true); this.button.AddHandler(Button.MouseLeftButtonDownEvent, new RoutedEventHandler(MouseLeftButtonDown), true); }
private new void PreMouseLeftButtonDown(object sender, RoutedEventArgs e)
{
Console.WriteLine("PreMouseLeftButtonDown由 " + sender.ToString() + " 觸發");
}
private new void MouseLeftButtonDown(object sender, RoutedEventArgs e)
{
Console.WriteLine("MouseLeftButtonDownEvent由 " + sender.ToString() + " 觸發");
}
結果:先隧道后冒泡

這兩句代碼作用是一樣的,當發生 PreviewMouseLeftButtonDown事件時執行PreMouseLeftButtonDown方法
Xmal中:
<Button Name="button" PreviewMouseLeftButtonDown="PreMouseLeftButtonDown" Content="right" DockPanel.Dock="Right" />
后台代碼:
this.button.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreMouseLeftButtonDown), true);
4.自定義路由事件
wpf已經有很多路由事件了,為什么還要自定義?作用是什么?
想一下已存在的事件有單擊Click,雙擊DoubleClick,左鍵點擊MouseLeftButtonDown,右鍵單擊MouseRightButtonDown,那我們想實現這樣一個事件,右鍵點擊3次事件,是不是沒有?這時候需要我們自己去定義完成此功能的路由事件,我們自定義的路由事件是和wpf中的其他事件是同一個級別的。
假如右鍵3次單擊的事件 名叫ThreeRightClick,那么ThreeRightClick和MouseLeftButtonDown、MouseRightButtonDown、Click等是一個級別的,也可以是冒泡的隧道的。在調用時的形式和他們沒有區別
調用方式1
<local:Mybutton ThreeRightClick="OnThreeRightClick" PreviewMouseLeftButtonDown="PreMouseLeftButtonDown" Height="30" Width="50" Content="右鍵3擊" />
調用方式2 this.AddHandler(Mybutton.ThreeRightClickEvent, new RoutedEventHandler(OnThreeRightClick));
4.1如何自定義?
public class Mybutton:Button { public Mybutton() { // 給Mybutton添加內置的MouseRightButtonDownEvent事件,用於觸發自定義路由事件ThreeRightClick this.AddHandler(Button.MouseRightButtonDownEvent, new RoutedEventHandler(OnRightClick)); // 給Mybutton添加內置的PreviewMouseRightButtonDownEvent事件,用於觸發自定義路由事件PreviewThreeRightClick this.AddHandler(Button.PreviewMouseRightButtonDownEvent, new RoutedEventHandler(OnPreviewRightClick)); // 給Mybutton添加自定義的ThreeRightClickEvent事件, //this.AddHandler(Mybutton.ThreeRightClickEvent, new RoutedEventHandler(OnThreeRightClick)); } private int cnt1 = 0; //PreviewMouseRightButtonDownEvent的回調函數,在里面去觸發自定義路由事件PreviewThreeRightClick private void OnPreviewRightClick(object sender, RoutedEventArgs e) { cnt1++; if (cnt1 == 3) { //自定義路由事件PreviewThreeRightClick 隧道形式 RoutedEventArgs routedEventArgs = new RoutedEventArgs(Mybutton.PreviewThreeRightClickEvent); this.RaiseEvent(routedEventArgs); cnt1 = 0; } } private int cnt = 0; //MouseRightButtonDownEvent的回調函數,在里面去觸發自定義路由事件ThreeRightClick private void OnRightClick(object sender, RoutedEventArgs e) { cnt++; if(cnt==3) { //自定義路由事件PreviewThreeRightClick 冒泡形式 RoutedEventArgs routedEventArgs = new RoutedEventArgs(Mybutton.ThreeRightClickEvent); this.RaiseEvent(routedEventArgs); cnt=0; } } //此自定義路由事件是冒泡的 public static readonly RoutedEvent ThreeRightClickEvent = EventManager.RegisterRoutedEvent("ThreeRightClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Mybutton)); public event RoutedEventHandler ThreeRightClick { add { AddHandler(ThreeRightClickEvent, value); } remove { RemoveHandler(ThreeRightClickEvent, value); } } //此自定義路由事件是隧道的 public static readonly RoutedEvent PreviewThreeRightClickEvent = EventManager.RegisterRoutedEvent("PreviewThreeRightClick", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(Mybutton)); public event RoutedEventHandler PreviewThreeRightClick { add { AddHandler(PreviewThreeRightClickEvent, value); } remove { RemoveHandler(PreviewThreeRightClickEvent, value); } } }
4.2如何使用自定義路由事件?
xaml中:
<Grid Name="grid" local:Mybutton.ThreeRightClick="OnThreeRightClick" local:Mybutton.PreviewThreeRightClick="OnPreviewThreeRightClick"> <local:Mybutton x:Name="mybutton" ThreeRightClick="OnThreeRightClick" PreviewThreeRightClick="OnPreviewThreeRightClick" Height="30" Width="50" Content="右鍵3擊" /> </Grid>
或者代碼中:
public MainWindow() { InitializeComponent(); this.AddHandler(Mybutton.ThreeRightClickEvent, new RoutedEventHandler(OnThreeRightClick)); this.AddHandler(Mybutton.PreviewThreeRightClickEvent, new RoutedEventHandler(OnPreviewThreeRightClick)); this.grid.AddHandler(Mybutton.ThreeRightClickEvent, new RoutedEventHandler(OnThreeRightClick)); this.grid.AddHandler(Mybutton.PreviewThreeRightClickEvent, new RoutedEventHandler(OnPreviewThreeRightClick)); this.mybutton.AddHandler(Mybutton.ThreeRightClickEvent, new RoutedEventHandler(OnThreeRightClick)); this.mybutton.AddHandler(Mybutton.PreviewThreeRightClickEvent, new RoutedEventHandler(OnPreviewThreeRightClick)); }


可以看到我們自定義的路由事件ThreeRightClick和PreviewThreeRightClick遵循冒泡和隧道順序。
自定義的路由事件,再復雜的,也是由一個簡單的事件引起的。這里是MouseRightButtonDown。需要注意簡單事件的路由規則和自定義的路由事件的路由規則要對應。
畫個圖整理一下其中的邏輯是怎樣的:

就是說,注冊路由事件,和路由事件觸發時執行什么操作不是同一個概念。注冊路由事件是向wpf系統中注冊,而路由事件被觸發時執行的操作是開發者的邏輯代碼。
就像click事件執行時,我們需要編寫一個方法去執行開發者的邏輯。注冊路由事件的含義就是開發者根據wpf的語法,去注冊一個類似於click事件的路由事件
