WPF之路-路由事件
理解路由事件
路由事件是一種可以針對元素樹中的多個偵聽器而不是僅僅針對引發該事件的物件呼叫處理程式的事件,也就是說,觸發事件源的父級或子級如果都有對該事件的監聽,則都能觸發事件
路由事件與一般事件的區別在於:路由事件是一種用於元素樹的事件,當路由事件觸發後,它可以向上或向下遍歷可視樹和邏輯樹,他用一種簡單而持久的方式在每個元素上觸發,而不需要任何定製的程式碼(如果用傳統的方式實現一個操作,執行整個事件的呼叫則需要執行程式碼將事件串聯起來)
路由事件的路由策略
所謂的路由策略就是指:路由事件實現遍歷元素的方式
路由事件一般使用以下三種路由策略:
- 冒泡:由事件源向上傳遞一直到根元素
- 直接:只有事件源才有機會響應事件
- 隧道:從元素樹的根部呼叫事件處理程式並依次向下深入直到事件源
一般情況下,WPF提供的輸入事件都是以隧道/冒泡對實現的。隧道事件常常被稱為Preview事件
冒泡
程式設計思路是,將多個Grid巢狀起來,構成父子結構,在最底層支Grid上定義一個按鈕,繫結一個單擊事件,並且按鈕的所有低階元素都繫結該事件,它們使用同一個事件處理程式;再定義一個ListBox,當事件處理程式執行的時候,打印出事件激發者的名字,這樣便能看到事件執行的順序
下圖是介面設計:
下面是XMAL程式碼,按鈕和每一個Grid都綁定了Button.Click="Btn_Click"
<Window x:Class="WPF_CODE.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<!--最外層的Grid-->
<Grid Button.Click="Btn_Click" Name="Grid_1" Background ="#FF43AEAE">
<!--定義兩行-->
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<!--第二層Grid-->
<Grid Button.Click="Btn_Click" Name="Grid_2" Margin="10" Background="#FF8D888D" Grid.Row="0">
<!--定義兩列-->
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<!--第三層左側Grid-->
<Grid Button.Click="Btn_Click" Name="Grid_3_Left" Grid.Column="0" Background="#FF3D8F3A" Margin="10">
<!--新增一個按鈕-->
<Button Button.Click="Btn_Click" Name="ButtonLeft" Width="80" Height="50" Content="Hello"/>
</Grid>
<!--第三層右側Grid-->
<Grid Button.Click="Btn_Click" Name="Grid_3_Right" Grid.Column="1" Background="#FFC95E3E" Margin="10">
<!--新增一個按鈕-->
<Button Button.Click="Btn_Click" Name="ButtonRight" Width="80" Height="50" Margin="10" Content="World"></Button>
</Grid>
</Grid>
<!--定義一個ListBox,用於輸出結果-->
<ListBox Name="Print_List" Grid.Row="1"/>
</Grid>
</Window>
下面是後端程式碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WPF_CODE
{
/// <summary>
/// MainWindow.xaml 的互動邏輯
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
// 事件處理程式
private void Btn_Click(object sender, RoutedEventArgs e)
{
//sender是指由誰激發了這個事件處理程式,它可以獲取到觸發物件
//通過(sender as FrameworkElement).Name轉換,將觸發控制元件的名稱拿出來,
string message = "觸發者:"+(sender as FrameworkElement).Name.ToString();
this.Print_List.Items.Add(message);
//e包含了與事件相關的一些引數,e.Handled如果設定為True,則表示冒泡不再繼續
//e.Handled = true;
}
}
}
執行程式,點選左側按鈕,結果如下:
點選右側按鈕,結果如下:
可以看出,事件首先在源元素上觸發,然後從每一個元素向上沿著樹傳遞,直到到達根元素為止(或者直到處理程式把事件標記為已處理為止),從而呼叫這些元素中的路由事件
如果將事件處理程式裡的e.Handled = true;
程式碼放開,效果如下
//e包含了與事件相關的一些引數,e.Handled如果設定為True,則表示冒泡不再繼續
e.Handled = true;
即事件處理程式只要被觸發一次,並不會發生冒泡
隧道
隧道的執行順序與冒泡正好相反,直接看例子,然後再解釋,將上便中所有的Click事件改為PreviewMouseDown事件,而繫結的事件處理程式不變
XAML程式碼如下:
<Window x:Class="WPF_CODE.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<!--最外層的Grid-->
<Grid PreviewMouseDown="Btn_Click" Name="Grid_1" Background="#FF43AEAE">
<!--定義兩行-->
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<!--第二層Grid-->
<Grid PreviewMouseDown="Btn_Click" Name="Grid_2" Margin="10" Background="#FF8D888D" Grid.Row="0">
<!--定義兩列-->
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<!--第三層左側Grid-->
<Grid PreviewMouseDown="Btn_Click" Name="Grid_3_Left" Grid.Column="0" Background="#FF3D8F3A" Margin="10">
<!--新增一個按鈕-->
<Button PreviewMouseDown="Btn_Click" Name="ButtonLeft" Width="80" Height="50" Content="Hello"/>
</Grid>
<!--第三層右側Grid-->
<Grid PreviewMouseDown="Btn_Click" Name="Grid_3_Right" Grid.Column="1" Background="#FFC95E3E" Margin="10">
<!--新增一個按鈕-->
<Button PreviewMouseDown="Btn_Click" Name="ButtonRight" Width="80" Height="50" Margin="10" Content="World"></Button>
</Grid>
</Grid>
<!--定義一個ListBox,用於輸出結果-->
<ListBox Name="Print_List" Grid.Row="1"/>
</Grid>
</Window>
執行程式,檢視效果:
可以看出,隧道是指事件首先是從根元素上被觸發,然後從每一個元素向下沿著樹傳遞,直到到達根元素為止(或者直到到達處理程式把事件標記為已處理為止),他的執行方式正好與冒泡策略相反
所有的隧道事件都以Preview
開頭
後臺程式碼記得將e.Handled=true
註釋掉
直接策略
事件僅僅在源元素上觸發,這個與普通的.Net事件的行為相同,不同的是這樣的事件仍然會參與一些路由事件的特定機制,如事件觸發器等;該事件唯一可能的處理程式是與其掛接的委託