WPF路由事件中的三種策略介紹
什么是路由事件
路由事件是具有更強(qiáng)傳播能力的事件,它可以在元素樹中向上冒泡和向下隧道傳播,并且能夠沿著傳播路徑被事件處理程序來處理。
路由事件允許事件在某個元素上被處理,即使這個事件源自于另外一個元素。事件路由允許某個元素的事件由另外一個元素引發(fā)。
路由事件是一種可以針對元素樹中的多個偵聽器而不是僅僅針對引發(fā)該事件的對象調(diào)用處理程序的事件。路由事件是一個CLR事件。
路由事件與一般事件的區(qū)別在于:路由事件是一種用于元素樹的事件,當(dāng)路由事件觸發(fā)后,它可以向上或向下遍歷可視樹和邏輯樹,他用一種簡單而持久的方式在每個元素上觸發(fā),而不需要任何定制的代碼(如果用傳統(tǒng)的方式實現(xiàn)一個操作,執(zhí)行整個事件的調(diào)用則需要執(zhí)行代碼將事件串聯(lián)起來)。
路由事件的路由策略:
所謂的路由策略就是指:路由事件實現(xiàn)遍歷元素的方式。
路由事件一般使用以下三種路由策略:
- 冒泡路由事件:冒泡路由事件在包含層次中向上傳遞,即由事件源向上傳遞一直到根元素。
- 直接路由事件:直接路由事件與普通的.NET事件是非常相似的,他們都起源于一個元素,并且不能夠傳遞給其它的元素。 只有事件源才有機(jī)會響應(yīng)事件。
- 隧道路由事件:從元素樹的根部調(diào)用事件處理程序并依次向下深入直到事件源。一般情況下,WPF提供的輸入事件都是以隧道/冒泡對實現(xiàn)的。隧道事件常常被稱為Preview事件。
1、冒泡路由事件
XAML代碼如下:
<Window x:Class="WpfRouteEventByBubble.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="190" Width="246" WindowStartupLocation="CenterScreen">
<Grid x:Name="GridRoot" Background="Lime">
<Grid x:Name="GridA" Margin="10" Background="Blue">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Canvas x:Name="CanvasLeft" Grid.Column="0" Background="Red" Margin="10">
<Button x:Name="ButtonLeft" Width="65" Height="100" Margin="10" Content="Left"></Button>
</Canvas>
<Canvas x:Name="CanvasRight" Grid.Column="1" Background="Yellow" Margin="10">
<Button x:Name="ButtonRight" Width="65" Height="100" Margin="10" Content="Right"></Button>
</Canvas>
</Grid>
</Grid>
</Window>運(yùn)行效果如下所示:

當(dāng)單擊Left按鈕的時候,Button.Click事件被觸發(fā),并且沿著ButtonLeft→CanvasLeft→GridA→GridRoot→Window這條路線向上傳遞,當(dāng)單擊Right按鈕就會沿著ButtonRight→CanvasRight→GridA→GridRoot→Window這條路線向上傳遞,這里還沒有添加監(jiān)聽器,所以是沒有反應(yīng)的。
如何加入監(jiān)聽器,我們可以再XAML中添加,XAML代碼如下:
<Window x:Class="WpfRouteEventByBubble.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="190" Width="246" WindowStartupLocation="CenterScreen">
<Grid x:Name="GridRoot" Background="Lime" Button.Click="Button_Click">
<Grid x:Name="GridA" Margin="10" Background="Blue" Button.Click="Button_Click">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Canvas x:Name="CanvasLeft" Grid.Column="0" Background="Red" Margin="10" Button.Click="Button_Click">
<Button x:Name="ButtonLeft" Width="65" Height="100" Margin="10" Content="Left" Button.Click="Button_Click"></Button>
</Canvas>
<Canvas x:Name="CanvasRight" Grid.Column="1" Background="Yellow" Margin="10" Button.Click="Button_Click">
<Button x:Name="ButtonRight" Width="65" Height="100" Margin="10" Content="Right" Button.Click="Button_Click"></Button>
</Canvas>
</Grid>
</Grid>
</Window>我們在XAML代碼中添加了Button.Click="Button_Click"這個事件處理器,就是監(jiān)聽器,并且事件處理交由Button_Click負(fù)責(zé),后臺Button_Click代碼如下:
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 WpfRouteEventByBubble
{
/// <summary>
/// MainWindow.xaml 的交互邏輯
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("我到達(dá)了:" + (sender as FrameworkElement).Name);
}
}
}我們分析一下,那兩個參數(shù)到底是什么呢?
- 參數(shù)一:sender,這是聽者,就是監(jiān)聽的地方,如果點擊了Left按鈕,那么Left按鈕就會大聲說:“我被點擊了”這個事件向上傳遞,知道到了設(shè)有監(jiān)聽Button.Click事件的地方,這個地方就是sender。
- 參數(shù)二:是RoutEventArgs類型的,這個參數(shù)攜帶了一些重要信息,例如事件是從哪里來的,上一個傳到哪里等,都可以利用這個參數(shù)來查詢。
運(yùn)行效果如下:




我們會發(fā)現(xiàn),當(dāng)點擊button按鈕時,ButtonLeft、CanvasLeft、GridA、GridRoot中的事件都會觸發(fā),這就是冒泡路由策略的功能所在,事件首先在源元素上觸發(fā),然后從每一個元素向上沿著樹傳遞,直到到達(dá)根元素為止(或者直到處理程序把事件標(biāo)記為已處理為止),從而調(diào)用這些元素中的路由事件。
如果把Button_Click事件修改為:
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("我到達(dá)了:" + (sender as FrameworkElement).Name);
e.Handled = true;//讓事件停止冒泡
}則以上事件就不會沿著ButtonLeft→CanvasLeft→GridA→GridRoot→Window這條路線傳遞下去,只會執(zhí)行ButtonLeft的事件。
MouseUp就是一個冒泡路由事件,看下面的XAML代碼:
<Window x:Class="路由事件.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:路由事件"
mc:Ignorable="d"
Title="冒泡路由事件" Height="596" Width="886"
MouseUp="SomethingClicked" WindowStartupLocation="CenterScreen">
<Grid Margin="3" MouseUp="SomethingClicked">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label Margin="5" Background="AliceBlue" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Left" MouseUp="SomethingClicked">
<StackPanel MouseUp="SomethingClicked">
<TextBlock Margin="3" MouseUp="SomethingClicked">
Image and picture lable
</TextBlock>
<Image Source="E:\practice\WPF\WPFDemo\路由事件\Image\face.jpeg" Stretch="None" MouseUp="SomethingClicked"></Image>
<TextBlock Margin="3" MouseUp="SomethingClicked">
Courtesy of the StackPanel
</TextBlock>
</StackPanel>
</Label>
<ListBox Margin="5" Name="lstMessage" Grid.Row="1"></ListBox>
<CheckBox Margin="5" Grid.Row="2" Name="chkHandle">Handle first event</CheckBox>
<Button Click="cmdClear_click" Grid.Row="3" HorizontalAlignment="Right" Margin="5" Padding="3">Clear List</Button>
</Grid>
</Window>當(dāng)我們點擊Image的時候,發(fā)發(fā)生冒泡路由事件,會一層層的向外傳遞,傳遞順序:Image->StackPanel->Label->Grid->Window。
后端代碼如下:
using System.Windows;
namespace 路由事件
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
protected int eventCounter = 0;
private void SomethingClicked(object sender, RoutedEventArgs e)
{
eventCounter++;
string message = $"#{eventCounter.ToString()}:\r\n" +
$"Sender:{sender.ToString()}\r\n" +
$"Source:{e.Source}\r\n" +
$"Original Source:{e.OriginalSource}";
lstMessage.Items.Add(message);
// Handled允許終止事件的冒泡或者終止隧道過程
// 設(shè)置Handled=True,事件就不會繼續(xù)傳遞了
e.Handled = (bool)chkHandle.IsChecked;
}
private void cmdClear_click(object sender, RoutedEventArgs e)
{
eventCounter = 0;
lstMessage.Items.Clear();
}
}
}運(yùn)行程序,輸出結(jié)果如下:

可以看到:輸出結(jié)果就是按照我們上面的順序輸出的。我們把Grid的MouseUp事件去掉,在看輸出結(jié)果:

這時就沒有Grid控件了。勾選下面的復(fù)選框,在執(zhí)行結(jié)果:

這時只有Image控件被觸發(fā)了,其它控件的MouseUp被終止了。
2、隧道路由事件
隧道路由事件跟冒泡路由事件一樣,都是在包含層次中,但是隧道路由事件的傳遞方向跟冒泡路由事件正好相反:隧道路由事件首先是從根元素上被觸發(fā),然后從每一個元素向下沿著樹傳遞,直到到達(dá)根元素為止(或者直到到達(dá)處理程序把事件標(biāo)記為已處理為止)。
冒泡路由事件是向上傳遞,隧道路由事件是向下傳遞。
隧道路由事件在事件到達(dá)恰當(dāng)?shù)目丶?,為預(yù)覽事件提供了機(jī)會。首先是在窗口級別上,也就是頂級,然后是更具體的容器,最后直到到達(dá)按下鍵時具有焦點的元素。我們很容易就可以識別隧道事件,因為他們都是以單詞Preview開頭的。WPF通常可以成對的定義冒泡路由事件和隧道路由事件。如果我們找到一個MouseUp的冒泡路由事件,還可以找到一個PreviewMouseUp的隧道路由事件。隧道路由事件總是在冒泡路由事件之前被觸發(fā)。
XAML代碼如下;
<Window x:Class="Wpf路由事件管道策略.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" WindowStartupLocation="CenterScreen" PreviewMouseDown="Window_PreviewMouseDown">
<Grid x:Name="grid" PreviewMouseDown="grid_PreviewMouseDown">
<Button Height="30" Width="100" Content="點擊我" PreviewMouseDown="Button_PreviewMouseDown"></Button>
</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路由事件管道策略
{
/// <summary>
/// MainWindow.xaml 的交互邏輯
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("windows被點擊");
}
private void grid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("grid被點擊");
}
private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("button被點擊");
}
}
}程序運(yùn)行效果:



特別值得注意的是:管道事件按照慣例,他們的名字中都有一個preview前綴,一般來說管道事件都有他的配對的冒泡事件,例如:PreviewMouseDown和MouseDown就是配對事件,如果同時存在的話,那么就會先執(zhí)行管道事件然后才執(zhí)行配對的冒泡事件。當(dāng)然e.Handled=true,依然能夠阻斷事件。
看下面的示例代碼:
<Window x:Class="路由事件.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:路由事件"
mc:Ignorable="d"
Title="隧道路由事件" Height="639" Width="853"
PreviewKeyDown="SomeKeyPressed">
<Grid Margin="3" PreviewKeyDown="SomeKeyPressed">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label Margin="5" Background="AliceBlue" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Stretch" PreviewKeyDown="SomeKeyPressed">
<StackPanel PreviewKeyDown="SomeKeyPressed">
<TextBlock Margin="3" HorizontalAlignment="Center" PreviewKeyDown="SomeKeyPressed">
Image and picture lable
</TextBlock>
<Image Source="E:\practice\WPF\WPFDemo\路由事件\Image\face.jpeg" HorizontalAlignment="Center" Stretch="None" PreviewKeyDown="SomeKeyPressed"></Image>
<DockPanel Margin="0,5,0,0" PreviewKeyDown="SomeKeyPressed">
<TextBlock Margin="3" PreviewKeyDown="SomeKeyPressed">
Type here:
</TextBlock>
<TextBox PreviewKeyDown="SomeKeyPressed"></TextBox>
</DockPanel>
</StackPanel>
</Label>
<ListBox Margin="5" Name="lstMessage" Grid.Row="1"></ListBox>
<CheckBox Margin="5" Grid.Row="2" Name="chkHandle">Handle first event</CheckBox>
<Button Click="cmdClear_click" Grid.Row="3" HorizontalAlignment="Right" Margin="5" Padding="3">Clear List</Button>
</Grid>
</Window>后端事件代碼:
using System.Windows;
namespace 路由事件
{
/// <summary>
/// Window1.xaml 的交互邏輯
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
protected int eventCounter = 0;
private void SomeKeyPressed(object sender, RoutedEventArgs e)
{
eventCounter++;
string message = $"#{eventCounter.ToString()}:\r\n" +
$"Sender:{sender.ToString()}\r\n" +
$"Source:{e.Source}\r\n" +
$"Original Source:{e.OriginalSource}" +
$"Event:{e.RoutedEvent}";
lstMessage.Items.Add(message);
// Handled允許終止事件的冒泡或者終止隧道過程
// 設(shè)置Handled=True,事件就不會繼續(xù)傳遞了
e.Handled = (bool)chkHandle.IsChecked;
}
private void cmdClear_click(object sender, RoutedEventArgs e)
{
eventCounter = 0;
lstMessage.Items.Clear();
}
}
}運(yùn)行結(jié)果:

可以看到:執(zhí)行順序是從頂級元素到最里層的元素。
注意:如果要使用冒泡路由事件,只需要將PreviewKeyDown改為KeyDown即可。
3、直接策略
事件僅僅在源元素上觸發(fā),這個與普通的.Net事件的行為相同,不同的是這樣的事件仍然會參與一些路由事件的特定機(jī)制,如事件觸發(fā)器等。
該事件唯一可能的處理程序是與其掛接的委托。
路由事件的事件處理程序的簽名(即方法的參數(shù)):
他與通用的.net事件處理程序的模式一致,也有兩個參數(shù):第一個為:System.Object對象,名為sender,第二個參數(shù)(一般名為e)是一個派生于System.EventArgs的類。sender參數(shù)就是該處理程序被添加的元素,參數(shù)e是RoutedEventArgs的一個實例提供了4個有用的屬性:
- Source---邏輯樹中開始觸發(fā)該事件的的元素。
- originalSource--可視樹中一開始觸發(fā)該事件的元素。
- handled---布爾值,設(shè)置為true表示事件已處理,在這里停止。
- RoutedEvent---真正的路由事件對象,(如Button.ClickEvent)當(dāng)一個事件處理程序同時用于多個路由事件時,它可以有效地識別被出發(fā)的事件。
以上所述是小編給大家介紹的WPF路由事件中的三種策略,希望對大家有所幫助。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
asp.net core webapi項目配置全局路由的方法示例
這篇文章主要介紹了asp.net core webapi項目配置全局路由的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09
Asp.net core利用dynamic簡化數(shù)據(jù)庫訪問
這篇文章介紹了Asp.net core利用dynamic簡化數(shù)據(jù)庫訪問的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07
ASP.NET中實現(xiàn)文件的保護(hù)性下載基礎(chǔ)篇
許多時候,我們需要在因特網(wǎng)上提供文件下載服務(wù),但是又要防止未經(jīng)授權(quán)的下載,這時該怎么辦?本文將為讀者詳細(xì)介紹一種使用ASP.NET實現(xiàn)的HTTP處理程序的解決方案。2011-02-02
asp.net中執(zhí)行存儲數(shù)據(jù)操作時數(shù)據(jù)被自動截取的一種情況
asp.net中執(zhí)行存儲數(shù)據(jù)操作時數(shù)據(jù)被自動截取的一種情況...2006-09-09
Asp.net FCKEditor 2.6.3 上傳文件沒有權(quán)限解決方法
到Fckeditor官方網(wǎng)站下載FredCK.FCKeditorV2.vs2005 (asp.net)2009-02-02
ASP.NET MVC @Helper輔助方法和@functons自定義函數(shù)的使用方法
本文主要介紹ASP.NET MVC中使用@Helper和@functons自定義一些代碼片段,方便視圖調(diào)用,從而達(dá)到減少重復(fù)代碼,快速開發(fā)的目的,希望對大家有所幫助。2016-04-04

