深入淺出WPF——附加事件(Attached Event)


 

3.3 事件也附加——深入淺出附加事件

                WPF事件系統中還有一種事件被稱為附加事件(Attached Event),簡言之,它就是路由事件。“那為什么還要起個新名字呢?”你可能會問。

                “身無彩鳳雙飛翼,心有靈犀一點通”,這就是對附加事件宿主的真實寫照。怎么解釋呢?讓我們看看都有哪些類擁有附加事件:

  • Binding類:SourceUpdated事件,TargetUpdated事件
  • Mouse類:MouseEnter事件、MouseLeave事件、MouseDown事件、MouseUp事件,等等
  • Keyboard類:KeyDown事件、KeyUp事件,等等

再對比一下那些擁有路由事件的類,諸如Button、Slider、TextBox……發現什么問題了嗎?原來,路由事件的宿主都是些擁有可視化實體的界面元素,而附加事件則不具備顯示在用戶界面上的能力。也就是說,附加事件的宿主沒有界面渲染功能這雙“飛翼”,但一樣可以使用附加事件這個“靈犀”與其他對象進行溝通。

                理解了附加事件的原理,讓我們動手寫一個例子。我想實現的邏輯是這樣的:設計一個名為Student的類,如果Student實例的Name屬性值發生了變化就激發一個路由事件,我會使用界面元素來捕捉這個事件。

                這個類的代碼如下:

1 public class Student
2 {
3     // 聲明並定義路由事件
4     public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRoutedEvent
5         ("NameChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Student));
6 
7     public int Id { get; set; }
8     public string Name { get; set; }        
9 }

設計一個簡單的界面:

1 <Window x:Class="WpfApplication1.Window1"
2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Attached Event"
4         Height="200" Width="200">
5     <Grid x:Name="gridMain">
6         <Button x:Name="button1" Content="OK" Width="80" Height="80"
7                 Click="Button_Click" />
8     </Grid>
9 </Window>

   其后台代碼如下:

 1 public partial class Window1 : Window
 2 {
 3     public Window1()
 4     {
 5         InitializeComponent();
 6 
 7         // 為外層Grid添加路由事件偵聽器
 8         this.gridMain.AddHandler(
 9             Student.NameChangedEvent,
10             new RoutedEventHandler(this.StudentNameChangedHandler));
11     }
12 
13     // Click事件處理器
14     private void Button_Click(object sender, RoutedEventArgs e)
15     {
16         Student stu = new Student() { Id = 101, Name = "Tim" };
17         stu.Name = "Tom";
18         //  准備事件消息並發送路由事件
19         RoutedEventArgs arg = new RoutedEventArgs(Student.NameChangedEvent, stu);
20         this.button1.RaiseEvent(arg);
21     }
22 
23     // Grid捕捉到NameChangedEvent后的處理器
24     private void StudentNameChangedHandler(object sender, RoutedEventArgs e)
25     {
26         MessageBox.Show((e.OriginalSource as Student).Id.ToString());
27     }
28 }

后台代碼中,當界面上唯一的Button被點擊后會觸發Button_Click這個方法。有一點必須注意的是:因為Student不是UIElement的派生類,所以它不具有RaiseEvent這個方法,為了發送路由事件就不得不“借用”一下Button的RaiseEvent方法了。在窗體的構造器中我為Grid元素添加了對Student.NameChangedEvnet的偵聽——與添加對路由事件的偵聽沒有任何區別。Grid在捕捉到路由事件后會顯示事件消息源(一個Student實例)的Id。

                運行程序並點擊按鈕,效果如圖:

 

 

 

                理論上現在的Student類已經算是具有一個附加事件了,但微軟的官方文檔約定要為這個附加事件添加一個CLR包裝以便XAML編輯器識別並進行智能提示。可惜的是,Student類並非派生自UIElement,因此亦不具備AddHandler和RemoveHandler兩個方法,所以不能使用CLR屬性作為包裝器(因為CLR屬性包裝器的add和remove分支分別調用當前對象的AddHandler和RemoveHandler)。微軟規定:

  • 為目標UI元素添加附加事件偵聽器的包裝器是一個名為Add*Handler的public static方法,星號代表事件名稱(與注冊事件時的名稱一致)。此方法接收兩個參數,第一個參數是事件的偵聽者(類型為DependencyObject),第二個參數為事件的處理器(RoutedEventHandler委托類型)。
  • 解除UI元素對附加事件偵聽的包裝器是名為Remove*Handler的public static方法,星號亦為事件名稱,參數與Add*Handler一致。

按照規范,Student類被升級為這樣:

 

 1 public class Student
 2 {
 3     // 聲明並定義路由事件
 4     public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRoutedEvent
 5         ("NameChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Student));
 6 
 7     // 為界面元素添加路由事件偵聽
 8     public static void AddNameChangedHandler(DependencyObject d, RoutedEventHandler h)
 9     {
10         UIElement e = d as UIElement;
11         if (e != null)
12         {
13             e.AddHandler(Student.NameChangedEvent, h);
14         }
15     }
16 
17     // 移除偵聽
18     public static void RemoveNameChangedHandler(DependencyObject d, RoutedEventHandler h)
19     {
20         UIElement e = d as UIElement;
21         if (e != null)
22         {
23             e.RemoveHandler(Student.NameChangedEvent, h);
24         }
25     }
26 
27     public int Id { get; set; }
28     public string Name { get; set; }
29 }

  原來的代碼也需要做出相應的改動(只有添加事件偵聽一處需要改動):

1 public Window1()
2 {
3     InitializeComponent();
4 
5     // 為外層Grid添加路由事件偵聽器
6     Student.AddNameChangedHandler(
7         this.gridMain,
8         new RoutedEventHandler(this.StudentNameChangedHandler));
9 }

 現在讓我們仔細理解一下附加事件的“附加”。確切地說,UIElement類是路由事件宿主與附加事件宿主的分水嶺,不單是因為從UIElement類開始才具備了在界面上顯示的能力,還因為RaiseEvent、AddHander和RemoveHandler這些方法也定義在UIElement類中。因此,如果在一個非UIElement派生類中注冊了路由事件,則這個類的實例即不能自己激發(Raise)此路由事件也無法自己偵聽此路由事件,只能把這個事件的激發“附着”在某個具有RaiseEvent方法的對象上、借助這個對象的RaiseEvent方法把事件發送出去;事件的偵聽任務也只能交給別的對象去做。總之,附加事件只能算是路由事件的一種用法而非一個新概念,說不定哪天微軟就把附加事件這個概念撤消了。

                最后分享些實際工作中的經驗。

第一,像Button.Click這些路由事件,因為事件的宿主是界面元素、本身就是UI樹上是一個結點,所以路由事件路由時的第一站就是事件的激發者。附加事件宿主不是UIElement的派生類,所以不能出現在UI樹上的結點,而且附加事件的激發是借助UI元素實現的,因此,附加事件路由的第一站是激發它的元素。

第二,實際上很少會把附加事件定義在Student這種與業務邏輯相關的類中,一般都是定義在像Binding、Mouse、Keyboard這種全局的Helper類中。如果需要業務邏輯類的對象能發送出路由事件來怎么辦?我們不是有Binding嗎!如果程序架構設計的好(使用數據驅動UI),那么業務邏輯一定會使用Binding對象與UI元素關聯,一旦與業務邏輯相關的對象實現了INotifyPropertyChanged接口並且Binding對象的NotifyOnSourceUpdated屬性設為true,則Binding就會激發其SourceUpdated附加事件,此事件會在UI元素樹上路由並被偵聽者捕獲。

 

轉載:http://blog.csdn.net/FantasiaX/article/details/4575968

相關:http://blog.csdn.net/fwj380891124/article/details/8139260


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM