詳解WPF中的隧道路由和冒泡路由事件
WPF中使用路由事件升級(jí)了傳統(tǒng)應(yīng)用開(kāi)發(fā)中的事件,在WPF中使用路由事件能更好的處理事件相關(guān)的邏輯,我們從這篇開(kāi)始整理事件的用法和什么是直接路由,什么是冒泡路由,以及什么是隧道路由。
事件最基本的用法
在基于事件驅(qū)動(dòng)的開(kāi)發(fā)中,把代碼放在響應(yīng)注冊(cè)的事件的處理函數(shù)內(nèi),比如Click事件、MouseDown事件、MouseUp事件等等。每個(gè)控件響應(yīng)自己的注冊(cè)事件,有很多如果在事件上有相互關(guān)聯(lián)和影響的事件,就要在一個(gè)業(yè)務(wù)邏輯里寫比較多的代碼。而路由事件主要的優(yōu)勢(shì)就是路由事件可以在元素樹(shù)上進(jìn)行傳遞,并且沿著元素樹(shù)的傳播途徑被事件處理程序處理。這樣我們寫代碼的過(guò)程中時(shí)就可以更好的組織代碼到合適的位置。
WPF事件模型和WPF屬性模型非常類似,與依賴項(xiàng)屬性一樣,路由事件由只讀的靜態(tài)字段表示,在靜態(tài)構(gòu)造函數(shù)中注冊(cè),并通過(guò)標(biāo)準(zhǔn)的.NET事件定義進(jìn)行封裝。這里我們只講如何更好的使用。原理部分請(qǐng)看源碼。比如ButtonBase提供的Click事件。
<Button Content="事件處理程序" Click="Button_Click"/>
private void Button_Click(object sender, RoutedEventArgs e) { //這是Click事件處理程序代碼部分。 }
在注冊(cè)事件后,在事件處理程序中第一個(gè)參數(shù) sender提供引發(fā)該事件的對(duì)象,第二個(gè)參數(shù)是EventArgs對(duì)象。在WPF中如果事件不需要傳遞額外的信息,可以使用RoutedEventArgs類,如果需要傳遞額外的信息,就要是有繼承自RoutedEventArgs的對(duì)象。比如處理inkcanvas墨跡繪制的。比如處理多點(diǎn)觸控的。這些都是變相繼承RoutedEventArgs類。里面會(huì)包含在這種場(chǎng)景下更加多的信息。
注冊(cè)事件的幾種寫法:
1)在XAML代碼中<Button x:Name="EventMessageButton" Content="事件處理程序" MouseUp="EventMessageButton_MouseUp"/> 2)在cs代碼中 EventMessageButton.MouseUp += EventMessageButton_MouseUp; 3)在cs代碼中 EventMessageButton.MouseUp += new MouseButtonEventHandler(EventMessageButton_MouseUp); private void EventMessageButton_MouseUp(object sender, MouseButtonEventArgs e) { //我是處理程序。 }
第一種寫法:我們使用XAML文件中在Button元素內(nèi)使用MouseUp來(lái)創(chuàng)建后臺(tái)事件處理代碼 Btn_eventMessge_MouseUp
第二種寫法:我們?cè)诤笈_(tái)代碼中使用MouseUp+=的方式注冊(cè)。一種是New MouseButtonEventHandler傳入方法名。一種是匿名的直接傳入方法名,這三種注冊(cè)方式達(dá)成的效果是一樣的。
而這三種實(shí)際上使用的是事件封裝器。另一種方式是通過(guò)使用UIElement.AddHandler來(lái)直接連接事件。這里看個(gè)人習(xí)慣把。但是各種寫法主要解決的問(wèn)題還是解耦,因?yàn)檫@些會(huì)關(guān)聯(lián)到后面的命令,動(dòng)畫。模板。觸發(fā)器。MVVM下的使用,等等。這是個(gè)比較長(zhǎng)久的問(wèn)題。所以在這里,能夠使用,看得明白,目前這個(gè)階段就可以了。
我們繼續(xù)往下。解除關(guān)聯(lián)
在注冊(cè)事件的時(shí)候,最好先使用-=來(lái)解除關(guān)聯(lián),避免多次觸發(fā)不合符預(yù)期的監(jiān)聽(tīng)事件。斷開(kāi)使用-=或者使用UIElement.RemoveHandler來(lái)解除關(guān)聯(lián)。 因?yàn)槭录诙啻?=注冊(cè)事件處理程序是可行的。而事件的多詞解除關(guān)系不會(huì)引發(fā)任何問(wèn)題,因此不要擔(dān)心+=和-=不匹配的問(wèn)題。
public MainWindow() { InitializeComponent(); EventMessageButton.MouseUp -= EventMessageButton_MouseUp; EventMessageButton.MouseUp += EventMessageButton_MouseUp; }
理解路由事件
我們知道了事件可以在元素上注冊(cè)事件處理程序,那么我們知道內(nèi)容控件是可以相互嵌套各種奇奇怪怪的組合以達(dá)到自己想要的效果,在這種情況下我們假設(shè)一個(gè)比較常見(jiàn)的場(chǎng)景。我們有一個(gè)標(biāo)簽,標(biāo)簽中包含一個(gè)StackPanel面板,面板中包含一幅圖片和2個(gè)文本。
<Label BorderBrush="Black" BorderThickness="1"> <StackPanel> <TextBlock Margin="3"> 我是圖片標(biāo)題 </TextBlock> <Image Source="1.png" Stretch="None"/> <TextBlock Margin="3"> 我是圖片正文 </TextBlock> </StackPanel> </Label>
我們的控件來(lái)回嵌套內(nèi)容結(jié)構(gòu)很復(fù)雜了。但是我們想在用戶點(diǎn)擊時(shí)只在一個(gè)地方響應(yīng)我們的代碼。如果為每個(gè)元素都關(guān)聯(lián)同一個(gè)事件處理程序,代碼會(huì)很亂。而且難以維護(hù)。而路由事件就是為了解決這個(gè)問(wèn)題的。路由事件分為三種:
1)和普通的.NET事件類似,直接路由事件(direct event) 他們?cè)从谝粋€(gè)元素,不傳遞給其他元素,比如MouseEnter事件,是直接路由事件。
2)向上傳遞的冒泡路由事件(bubbling event)比如MouseDown事件,是冒泡路由事件,該事件先由被單擊的元素引發(fā),接下來(lái)被該元素的父元素引發(fā),然后被父元素的父元素引發(fā),以此類推。直到元素樹(shù)的頂部。
3)向下傳遞的隧道路由事件(tunneling event) 比如PreviewKeyDown事件,隧道路由事件在事件到達(dá)恰當(dāng)?shù)目臻g之前為預(yù)覽事件和終止事件提供了機(jī)會(huì),比如PreviewKeyDown事件可以截獲是否按下了某個(gè)鍵,首先在窗口級(jí)別上,然后是更具體的容器,直到當(dāng)按下時(shí)具有焦點(diǎn)的元素。
當(dāng)使用EventManager.RegisterEvent()發(fā)給發(fā)注冊(cè)路由事件時(shí),需要傳遞一個(gè)RoutingStrategy枚舉,指示希望用于事件的事件行為。
MouseUP和MouseDown事件都是冒泡路由事件,因此當(dāng)在上面的圖片中按下鼠標(biāo)左鍵后順序觸發(fā)MouseDown事件的順序是冒泡的。我們使用Snoop軟件抓取一下過(guò)程:
從圖中我們看到首先觸發(fā)的是PreviewMouseDown的隧道路由。他可以讓我們有機(jī)會(huì)預(yù)覽事件或終止事件。我們看到了從MainWindow開(kāi)始到最終的Image結(jié)束。我們沒(méi)有終止路由。所以進(jìn)行了下一輪的冒泡路由。從Image開(kāi)始到MainWindow。
整個(gè)流程就結(jié)束了。我們看到路由事件提供了對(duì)事件處理非常豐富的功能。具體的隧道或冒泡行為可以參考RoutedEventArgs中的內(nèi)容。
Source 屬性是引發(fā)事件的的對(duì)象。 OriginalSource是最初是什么對(duì)象引發(fā)了事件。RoutedEvent為觸發(fā)的事件提供的RoutedEvent對(duì)象。里面是需要用到的當(dāng)前的參數(shù),比如鼠標(biāo)坐標(biāo),touch等等。Handled屬性的作用是終止事件是否繼續(xù)傳遞。
我們現(xiàn)在開(kāi)始在這個(gè)例子上添加代碼。用于演示我們?cè)趺刺幚砻芭萋酚伞?/p>
<Window x:Class="WPFEvent.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:WPFEvent" mc:Ignorable="d" MouseUp="EventResponseProcess_MouseUp" Title="MainWindow" Height="450" Width="800"> <Grid Margin="3" MouseUp="EventResponseProcess_MouseUp"> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Label Margin="5" Grid.Row="0" HorizontalAlignment="Left" Background="AliceBlue" BorderBrush="Black" BorderThickness="1" MouseUp="EventResponseProcess_MouseUp"> <StackPanel MouseUp="EventResponseProcess_MouseUp"> <TextBlock Margin="3" MouseUp="SomethingClicked"> 我是圖片標(biāo)題 </TextBlock> <Image Source="1.png" Stretch="None" MouseUp="EventResponseProcess_MouseUp"/> <TextBlock Margin="3"> 我是圖片正文 </TextBlock> </StackPanel> </Label> <ListBox Grid.Row="1" Margin="5" Name="MessageListBox"></ListBox> <CheckBox Grid.Row="2" Margin="5" Name="HandlerCheckBox"> Handle first event </CheckBox> <Button Grid.Row="3" Margin="5" Padding="3" HorizontalAlignment="Right" Name="ClearButton" Click="ClearButton_Click">Clear List</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 WPFEvent { /// <summary> /// MainWindow.xaml 的交互邏輯 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } protected int eventCounter = 0; private void EventResponseProcess_MouseUp(object sender, MouseButtonEventArgs e) { eventCounter++; string message = $"#{ eventCounter}:\r\n Sender: {sender} \r\n Source: {e.Source} \r\n Original Source: {e.OriginalSource}"; MessageListBox.Items.Add(message); e.Handled = (bool)HandleCheckBox.IsChecked; } private void ClearButton_Click(object sender, RoutedEventArgs e) { } } }
和上圖一樣,我們嘗試觀察執(zhí)行過(guò)程??聪掠|發(fā)過(guò)程,我們就能了解這個(gè)冒泡路由的工作過(guò)程了。上面寫的這個(gè)例子。主要是讓我們熟悉對(duì)于事件傳入?yún)?shù)OriginalSource的使用。勾選界面上的HandleCheckBox復(fù)選框可以終止冒泡事件,從而只觸發(fā)第一個(gè)Image的事件??梢宰约簩懘a嘗試一下整個(gè)過(guò)程。
有個(gè)方法可以接收終止的事件消息,使用AddHandler()重載的方法。
這里還有一個(gè)常用的技巧,事件的附加。
如下面的代碼,在StackPanel中不存在Button的Click事件,但是可以通過(guò)ButtonBase.Click獲取按鈕的點(diǎn)擊事件,此事件將會(huì)在StackPanel容器里面的任意按鈕被點(diǎn)擊時(shí)觸發(fā)。
<StackPanel ButtonBase.Click="StackPanel_Click" Margin="5"> <Button Content="按鈕A" Click="Button_Click"/> <Button Content="按鈕B" Click="Button_Click"/> <Button Content="按鈕C" Click="Button_Click"/> </StackPanel>
隧道路由這里就不寫了,前面已經(jīng)講過(guò)。隧道路由命名都是Preview開(kāi)頭的。隧道路由全部結(jié)束了之后,同級(jí)的冒泡路由才開(kāi)始。隧道路由主要是做預(yù)先處理,可以停止路由事件,也可以在事件處理程序中寫一些對(duì)應(yīng)的處理代碼。
這篇文章只是熟悉什么是路由事件,了解什么是冒泡路由、什么是隧道路由。所以理解這些是什么事件,這些事件在如何工作就可以了。后面會(huì)講到Window類的生命周期。才會(huì)去深入分析WPF下的事件過(guò)程。
以上就是詳解WPF中的隧道路由和冒泡路由事件的詳細(xì)內(nèi)容,更多關(guān)于WPF中的隧道路由和冒泡路由事件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Entity?Framework代碼優(yōu)先(Code?First)模式
這篇文章介紹了Entity?Framework代碼優(yōu)先(Code?First)模式,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06C#用Activex實(shí)現(xiàn)Web客戶端讀取RFID功能的代碼
由于要在Web項(xiàng)目中采用RFID讀取功能,所以有必要開(kāi)發(fā)Activex,一般情況下開(kāi)發(fā)Activex都采用VC,VB等,但對(duì)這兩塊不是很熟悉,所以采用C#編寫Activex的方式實(shí)現(xiàn)2011-05-05C# 實(shí)現(xiàn)窗口無(wú)邊框,可拖動(dòng)效果
這篇文章主要介紹了C# 實(shí)現(xiàn)窗口無(wú)邊框,可拖動(dòng)效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2018-03-03C#中數(shù)據(jù)的傳遞以及ToolStripProgressBar
本文主要介紹了C#的數(shù)據(jù)傳遞方法以及ToolStripProgressBar進(jìn)度條的使用。希望對(duì)大家有所幫助,話不多說(shuō),請(qǐng)看下面代碼2016-11-11