3.2自定義路由事件 為了方便程式中對象之間的通信,通常需要我們自己定義一些路由事件。那麼如何去創建自定義路由事件呢?下麵通過一個例子來說明自定義路由事件的創建。 創建自定義路由事件大體來說分為三個步驟: 首先,定義路由事件與依賴屬性的定義手法極為相似——申明一個由public static rea ...
3.2自定義路由事件
為了方便程式中對象之間的通信,通常需要我們自己定義一些路由事件。那麼如何去創建自定義路由事件呢?下麵通過一個例子來說明自定義路由事件的創建。
創建自定義路由事件大體來說分為三個步驟:
- 聲明並註冊路由事件
首先,定義路由事件與依賴屬性的定義手法極為相似——申明一個由public static readonly修飾的RoutedEvent類型的欄位,然後使用EventManager類的RegisterRoutedEvent方法進行註冊。 完整的註冊路由事件的代碼如下:
-
//聲明並註冊路由事件 public static readonly RoutedEvent ClickEvent = EventManger.RegisterRoutedEvent("Click",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase));
我們來分析一下 EventManger.RegisterRoutedEvent這個方法的四個參數。
第一個參數是string類型的,被稱為路由事件的名稱。按照微軟的建議,這個字元串RoutedEvent變數的首碼和CLR事件包裝器的名稱一致。再本例中,路由變數的名稱為ClickEvent,則此字元串為Click。
第二個參數為路由事件的策略,在WPF中,路由事件總共有三種策略:
- Bubble,冒泡式:路由事件從激發它的容器開始,一層層向上傳遞,直至最外層容器(Window或者Page)。
- Tunnel,隧道式:路由事件的傳遞方向和Bubble正好相反,是由UI樹的樹根向激發事件的控制項移動
- Direct,直達式:模仿CLR直接事件,將消息直接送達事件處理器
第三個參數用於指定事件處理器的類型。事件處理器的返回值類型和參數列表必須與此參數指定的委托保持一致,不然會導致編譯時拋出異常。
第四個參數用於指明路由事件的宿主是哪個類型的。
- 為路由事件創建CLR事件包裝
為路由事件添加CLR事件包裝是為了把路由事件暴露的很像一個傳統的直接事件,如果不關註底層實現,程式員是不會感覺到它與傳統的直接事件的區別。編程中仍然可以使用+=號為事件添加事件處理器和使用-=為事件移除不再使用的事件處理器。
- 創建可以激發路由事件的方法
激發路由事件的方法很簡單,首先創建一個需要事件攜帶的消息並把它與路由事件相關聯,然後調用元素的RaiseEvent方法將事件發送出去。
下麵我們自己動手創建了一個路由事件,這個事件的用途是報告事件發生的時間。以此為例,說明自定義路由事件的創建方法。
在上文中提到過,創建自定義路由事件有三個步驟,分別是聲明並註冊路由事件、為路由事件添加CLR的包裝、激發路由事件。在本例中,我們要激發路由事件,首先得創建一個需要事件攜帶的消息。正所謂“兵馬未動,糧草先行”,我們首先創建一個用於承載消息的事件參數。
1 //用於承載時間消息的事件參數 2 class ReportTimeEventArgs : RoutedEventArgs 3 { 4 public DateTime ClickTime { get; set; } 5 6 public ReportTimeEventArgs(RoutedEvent routedEvrent,object source):base(routedEvrent,source) 7 { 8 9 } 10 }
然後,在創建一個Button類的派生類並按前述步驟為其添加路由事件:
1 class TimeButton : Button 2 { 3 //聲明、註冊路由事件 4 public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton)); 5 6 //CLR事件包裝器 7 public event RoutedEventHandler ReportTime 8 { 9 add { this.AddHandler(ReportTimeEvent, value); } 10 remove { this.RemoveHandler(ReportTimeEvent,value); } 11 } 12 13 //激發路由事件,借用Click事件的激發方法 14 15 protected override void OnClick() 16 { 17 base.OnClick(); 18 19 ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent, this); 20 args.ClickTime = DateTime.Now; 21 this.RaiseEvent(args); 22 } 23 }
下麵是程式的界面XAML代碼:
1 <Window x:Class="_02_自定義路由事件.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:_02_自定義路由事件" 7 mc:Ignorable="d" 8 Title="Routed Event" Height="300" Width="300" Name="window_1" 9 local:TimeButton.ReportTime="ReportTimeHandle"> 10 <Grid Name="grid_1" local:TimeButton.ReportTime="ReportTimeHandle"> 11 <Grid Name="grid_2" local:TimeButton.ReportTime="ReportTimeHandle"> 12 <Grid Name="grid_3" local:TimeButton.ReportTime="ReportTimeHandle"> 13 <StackPanel Name="stackPanel_1" local:TimeButton.ReportTime="ReportTimeHandle"> 14 <ListBox Name="listBox" Margin="10"></ListBox> 15 <local:TimeButton x:Name="timeButton" Width="80" Height="80" 16 Content="報時" local:TimeButton.ReportTime="ReportTimeHandle"> 17 </local:TimeButton> 18 </StackPanel> 19 </Grid> 20 </Grid> 21 </Grid> 22 </Window>
在UI界面上,以Window為根,套了三層Grid和一層StackPanel(在裡面都設置了Name屬性),在最內部的StackPanel裡面放置了一個ListBox和TimeButton(也就是上面剛剛創建的Button類的派生類)。我們可以看出,從最外層的Window到最內層的TimeButton,都在監聽者ReportTimeEvent 這個路由事件,並用ReportTimeHandle方法來響應這個事件。ReportTimeHandle的代碼如下:
1 //ReportTimeEvent路由事件處理器 2 private void ReportTimeHandle(object sender, ReportTimeEventArgs e) 3 { 4 FrameworkElement element = sender as FrameworkElement; 5 string timeStr = e.ClickTime.ToLongTimeString(); 6 string content = string.Format("{0}到達{1}", timeStr, element.Name); 7 this.listBox.Items.Add(content); 9 }
運行程式,效果如下圖所示:
在這個例子中我們採用的路由事件的策略為冒泡式(bubble),我們對程式做個簡單的修改,在聲明和註冊路由事件時,將其事件的策略改為隧道式(tunnel):
//聲明、註冊路由事件 public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Tunnel, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton));
效果如下圖所示:
對比兩張效果圖,我們可以很清楚的看到,兩種不同的策略所帶來不同,藉此也能更好的理解之前所說的內容。
這時候,大家有個疑問,如果讓一個路由事件在某個地方被處理之後不再向後傳呢?很簡單,在RoutedEventArgs類或者其派生類的實例中,其具有一個bool類型的屬性Handeled,一旦這個屬性被設置為true,那麼路由事件就不會再往下傳遞了。對應於我們這個例子,則需要做以下修改:
1 //ReportTimeEvent路由事件處理器 2 private void ReportTimeHandle(object sender, ReportTimeEventArgs e) 3 { 4 FrameworkElement element = sender as FrameworkElement; 5 string timeStr = e.ClickTime.ToLongTimeString(); 6 string content = string.Format("{0}到達{1}", timeStr, element.Name); 7 this.listBox.Items.Add(content); 8 9 if (element == this.grid_2) 10 { 11 e.Handled = true; 12 } 13 }
這樣修改後的效果圖如下:
To Be Continue!