1. 程式人生 > >WPF Event事件

WPF Event事件

1.1 邏輯樹與可視樹

    如果把一片樹葉放在顯微鏡下觀察,你會發現這片葉子也像一棵樹----有自己的基部並向上生長出多級分叉。在WPF的Logic Tree上,扮演葉子的一般都是控制元件。如果我們把WPF中的控制元件也放在顯微鏡下觀察,你會發現WPF控制元件本身也是一棵由更細微級別的元件(他們不是控制元件,而是一些視覺化元件,派生至Visual類)組成的樹。

    在WPF中有兩種樹:邏輯樹(Logical Tree)和可視樹(Visual Tree),XAML是表達WPF的一棵樹。邏輯樹完全是由佈局元件和控制元件構成。如果我們把邏輯樹延伸至Template元件級別,我們就得到了可視樹,所以可視樹把樹分的更細緻。

1.2 事件的來龍去脈

    微軟把訊息機制封裝成了更容易讓人理解的事件模型。


    事件模型隱藏了訊息機制的很多細節,讓程式開發變的簡單。繁瑣的訊息驅動機制在事件模型中被簡化為了3個關鍵點:

事件的擁有者:即訊息的傳送者。事件的宿主可以在某些條件下激發它擁有的事件,事件被觸發則訊息被髮送。

事件的響應者:即訊息的接收者、處理者。事件接收者使用其事件處理器(EventHandler)對事件做出響應。

事件的訂閱關係:事件的擁有者可以隨時激發事件,但事件發生後會不會得到響應要看有沒有事件響應者,或者說要看這個事件是否被關注。如果物件A關注物件B的某個事件是否發生,則稱A訂閱了B的某個事件。更進一步講,事件實際上是一個使用Event關鍵字修飾的委託型別的成員變數,事件處理器則是一個函式,說A訂閱了B的某個事件,本質就是讓B.Event和A.EventHandler關聯起來。所謂事件激發就是B.Event被呼叫,這時,與其關聯的A.EventHandler就會被呼叫。

    在這種模型裡,事件的響應者通過訂閱關係直接關聯在事件擁有者的事件上,為了與WPF路由事件模型分開,我們把這種事件模型稱為直接事件模型或CLR事件模型。每條訊息是“傳送---響應”關係,必須顯示的建立點對點訂閱關係。

1.3 初試路由事件

    為了降低由事件訂閱帶來的耦合度和程式碼量,WPF推出了路由事件機制。路由事件和直接事件的區別在於,直接事件激發時,傳送者直接將訊息通過事件訂閱交給事件的響應者,事件響應者使用其事件處理器方法對事件的發生做出響應驅動邏輯程式按客戶需求執行。路由事件的擁有者和事件響應者之間則沒有直接顯示的訂閱關係,事件的擁有者只負責激發事件,事件將由誰響應它並不知道,事件的響應者則安裝有事件偵聽器,針對某類事件進行偵聽。

當有某類事件傳遞至此時事件響應者就使用事件處理器來響應事件並決定事件是否可以繼續傳遞。

    舉個例子,在Visual Tree上有一個button控制元件,當它被單擊的時候就相當於自己喊了一聲“我被單擊了”,這樣一個button.Click開始在Visual Tree上開始傳播。當事件經過某個節點的時候如果這個節點沒有安裝用於偵聽button.Click事件的“耳朵”,那麼它會無視這個事件,讓它繼續暢通無阻的繼續傳播,如果某個節點安裝了針對button.Click的偵聽器,它的事件處理器就會被呼叫,在事件處理器內部,程式設計師可以檢視路由事件原始的出發點是哪個控制元件,上一站是哪裡,還可以決定事件傳遞到此為止還是可以繼續往下傳遞----路由事件就是這樣依靠“口耳相傳”的辦法將訊息傳遞給“關心”它的控制元件的。雖然WPF推出了路由事件機制,但它仍然支援傳統的直接事件模型

下面通過一個例子初試一下路由事件:

<Window x:Class="WpfApplication8.wnd831"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="200" Width="200">
    <Grid x:Name="_gridRoot" Background="Lime">
        <Grid x:Name="_gridA" Background="Blue" Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Canvas x:Name="_canvasLeft" Background="Red" Margin="10" Grid.Column="0">
                <Button x:Name="_buttonLeft" Content="Left" Margin="10" Width="45" Height="110"/>
            </Canvas>
            <Canvas x:Name="_canvasRight" Background="LightSlateGray" Margin="10" Grid.Column="1">
                <Button x:Name="_buttonRight" Content="Right" Margin="10" Width="45" Height="110"/>
            </Canvas>
        </Grid>
    </Grid>
</Window>

    我們點選按鈕時,無論是_buttonLeft還是_buttonRight單擊都能顯示按鈕的名稱。兩個按鈕到頂部的window有唯一條路,左邊的按鈕對應的路:_buttonLeft->_canvasLeft->_gridA->_GridRoot->_Window,右邊按鈕對應的路:_buttonRight->_canvasRight->_gridA->_GridRoot->_Window。如果GridRoot訂閱兩個處理器,那麼處理器應該是相同的。後臺程式碼為:
    /// <summary>
    /// MainWindow.xaml 的互動邏輯
    /// </summary>
    public partial class wnd831 : Window
    {
        public wnd831()
        {
            InitializeComponent();

            // 為指定的路由事件新增路由事件處理程式
            _gridRoot.AddHandler(Button.ClickEvent, new RoutedEventHandler(ButtonClicked));
        }

        public void ButtonClicked(object sender, RoutedEventArgs e)
        {
            MessageBox.Show((e.OriginalSource as FrameworkElement).Name);
        }
    }

    

     下面先解釋一下路由事件是怎麼沿著可視樹來傳播的,當Button被點選,Button就開始傳送訊息了,可視樹上的元素如果訂閱了Button的點選事件,那麼才會根據訊息來作出相應的反應,如果沒有訂閱的話,就無視它發出的訊息,當然我們還可以控制它的訊息的傳播方式,是從樹根到樹葉傳播,還是樹葉向樹根傳播以及是直接到達目的傳播,不僅如此,還能控制訊息傳到某個元素時,停止傳播。具體的會在後面記錄到。

    其次是this._GridRoot.AddHandler(Button.ClickEvent,new RoutedEventHandler(this.ButtonClicked));訂閱事件時,第一個引數是路由事件型別,在這裡用的是Button的ClickEvent,就像依賴屬性一樣,類名加上依賴屬性,這裡是類名加上路由事件。另外一個是e.OriginalSourcee.Source的區別,由於訊息每傳一站,都要把訊息交到下一個控制元件(此控制元件成為了訊息的傳送地點),e.Source為邏輯樹上的源頭,要想獲取原始發訊息的控制元件(可視樹的源頭)要用e.OriginalSource。

1.4 自定義路由事件

建立自定義路由事件大體分為3個步驟:
宣告並註冊路由事件。
為路由事件新增CLR事件包裝。
建立可以激發路由事件的方法。

宣告路由事件引數:

    /// Event Handler
    delegate void ReportTimeRouteEventHandler(object sender, ReportTimeRoutedEventArgs e);

    /// <summary>
    /// 包含時間的路由事件引數
    /// </summary>
    public class ReportTimeRoutedEventArgs : RoutedEventArgs
    {
        public ReportTimeRoutedEventArgs(RoutedEvent routedEvent, object source)
            : base(routedEvent, source){}

        public DateTime ClickTime { get; set; }
    }

建立路由事件:
    /// <summary>
    /// 繼承Button
    /// </summary>
    public class TimeButton : Button
    {
        /// <summary>
        /// 宣告和註冊路由事件
        /// </summary>
        public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent(
            "ReportTime",
            RoutingStrategy.Bubble,
            typeof(ReportTimeRouteEventHandler)/*typeof(EventHandler<ReportTimeRoutedEventArgs>)*/,
            typeof(TimeButton));

        /// <summary>
        /// CLR事件包裝器
        /// </summary>
        public event RoutedEventHandler ReportTime
        {
            add { this.AddHandler(ReportTimeEvent, value); }
            remove { this.RemoveHandler(ReportTimeEvent, value); }
        }

        /// <summary>
        /// 激發路由事件,借用Click事件激發
        /// </summary>
        protected override void OnClick()
        {
            base.OnClick();

            ReportTimeRoutedEventArgs args = new ReportTimeRoutedEventArgs(ReportTimeEvent, this);
            args.ClickTime = DateTime.Now;
            this.RaiseEvent(args);
        }
    }

註冊路由事件時注意:

第一個引數是一個String型別,被稱為路由事件的名稱,按微軟的建議,這個字串應該與RountEvent變數的字首和CLR事件包裝器名稱一致。本例中,路由事件的名稱是ReportTimeEvent,則此字串是ReportTime,CLR事件名亦為ReportTime。


第二個引數為路由事件的策略。WPF路由事件有三種路由策略:
Bubble,冒泡式:路由事件由事件激發者出發向它的上一層容器一層一層路由,直至最外層的容器(Windows或Page)。因為是由樹的底部想樹的頂部移動,而且從事件激發元素到UI樹的樹根只有確定的一條路徑,所以這種策略被形象的命名為“冒泡式”。
Tunnel,隧道式:事件的路由剛好和冒泡式相反,是由樹的樹根向事件激發者移動,這就想當於在樹根和目標控制元件之間挖了一條隧道,事件只能沿著隧道移動,所以稱為“隧道式”。
Direct,直達式:模仿CLR直接事件,直接將事件訊息送達事件處理器。

第三個引數用於指定事件處理器的型別。事件處理器的返回值型別和引數列表必須與此引數指定的委託保持一致,不然會導致在編譯的時候報異常。


第四個引數用於指定路由事件的宿主(擁有者)是哪個型別。

XAML:讓控制元件都監聽型別為ReportTime的路由事件:

<Window x:Class="WpfApplication8.wnd832"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication8"
        Title="wnd832" Height="300" Width="300">
    <Grid x:Name="grid_1"  local:TimeButton.ReportTime="ReportTimeHandle">
        <Grid x:Name="grid_2" local:TimeButton.ReportTime="ReportTimeHandle">
            <Grid x:Name="grid_3" local:TimeButton.ReportTime="ReportTimeHandle">
                <StackPanel x:Name="dock1" local:TimeButton.ReportTime="ReportTimeHandle">
                    <ListBox x:Name="listBox1" local:TimeButton.ReportTime="ReportTimeHandle"/>
                    <local:TimeButton x:Name="btn1" Content="報時" Width="80" Height="80" local:TimeButton.ReportTime="ReportTimeHandle"/>
                </StackPanel>
            </Grid>
        </Grid>
    </Grid>
</Window>
事件處理器:
public void ReportTimeHandle(object sender, ReportTimeRoutedEventArgs e)
{
    FrameworkElement ele = sender as FrameworkElement;
    string content = string.Format("{0}到達{1}", e.ClickTime.ToLongTimeString(), ele.Name);
    listBox1.Items.Add(content);

    //當事件傳遞到grid_2就停止了
    if (ele == grid_2)
    {
        e.Handled = true; 
    }
}



參考《深入淺出WPF》

相關推薦

WPF Event事件

1.1 邏輯樹與可視樹     如果把一片樹葉放在顯微鏡下觀察,你會發現這片葉子也像一棵樹----有自己的基部並向上生長出多級分叉。在WPF的Logic Tree上,扮演葉子的一般都是控制元件。如果

WPF 路由事件 Event Routing

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

python-Event事件線程同步和互斥

sse logs pan else 控制 事件 utf-8 event Coding 1 #!/usr/bin/python 2 #coding=utf-8 3 #用於線程間通信,通過事件標識控制 4 import threading 5 from time

跨平臺的EVENT事件 windows linux(轉)

-1 hand lock break type nal lin pthread.h blog #ifndef _HIK_EVENT_H_ #define _HIK_EVENT_H_ #ifdef _MSC_VER #include <Window

Event 事件 - 基礎

lis 事件綁定 class tro 觸發 rip javascrip style 事件類型 事件驅動三要素 事件源:即觸發事件的元素 事件:被JavaScript檢測到的行為。例如:    鼠標點擊    鍵盤按鍵    選輸入框 事件處理函數:事件發生時要進行的操作,又

第53天:鼠標事件event事件對象

鼠標右鍵 pin mov offsetx 參數 cursor wid 坐標 logs -->鼠標事件-->event事件對象-->默認事件-->鍵盤事件(keyCode)-->拖拽效果 一、鼠標事件 onclick --------------

javascript event事件兼容性處理

cli clientx click script document java 事件 () 坐標 ie 6-8支持event事件,ff瀏覽器不支持 獲取鼠標點擊位置的坐標 document.onclick = function(){ alert(event.cl

event事件的preventDefault()

spa table 事件 fault 默認 code blog ble prevent 1.preventDefault()取消事件的默認動作 2.有時候一個頁面有table選項卡,點擊一個選項卡切換內容,這時候切換沒問題,但是點擊本身的時候地址錯誤,這時候就要調用prev

spring boot: 一般註入說明(五) @Component, application event事件為Bean與Bean之間通信提供了支持

listen source 監聽 監聽器 nbsp lis 所有 fin 配置 spring的事件,為Bean與Bean之間通信提供了支持,當一個Bean處理完成之後,希望另一個Bean知道後做相應的事情,這時我們就讓另外一個Bean監聽當前Bean所發送的事件。 spri

event——事件對象詳解

bubuko 方法 相對 需要 family 分享 null chrome 它的 PS:轉自https://www.cnblogs.com/songyaqi/p/5204143.html 1. 事件對象 Event 對象代表事件的狀態,比如事件在其中發生的元素、鍵盤按鍵的狀

python全棧開發基礎【第二十五篇】死鎖,遞歸鎖,信號量,Event事件,線程Queue

random 問題 定時器 初始 .get rand true () 進入 一、死鎖現象與遞歸鎖 進程也是有死鎖的 所謂死鎖: 是指兩個或兩個以上的進程或線程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用, 它們都將無法推進下去。此時稱系統處於死鎖狀態或系

WPF自學入門(三)WPF路由事件之內置路由事件

順序 初學者 rgs 理念 技術 設計 行處理 再處理 之前 有沒有想過在.NET中已經有了事件機制,為什麽在WPF中不直接使用.NET事件要加入路由事件來取代事件呢?最直觀的原因就是典型的WPF應用程序使用很多元素關聯和組合起來,是否還記得在WPF自學入門(

並發編程 - 線程 - 1.互斥鎖/2.GIL解釋器鎖/3.死鎖與遞歸鎖/4.信號量/5.Event事件/6.定時器

級別 src 總結 alex post strip CQ bsp 回收機制 1.互斥鎖: 原理:將並行變成串行 精髓:局部串行,只針對共享數據修改 保護不同的數據就應該用不用的鎖 1 from threading import Thread

Event事件

w3c標準 code pagex 提示 兼容 event對象 sem itl con Event對象(事件源對象)代表事件的狀態   比如事件在其中發生的元素、鍵盤按鍵的狀態、鼠標的位置、鼠標按鈕的狀態。 btn.onmousedown = function(eve

並發編程---死鎖||遞歸鎖---信號量---Event事件---定時器

遞歸 spa sleep 事件 lang tin lap 計數器 name 死鎖 互斥鎖:Lock(),互斥鎖只能acquire一次 遞歸鎖: RLock(),可以連續acquire多次,每acquire一次計數器+1,只有計數為0時,才能被搶到acquire # 死

saltstack之salt event事件用法

OS CP values exists medium highlight -a character jid   event是一個本地的ZeroMQ PUB Interface,event是一個開放的系統,用於發送信息通知salt或其他的操作系統。每個event都有一個標簽。

java 前端--event 事件

span elb log rop foo 不讓 pub urn nodename <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8">

發布+訂閱 event事件

cal cti != 訂閱 for function 重復 als turn function on(ele,type,f) {//type 對應報社的某個頻道 //若是JS原生事件,我們需要改變綁定方式 if(/^(my)/.test(type)){

Python入門學習-DAY36-GIL全局解釋器鎖、死鎖現象與遞歸鎖、信號量、Event事件、線程queue

可重入 def 代碼 threading 結果 運算 分析 rand pen 一、GIL全局解釋器鎖 1. 什麽是GIL全局解釋器鎖 GIL本質就是一把互斥鎖,相當於執行權限 在Cpython解釋器下,如果想實現並行可以開啟多個進程 2. 為何要有GIL 我們首先要知道,一

GIL全局解釋器鎖、死鎖遞歸鎖、信號量、Event事件、線程Queue

main 圖片 加鎖 2.0 最大 sin 解決 mutex 帶來 GIL全局解釋器鎖   GIL本質就是一把互斥鎖,和所有互斥鎖本質一樣,都是把並發運行變成串行,以此來控制同一時間內共享數據只能被一個任務修改,進而保證數據安全   保護不同的數據的安全,就應該加不同的鎖。