每一個.NET開發人員都熟悉「事件」的思想——當有意義的事情發生時,由對象(如WPF元素)發送的用於通知代碼的消息。WPF經過事件路由(event routing)的概念加強了.NET事件模型。事件路由容許源自某個元素的事件由另外一個元素引起。例如,使用事件路由,來自工具欄按鈕的單擊事件可在被代碼處理以前上傳到工具欄,而後上傳到包含工具欄的窗口。編程
事件路由爲在最合適的位置編寫緊湊的、組織良好的用於處理事件的代碼提供了靈活性。要使用WPF內容模型,事件路由也是必需的,內容模型容許使用許多不一樣的元素構建簡單元素(如按鈕),而且這些元素都擁有本身獨立的事件集合。函數
1、定義、註冊和封裝路由事件工具
WPF事件模型和WPF屬性模型很是相似。與依賴項屬性同樣,路由事件由只讀的靜態字段表示,在靜態構造函數中註冊,並經過標準的.NET事件定義進行封裝。this
例如,WPF的Button類提供了你們熟悉的Click事件,該事件繼承自抽象的ButtonBase基類。下面的代碼說明了該事件是如何被定義和註冊的:編碼
public abstract class ButtonBase:ContentControl,... { public static readonly RoutedEvent ClickEvent; static ButtonBase() { ButtonBase.ClickEvent=EventManager.RegisterRoutedEvent("Click",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase)); ... } public event RoutedEventHandler Click { add { base.AddHandler(ButtonBase.ClickEvent,value); } remove { base.RemoveHandler(ButtonBase.ClickEvent,value); } } ... }
依賴項屬性是使用DependencyProperty.Register()方法註冊的,而路由事件是使用EvenetManager.RegisterRoutedEvent()方法註冊的。當註冊事件時,須要制定事件的名稱、路由類型、定義事件處理程序語法的委託以及擁有事件的類。spa
一般,路由事件經過廣泛的.NET事件進行封裝,從而使用全部.NET語言都能訪問他們。事件封裝器可以使用AddHandler()和RemoveHandler()方法添加和刪除已註冊的調用程序,這兩個方法都在FrameworkElement基類中定義,並被每一個WPF元素繼承。設計
2、共享路由事件code
與依賴項屬性同樣,可在類之間共享路由事件的定義。例如,UIElement(該類是全部普通WPF元素的起點)和ContentElement(該類是全部內容元素的起點,內容元素是能夠被放入流文檔中的單獨內容片段)這兩個基類都使用了MouseUp事件。MouseUp事件是由System.Windows.Input.Mouse類定義的。UIElement類和ContentElement類只經過Routed-Event.AddOwner()方法重用MouseUp事件:對象
UIElement.MouseUpEvent=Mouse.MouseUpEvent.AddOwner(typeof(UIElement));
3、引起路由事件blog
固然,與全部事件相似,定義類須要在一些狀況下引起事件。到底在哪裏發生是實現細節。然而,重要的細節是事件不是經過傳統的.NET事件封裝器引起的,而是使用RaiseEvent()方法引起事件,全部元素都從UIElement類基礎了該方法。下面是來自ButtonBase類深層的代碼:
RoutedEventArgs e=new RoutedEventArgs(ButtonBase.ClickEvent,this); base.RaiseEvent(e);
RaiseEvent()方法負責爲每一個已經經過AddHandler()方法註冊的調用程序引起事件。由於AddHandler()方法是公有的,全部調用程序可訪問該方法——他們可以經過直接調用AddHandler()方法註冊他們本身,也可使用事件封裝器。不管使用哪一種方法,當調用RaiseEvent()方法時都會通知他們。
全部WPF事件都爲事件簽名使用熟悉的.NET約定。每一個事件處理程序的第一個參數(sender參數)都提供引起該事件的對象的引用。第二個參數是EventArgs對象,該對象與其餘全部可能很重要的附加細節綁定在一塊兒。例如,MouseUp事件提供了一個MouseEventArgs對象,用於指示當事件發生時按下了哪些鼠標鍵:
private void img_MouseUp(object sender,MouseButtonEventArgs e) { }
在WPF中,若是事件不須要傳遞任何額外細節,可以使用RoutedEventArgs類,該類包含了有關如何傳遞事件的一些細節。若是事件確實須要傳遞額外的信息,那麼須要使用更特殊的繼承自RoutedEventArgs的對象(如上面示例中的MouseButtonEventArgs)。由於每一個WPF事件參數都繼承自RoutedEventArgs類,因此每一個WPF事件處理程序均可訪問與事件路由相關的信息。
4、處理路由事件
正如前幾章介紹,可使用多種方法關聯事件處理程序。最經常使用的方法是爲XAML標記添加事件特性。事件特性按照想要處理的事件命名,它的值就是事件處理程序方法的名稱。下面的示例使用這一語法將Image對象的MouseUp事件鏈接到名爲img_MouseUp的事件處理程序:
<Image Source="a.jpg" Stretch="None" Name="img" MouseUp="img_MouseUp" />
一般約定以"元素名_事件名"的形式命名事件處理程序方法,但這不是必需的。若是沒有爲元素定義名稱(多是由於不須要在代碼的任何地方與元素進行交互),可考慮使用如下形式的事件名稱:
<Button Click="cmdOK_Click">OK</Button>
也可使用代碼鏈接事件。下面的代碼和上面給出的XAML標記具備相同的效果:
img.MouseUp+=new MouseButtonEventHandler(img_MouseUp);
上面的代碼建立了一個針對該事件具備正確的簽名的委託對象(在該例中,是MouseButtonEventHandler委託的實例),並將該委託指向img_MouseUp()方法。而後將該委託添加到img.MouseUp事件的已註冊的事件處理程序列表中。
C#還容許使用更精簡的語法,隱式地建立合適的委託對象:
img.MouseUp+=img_MouseUp;
若是須要動態建立控件,並在窗口生命週期的某一時刻關聯事件處理程序,代碼方法是很是有用的。相比而言,在XAML中關聯的事件總在窗口對象第一次實例化時就被關聯到相應的事件處理程序。代碼方法是XAML更簡單,更精練,若是計劃於非編程人員(如藝術設計人員)合做,這是很是好的。缺點是大量的樣板代碼會使代碼文件變得雜亂五章。
上面的代碼方法依賴與事件封裝器,時間封裝器調用UIElement.AddHandler()方法。也能夠自行經過調用UIElement.AddHandler()方法直接鏈接事件。下面是一個示例:
img.AddHandler(Image.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));
當使用這種方法時,始終須要建立合適的委託類型(如MouseButtonEventHandler),而不能隱式地建立委託對象(這與經過屬性封裝器關聯事件時不一樣)。這是由於UIElement.AddHandler()方法支持全部WPF事件,而且它不知道你想使用的委託類型。
有些開發人員更喜歡使用定義事件的類的名稱,而不是引起事件的類的名稱。例以下面的等效語法使得MouseUp事件在UIElement中定義的這一事實更加清晰:
img.AddHandler(UIElment.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));
若是想斷開事件處理程序,那麼只能使用代碼。可以使用-=運算符,以下所示:
img.MouseUp -=img_MouseUp;
或者使用UIElement.RemoveHandler()方法:
img.RemoveHandler(UIElment.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));
爲同一事件屢次鏈接相同的事件處理程序,在技術角度上可行的,這一般是編碼錯誤的結果(這種狀況下,事件處理程序會觸發屢次)。若是試圖刪除已經鏈接了兩次的事件處理程序,事件仍會觸發事件處理程序,但只觸發一次。