最近因為工作需要學習WPF方面的知識,因為以前只關注的是B/S架構的東西,可是沒想到參加工作的第一個項目竟然是C/S架構的WPF方面的開發,因為Web方面主要是請求響應模型,沒有事件這個東西,在學習webform時雖然是基於事件模型的也有沒有認真的研究事件,因為它已經逐漸被mvc或者其他方式(比如ashx和jquery easyui等類似的)替代,現在是CS架構了,需要把這塊知識補上。
1、簡單的事件模型
事件的前身是消息,消息的本質就是一組數據記錄這要執行的操作,然后消息處理函數根據消息的數據執行相應的操作,那么在消息處理函數中就充斥這大量的判斷或者switch,這樣對於大型應用程序的開發帶來了不少麻煩。為了簡單的開發微軟封裝了一套簡單的事件模型。以前了解過window form的應該都知道,當托一個按鈕到窗體后然后雙擊按鈕就可以在.cs代碼自動生成有關事件的代碼,這就是一個簡單的事件模型
事件模型包括一下幾個部分,我們對應到一個winform程序的Demo中如下:
事件的擁有者:就是按鈕:button1
事件:就是button1.Click,在Form1.cs中
事件的處理器就是這個方法button1_Click
訂閱關系:也就是說事件和事件處理器如何建立聯系的呢:
打開Form1.Designer.cs找答案
this.button1.Click += new System.EventHandler(this.button1_Click);
這里就建立了事件和事件處理器的聯系,當然一個事件我們也可以定義多個處理器相應。
最后一個響應者:就是窗體本身
2、路由事件模型
傳統的簡單事件模型中,在消息激發是將消息通過事件訂閱的然后交給事件的相應者,事件的相應者使用事件的處理器來做出相應,這樣就存在一個問題,用戶控件內部的事件就不能被外界訂閱,因為事件的宿主必須能夠直接訪問到事件的響應者。
路由事件的事件擁有者和事件的相應者之間則沒有直接的顯式訂閱關系,事件的擁有者則只負責激發事件,事件將有誰相應它並不知道,事件的響應者則有事件的監聽器,針對事件進行監聽,當有此類事件傳遞至此事件響應者就使用事件處理器來相應事件並決定此事件是否繼續傳遞。比如像上一個程序中的,點擊“點我”以后事件就開始激發了,然后事件就會在控件樹上進行傳遞,事件的響應者安裝了監聽器,當監聽到這個事件進行響應,並決定這個事件是否繼續傳遞。
如果當事件在某個節點處理以后,不想讓它繼續傳遞,可以把它標記為“已處理”,就會停止路由,所有的路由事件都共享一個公共的事件數據基類 RoutedEventArgs。RoutedEventArgs 定義了一個采用布爾值的 Handled 屬性。把事件設為已處理只要把Handled屬性設為true即可
Xaml代碼:
<Window x:Class="RouteEventWpfDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid x:Name="GridA"> <Grid x:Name="GridB"> <Grid x:Name="GridC"> <Button Canvas.Left="101" Canvas.Top="68" Content="Button" Height="23" Name="ButtonA" Width="75" /> </Grid> </Grid> </Grid> </Window>
xaml的交互邏輯代碼
public partial class MainWindow : Window {/// <summary> /// 主窗口構造器 /// </summary> public MainWindow() { InitializeComponent(); //為GridA添加Button.ClickEvent監聽 this.GridA.AddHandler(Button.ClickEvent,new RoutedEventHandler(this.ButtonA_Click)); //為GridB添加Button.ClickEvent監聽 this.GridB.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.ButtonA_Click)); //為GridC添加Button.ClickEvent監聽 this.GridC.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.ButtonA_Click)); } private void ButtonA_Click(object sender, RoutedEventArgs e) { MessageBox.Show(((Grid)sender).Name); }
3、自定義路由事件
自定義路由事件大體需要三個步驟:
1、聲明並注冊路由事件
2、為路由事件添加CLR事件包裝
3、創建可以激發路由事件的方法
Xaml代碼
<Window x:Class="MyRouteEventWpfDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MyRouteEventWpfDemo" local:TimeButton.ReportTime="ReportTimeHandler" Title="MainWindow" Height="350" Width="525"> <Grid x:Name="GridA" local:TimeButton.ReportTime="ReportTimeHandler"> <Grid x:Name="GridB" local:TimeButton.ReportTime="ReportTimeHandler"> <Grid x:Name="GridC" local:TimeButton.ReportTime="ReportTimeHandler"> <StackPanel x:Name="StackPanelA" local:TimeButton.ReportTime="ReportTimeHandler"> <ListBox x:Name="listBox"/> <local:TimeButton x:Name="btnTime" Content="時間" local:TimeButton.ReportTime="ReportTimeHandler" /> </StackPanel> </Grid> </Grid> </Grid> </Window>
自定義的ReportTimeEventArgs
/// <summary> /// 可以記錄時間的RoutedEventArgs /// </summary> public class ReportTimeEventArgs : RoutedEventArgs { public ReportTimeEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { } /// <summary> /// 記錄點擊時間 /// </summary> public DateTime ClickTime { get; set; } }
定義一個時間Button
/// <summary> /// 自定義個一個時間控件 /// </summary> public class TimeButton : Button { //聲明並注冊路由事件 /* * 1、第一個參數ReportTime 為路由事件的名稱 * 2、第二個參數是路由事件的策略,包括Bubble冒泡式,Tunnel隧道式,Direct直達式(和直接事件類似) * 3、第三個參數用於指定事件處理器的類型 * 4、第四個參數用於指定事件的宿主是哪一種類型 */ 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); } //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); } } }
xaml的交互邏輯代碼
/// <summary> /// MainWindow.xaml 的交互邏輯 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void ReportTimeHandler(object sender, ReportTimeEventArgs e) { FrameworkElement element = (FrameworkElement)sender; string timeStr = e.ClickTime.ToString("HH:mm:ss"); string content = string.Format("{0}==>到達{1}", timeStr, element.Name); this.listBox.Items.Add(content); } }
4、附加事件
在wpf中還有一種事件附加事件,像前面說的路由事件它的事件宿主都是我們可以看到的界面元素,但是附加事件不具備顯式在用戶界面上的能力,比如一個文本框的改變,鼠標的按下,鍵盤的按下這些事件都是附加事件的例子
還是上面的例子我們給他加上顏色改變事件
在TimeButton類中添加如下代碼
/// <summary> /// 聲明顏色屬性 /// </summary> public string Color { get; set; } /// <summary> /// 聲明並注冊顏色改變路由事件 /// </summary> public static readonly RoutedEvent ColorChangedEvent = EventManager.RegisterRoutedEvent ("ColorChanged", RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton)); /// <summary> /// 添加顏色改變事件 /// </summary> /// <param name="d"></param> /// <param name="e"></param> public static void AddColorChangedHandler(DependencyObject d, RoutedEventHandler e) { UIElement ui = (UIElement)d; if (ui != null) { ui.AddHandler(TimeButton.ColorChangedEvent, e) ; } } /// <summary> /// 刪除顏色改變事件 /// </summary> /// <param name="d"></param> /// <param name="e"></param> public static void RemoveColorChangedHandler(DependencyObject d, RoutedEventHandler e) { UIElement ui = (UIElement)d; if (ui != null) { ui.RemoveHandler(TimeButton.ColorChangedEvent, e); } }
MainWindow.cs改為
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); //btnTime添加路由事件監聽 TimeButton.AddColorChangedHandler(this.btnTime, new RoutedEventHandler(this.ColorChangedHandler)); } private void ReportTimeHandler(object sender, ReportTimeEventArgs e) { FrameworkElement element = (FrameworkElement)sender; string timeStr = e.ClickTime.ToString("HH:mm:ss"); string content = string.Format("{0}==>到達{1}", timeStr, element.Name); this.listBox.Items.Add(content); this.btnTime.Color = "123"; //准備消息傳給路由事件 RoutedEventArgs args = new RoutedEventArgs(TimeButton.ColorChangedEvent, this.btnTime); //引發事件 this.btnTime.RaiseEvent(args); } private void ColorChangedHandler(object sender, RoutedEventArgs e) { MessageBox.Show((e.OriginalSource as TimeButton).Color); } }
參考:http://msdn.microsoft.com/zh-cn/library/ms742806.aspx
http://msdn.microsoft.com/zh-cn/library/bb613550.aspx