天天看點

【WPF學習】第十三章 了解路由事件

  每個.NET開發人員都熟悉“事件”的思想——當有意義的事情發生時,由對象(如WPF元素)發送的用于通知代碼的消息。WPF通過事件路由(event routing)的概念增強了.NET事件模型。事件路由允許源自某個元素的事件由另一個元素引發。例如,使用事件路由,來自工具欄按鈕的單擊事件可在被代碼處理之前上傳到工具欄,然後上傳到包含工具欄的視窗。

  事件路由為在最合适的位置編寫緊湊的、組織良好的用于處理事件的代碼提供了靈活性。要使用WPF内容模型,事件路由也是必需的,内容模型允許使用許多不同的元素建構簡單元素(如按鈕),并且這些元素都擁有自己獨立的事件集合。

一、定義、注冊和封裝路由事件

  WPF事件模型和WPF屬性模型非常類似。與依賴項屬性一樣,路由事件由隻讀的靜态字段表示,在靜态構造函數中注冊,并通過标準的.NET事件定義進行封裝。

  例如,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()方法注冊的。當注冊事件時,需要制定事件的名稱、路由類型、定義事件處理程式文法的委托以及擁有事件的類。

  通常,路由事件通過普遍的.NET事件進行封裝,進而使用所有.NET語言都能通路他們。事件封裝器可使用AddHandler()和RemoveHandler()方法添加和删除已注冊的調用程式,這兩個方法都在FrameworkElement基類中定義,并被每個WPF元素繼承。

二、共享路由事件

  與依賴項屬性一樣,可在類之間共享路由事件的定義。例如,UIElement(該類是所有普通WPF元素的起點)和ContentElement(該類是所有内容元素的起點,内容元素是可以被放入流文檔中的單獨内容片斷)這兩個基類都使用了MouseUp事件。MouseUp事件是由System.Windows.Input.Mouse類定義的。UIElement類和ContentElement類隻通過Routed-Event.AddOwner()方法重用MouseUp事件:

UIElement.MouseUpEvent=Mouse.MouseUpEvent.AddOwner(typeof(UIElement));      

三、引發路由事件

  當然,與所有事件類似,定義類需要在一些情況下引發事件。到底在哪裡發生是實作細節。然而,重要的細節是事件不是通過傳統的.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事件處理程式都可通路與事件路由相關的資訊。

四、處理路由事件

  正如前幾章介紹,可以使用多種方法關聯事件處理程式。最常用的方法是為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));      

  為同一事件多次連接配接相同的事件處理程式,在技術角度上可行的,這通常是編碼錯誤的結果(這種情況下,事件處理程式會觸發多次)。如果試圖删除已經連接配接了兩次的事件處理程式,事件仍會觸發事件處理程式,但隻觸發一次。

作者:Peter Luo

出處:https://www.cnblogs.com/Peter-Luo/

本文版權歸作者和部落格園共有,歡迎轉載,但必須給出原文連結,并保留此段聲明,否則保留追究法律責任的權利。

繼續閱讀