1. 程式人生 > >WPF 路由事件 Event Routing

WPF 路由事件 Event Routing

1.路由事件介紹

之前介紹了WPF的新的依賴屬性系統,本篇將介紹更高階的路由事件,替換了之前的.net普通事件。相比.net的事件,路由事件具有更強的傳播能力,支援向上冒泡和向下隧道傳播。路由事件允許源自某個元素的事件由另一個元素引發。

2.路由事件定義

WPF事件模型和WPF屬性模型非常類似。都是隻讀的靜態欄位。

    [DefaultEvent("Click")]
    [Localizability(LocalizationCategory.Button)]
    public abstract class ButtonBase : ContentControl, ICommandSource
    {
        public static readonly RoutedEvent ClickEvent;
    }

3.路由事件註冊

和WPF事件模型的註冊與屬性幾乎一樣,使用EventManager.RegisterRoutedEvent()來進行註冊。可以檢視該函式的說明:

        //
        // 摘要:
        //     向 Windows Presentation Foundation (WPF) 事件系統註冊新的路由事件。
        //
        // 引數:
        //   name:
        //     路由事件的名稱。該名稱在所有者型別中必須是唯一的,並且不能為 null 或空字串。
        //
        //   routingStrategy:
        //     作為列舉值的事件的路由策略。
        //
        //   handlerType:
        //     事件處理程式的型別。該型別必須為委託型別,並且不能為 null。
        //
        //   ownerType:
        //     路由事件的所有者類型別。該型別不能為 null。
        //
        // 返回結果:
        //     新註冊的路由事件的識別符號。現在可將該識別符號物件儲存為類中的靜態欄位,然後將其用作將處理程式附加到事件的方法的引數。路由事件識別符號也用於其他事件系統
        //     APIs。
        public static RoutedEvent RegisterRoutedEvent(string name, RoutingStrategy routingStrategy, Type handlerType, Type ownerType);
與依賴屬性同樣,在一個靜態建構函式中註冊:
        static ButtonBase()
        {
            ButtonBase.ClickEvent=EventManager.RegisterRoutedEvent
                ("Click",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase));
        }

4.路由事件包裝



路由事件通過普通的.net事件進行包裝。從而使所有.net語言都能訪問它們。事件包裝器可以使用AddHandler()和RemoveHandler()方法新增和刪除已註冊的呼叫程式。
AddHandler與RemoveHandler在基類UIElement中定義,每個WPF元素都繼承它們。
        // 摘要:
        //     在單擊 System.Windows.Controls.Button 時發生。
        [Category("Behavior")]
        public event RoutedEventHandler Click
        {
            add
            {
                base.AddHandler(ButtonBase.ClickEvent, value);
            }
            remove
            {
                base.RemoveHandler(ButtonBase.ClickEvent, value);
            }
        }

5.路由事件共享



public RoutedEvent AddOwner(Type ownerType);
將路由事件關聯另一個所有者型別,並啟用事件及其處理的路由
WPF中所有控制元件的基類UIElement型別就共享了MouseUp事件。MouseUp事件是在System.Windows.Input.Mouse類定義的。UIElement只是通過了AddOwner()方法重用了MouseUp事件。

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


6.引發路由事件

            RoutedEventArgs args = new RoutedEventArgs(ButtonBase.ClickEvent,this);
            base.RaiseEvent(args);
RaiseEvent方法負責為每個已經通過AddHandler()方法註冊的呼叫程式引發路由事件。 
這裡先貼出來RoutedEventArgs的建構函式。
        // 引數:
        //   routedEvent:
        //     System.Windows.RoutedEventArgs 類的此例項的路由事件識別符號。
        //
        //   source:
        //     將在處理事件時報告的備用源。這將預先填充 System.Windows.RoutedEventArgs.Source 屬性。
        public RoutedEventArgs(RoutedEvent routedEvent, object source);
RoutedEventArgs有下面四個屬性:
1.Source  指定了引發事件的物件 
2.OriginalSource 指出了最初是什麼物件引發了事件。比Source更深一層。
3.Handled 使用者來終止事件的冒泡或者隧道過程。如果一個控制元件將Handled設定為true,剛這個事件就不會繼續傳遞下去。
4.RoutedEvent 獲取或設定與此RoutedEventArgs 例項關聯的路由事件。
通過使用RoutedEventArgs我們可以為事件提供相應的源。在WPF中,如果一個事件確實需要傳遞額外的資訊,我們可以自定義一個物件,繼承自RoutedEventArgs。例如WPF中常見的MouseEventArgs。

7.關聯路由事件



將後臺事件處理程式與前臺元素相關聯有很多種方法。最常見的就是為Xaml新增事件特性。如:
<Button x:Name="button1" Content="ok" Width="80" Height="40" Click="button1_Click"/>
或者不需要元素名,如:
<Button Content="ok" Width="80" Height="40" Click="OK_Click"/>
也可以在後臺程式碼中進行連線事件,如:
img.MouseUp+=new MouseButtonEventHandler(img_MouseUp);
C#還支援隱式地建立委託物件:
img.MouseUp+=img_MouseUp;
上述方法實質上都是呼叫了事件包裝器(小節4)。我們可以直接呼叫UIElement.AddHandler()方法來直接連線事件。如:
img.AddHandler(Image.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));

8.斷開關聯

斷開與路由事件的關聯主要有兩種方法:
1.-=運算子:
img.MouseUp-=img_MouseUp;
2.使用UIElement.RemoveHandler()方法:
img.RemoveHandler(Image.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));

9.最後的例子



與之前一樣,以一個小例子結尾,來幫助理解。程式碼不多,直接貼上來

 public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.AddHandler(BigDog.TestEvent, new RoutedEventHandler(this.TestHandler));
        }
        private void TestHandler(object sender, RoutedEventArgs e)
        {
            MessageBox.Show((e.OriginalSource as BigDog).Name.ToString());
            e.Handled = true;
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            BigDog dear = new BigDog { Name="Dear"};
            RoutedEventArgs args = new RoutedEventArgs(BigDog.TestEvent, dear);
            //引發路由事件
            this.button1.RaiseEvent(args);
        }
    }
    public class BigDog
    {
        public string Name { get; set; }
        //定義路由事件
        public static readonly RoutedEvent TestEvent = EventManager.RegisterRoutedEvent
            ("Test", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(BigDog));
    }