WPF 學習筆記 路由事件


1. 可傳遞的消息: WPF的UI是由布局組建和控件構成的樹形結構,當這棵樹上的某個節點激發出某個事件時,程序員可以選擇以傳統的直接事件模式讓響應者來響應之,也可以讓這個事件在UI組件樹沿着一定的方向傳遞且路過多個中轉結點,並讓這個路由過程被恰當的處理。

2,WPF有兩種樹,Logical Tree和Visual tree。 LogicTree上,充當葉子的一般都是控件,如果我們把WPF的控件也放在“放大鏡下觀察”,你會發現每個WPF空間本身也是一棵更細微級別的組件組成的樹。用來觀察WPF控件的放大鏡是我們提到的Blend。如果把Logical Tree衍生至Template組件級別,我們的到的就是Visual Tree。

3,路由事件是沿着Visual Tree傳遞的。

4.一個事件包括5方面: 

    1. 事件的擁有者
    2. 事件
    3. 事件的響應者
    4. 事件處理器
    5. 訂閱關系

5. 路由事件的原理: 舍棄直接事件響應者,讓不同的控件變成監聽者。 而事件擁有者,只負責出發事件。

6,使用WPF內置的路由事件:

Wpf系統的大多數事件都是可路由事件。我們以Button的Click事件來說明路由事件的使用。

1,如何添加監聽者:

    this.gridRoot.AddHandler(Button.ClickEvent, new RoutedEventHandler(DefineMethod));

上面這個AddHandler方法來自URElement類。 第一個參數是Button.ClickEvent, 這個叫做路由事件,這里也使用了類似依賴屬性包裝器的方式。

    這里要注意的是,路由事件方法中RoutedEventHandler.Source 是gridRoot. 如果想要得到Button的話,使用RoutedEventHandler.OrigenalSource.

   可以在Xaml中使用簡化的方法:  <Grid X:Name= “gridRoot”  BackGround=”Lime” Button.Click=”ButtonClicked”>

7,自定義路由事件

三個步驟:

  1. 聲明並注冊路由事件
  2. 為路由事件添加CLR事件包裝
  3. 創建可以激發路由事件的方法

看Code:

public abstract class ButtonBase: ContentControl, ICommandSource
{
    public static readonly RoutedEvent ClickEvent =
    /*注冊路由事件*/
   EventManager.RegisterRoutedEvent("Click",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase));
    
    public event RoutedEventHandler Click
    {
          add {this.AddHandler(ClickEvent,value)}
          Remove {this.RemoveHandler(ClickEvent, value)}
     }

    protected virtual void OnClick()
   {
       RoutedEventArgs newEvent = new RoutedEventArgs(ButtonBase.ClickEvent,this);
      this.RaiseEVent(newEvent);
   }
}

路由策略:

  1. Bubble  向上
  2. Tunnel 向下
  3. Direct  模仿CLR直接事件

Demo 2: 實現一個繼承自Button的TimeButton類,並添加路由事件

//創建一個RoutedEventArgs類:
   class ReportTimeEventArgs:RoutedEventArgs
   {
         Public ReportTimeEventArgs(RoutedEvent routedEvent, object source)
         :Base(routedEvent,source) {}

          Public DateTime ClickTime {get;set;}
    }
class TimeButton : Button
{
     //生命和注冊路由事件
      public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent
           ("ReportTime",RoutingStrategy.Bubble,typeof(EventHandler<ReportTimeEventArgs>),typeof(TimeButton));
       
      //CLR 事件包裝器
       public event RoutedEventhandler ReportTime
       {
           add {this.AddHandler(ReportTimeEvent , value) ;}
            remove {this.RemoveHandler(ReportTimeEvent,value); 
       }

//激發路由事件,借用Click事件的激發方法
       protected override void OnClick()
       {
         base.OnClick();
         ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent, this);
         args.ClickTime = DateTime.Now;
         this.RaiseEvent(args);
       }
 //看看怎么使用吧
       local:TimeButton.ReportTime = “ReportTimeHandler”;
           //ReportTimeEvent 路由事件處理器
           private void ReportTimeHandler(objecet sender, ReportTimeEventArgs e)
           {
                FrameworkElement element  = sender as FrameworkElement;
                string timeStr  = e.ClickTime.ToLongTimeString();
                string content = string.Format(“{0}  到達 {1}”,timeStr,element.Name);
                this.listBox.Items.Add(content);
           }
8, 如何讓路由事件停止傳播:
      使用e.handled 如果e.handled==true,則停止傳遞。
9. RoutedEventArgs的Source和OriginalSource
Source是logicTree的消息源頭,而OriginalSource是visualTree上的源頭。
如果一個Button是在UserControl中,那么Source應該是UserControl , OriginalSource 應該是 Button/
10. 事件也附加----深入淺出附加事件
那些類擁有附加事件
  • Binding類: SourceUpdated事件、 TargetUpdated事件
  • Mouse類: MouseEnter事件、MouseLeave事件、MouseDown事件、MouseUp事件等
  • Keyboard類: KeyDown事件、KeyUp事件
對比路由事件會發現: 附加事件不具備顯示在用戶界面上的能力。
看一個Demo:設計一個Student類,如果Student實例的Name屬性值發生了變化就激發一個路由事件,然后使用界面元素來捕捉這個事件。
public class Student
{
    public static readonly RoutedEvent NameChangedEvent = EventManager.ResisterRoutedEvent
      ( "NameChanged",routingStrategy.Bubble,typeof(RoutedEventHandler),typeof(Student));

     public int Id {get;set;}
     public string Name {get;set;}
}
然后我們設計一個button. 
<Button  x:Name = "button1" Content="OK"  Width="80" Height="80"   Click="Button_Click">
 看看后台代碼 
   //添加事件監聽器
  this.gridMain.AddHandler(Student.nameChangedEvent, new RoutedEventHandler(this.DtudentnameChangedHandler))
   //Click 事件處理器
   private void Button_Click(objcet sender, RoutedEventArgs e)
{
       Student stu = new Student() {Id=10,Name="Tim"};
        stu.Name="Tom";
        //准備事件消息並發送路由事件
        //附加事件宿主是沒有辦法發送路由事件的,要借助一個FrameworkElement來RaiseEvent(arg)
        //其中RoutedEventArgs 有兩個參數,一個附加事件,一個是實例。
       RoutedEventArgs arg = new RoutedEventArgs(Student.NameChangedEvent,stu);
       this.button1.RaiseEvent(arg);
}

//Grid 捕捉到nameChangedEvent后的處理器
private void StudentNameChangedHandler(object sender, RoutedEventArgs e)
{
      MessageBox.Show((e.OriginalSource as Student).Id.ToString());
}
11. 事件也附加2
其實上面那個例子已經是一個附加文件了,但是微軟的官方文檔約定要為這個附加事件添加一個CLR包裝以便XAML編輯器識別並進行只能提示。 但是,因為Student類不是UIElement的派生類,因為不具備Addhandler和 RemoveHandler這兩個方法,所以不能使用CLR屬性作為包裝器:
  • 為目標UI元素附加事件偵聽器的包裝器是一個名為Add*Handler的public static 方法,星號代表事件名稱
public class Student
{
     //聲明並定義路由事件
    public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRouredEvent
    ("NameChanged",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typdof(Student));

     //為界面元素添加路由事件偵聽
      public static void AddNameChangedHandler(DependencyObject d,RoutedEventHandler h)
      {
             UIElement e = d as UIElement;
             if(e!=null)
             {
                   e.AddHandler(Student.NameChangedEvent, h);  
              }          
       }
   
     public static voidRemoveNameChangedHandler(DependencyObject d,RoutedEventHandler h)
      {
             UIElement e = d as UIElement;
             if(e!=null)
             {
                   e.AddHandler(Student.NameChangedEvent, h);  
              }          
       }
  

     public int Id {get;set;}
     public string Name {get;set;}
}

public Window1()

{

     Student.AddNameChangedHandler(this.gridMain, new RoutedEventHandler(this.StudnetnameChagnedHandler));

}

再次理解一下附加事件:

UIElement類是路由事件宿主與附加事件宿主的分水嶺,不但是因為從UIElemtn類開始才具備了界面上顯示的能力,還因為RaiseEvent、AddHandler和RemoveHandler 這些方法也定義在UIElement類中。 如果在一個非UIElement派生類中注冊了路由事件,則這個類的實例既不能自己激發,也無法自己偵聽此路由事件。

image


免責聲明!

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



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