ASP.NET2.0服務器控件之復合控件事件
在上面一篇文章中,我們討論了有關創建復合控件的基本理論,并且通過一個典型應用掌握了復合控件的呈現方法。本文將繼續講解有關創建復合控件的內容,重點是為復合控件實現事件的具體方法。
復合控件的事件處理簡介
談到自定義控件的事件處理問題,這在前面的系列文章中已經進行講解。由前文可知,實現控件事件的核心主要是定義事件屬性結構和事件處理程序等。然而,這些內容是構建所有自定義服務器控件的基礎。僅僅依靠這些方法是無法實現復合控件的事件的。因為,復合控件中包含子控件,這就使得復合控件的事件處理變得復雜起來。顯而易見,在復合控件的事件實現過程中,需要面臨的最大問題是:由于不允許開發人員直接訪問子控件(雖然通過Controls集合訪問的方法可以實現,但是破壞了程序的封裝性,因此是不被允許的),如果子控件的事件不能作為頂級事件引發,那么將無法實現子控件的事件處理。簡單的說,即如何實現子控件的事件上傳。所謂事件上傳是指把子控件的事件暴露為頂級事件,這樣父控件可以檢查到事件,并按照定義來執行相關事件處理程序。
由以上內容可知,復合控件的事件處理,主要是實現子控件事件上傳的過程。下面將介紹兩種常用的事件上傳實現方法:包含法和冒泡法。這兩種方法實現機理不同,然而,完成了同樣的功能。在下文中,我們將通過理論結合示例的方法展開講解。
包含法
包含法的核心是,通過在子控件的事件處理程序中調用復合控件的頂層事件處理程序,以完成子控件的事件上傳。在執行過程中,當引發子控件事件后,子控件的事件處理程序將自動調用相關頂層事件處理程序。
包含法的關鍵步驟如下:
· 在CreateChildControls方法中,為子控件添加事件處理程序。
· 定義頂層事件及其事件處理程序OnEventName。
· 在子控件的事件處理程序中調用OnEventName。
· 定義事件屬性結構。
由以上內容可知,包含法的步驟與前面文章中介紹的實現控件的方法基本類似。關鍵是多出了一個在CreateChildControls方法中,為子控件添加事件處理程序的步驟。為了讀者能夠更加清晰的理解包含法,下文列舉了一個利用包含法為復合控件實現事件的示例。
首先,利用上一篇文章中介紹的復合控件呈現方法,創建一個由文本框和按鈕組成的復合控件,然后,使用上文所述的包含法,將按鈕的Click事件上傳為頂層事件Submit。下面列舉了該控件的源代碼。
如上代碼所示,復合控件CompositeEvent中包含兩個屬性:Text和ButtonText。前者用于獲取或者設置文本框中的文本內容,后者用于獲取或者設置按鈕的顯示文本。另外,復合控件類中還實現了一個Submit事件。相關重要邏輯包括:
第一、在重寫CreateChildControls方法中,為子控件Button添加事件處理程序_button_Click。
第二、和普通的自定義事件一樣,為復合控件定義一個頂層事件Submit。這其中包括定義事件屬性結構Submit,定義事件處理程序OnSubmit。
第三、實現_button_Click事件處理程序,調用頂層事件Submit的事件處理程序OnSubmit。
下面是為測試復合控件CompositeEvent而創建的Default.aspx文件代碼。
示例效果如圖1所示。
在以上應用中,當用戶單擊"提交"按鈕之后,將引發demo1_Submit處理程序的執行,由此顯示文本框輸入內容。
需要讀者注意的是其內部執行過程。控件定義的頂層事件是Submit,其對應事件處理程序是OnSubmit,而不是_button_Click。_button_Click是復合控件的子控件的Click事件處理程序。由于在控件實現的代碼中定義了子控件的事件處理程序_button_Click,所以,當用戶單擊按鈕后,將首先執行_button_Click,該方法要求調用頂層事件Submit的事件處理程序OnSubmit。從外部來看,子控件的事件即暴露為頂層事件。
從以上實現過程來看,包含法使用的是程序代碼上的小技巧來實現事件上傳功能。下面介紹的冒泡法則與此不同,它使用.NET框架提供的事件上傳機制來完成子控件的事件上傳。
冒泡法
冒泡法也稱"事件冒泡",其核心是使用ASP.NET 2.0框架提供的事件上傳機制。這種機制允許子控件將事件沿其包容層次結構向上傳播到合適的位置引發,并且允許將事件處理程序附加到原始控件以及公開冒泡的事件的控件上。
冒泡法的實現,使用Control基類中專門用于事件上傳的兩個方法:OnBubbleEvent和RaiseBubbleEvent。它們的聲明如下所示。
OnBubbleEvent方法用于確定子控件的事件是否沿復合控件層次結構向上傳遞。在該方法中,參數source表示事件源,參數args表示包含事件數據的EventArgs對象。如果子控件的事件向上傳遞,則為true;否則為false。默認值為false。RaiseBubbleEvent方法用于將所有事件源及其信息分配給控件的父級,并且不能被重寫。盡管無法重寫此方法,但創作的控件可以通過重寫 OnBubbleEvent 方法處理或引發冒泡事件。
復合控件的事件冒泡主要存在以下兩種情況:
情況一:控件停止事件冒泡并引發和/或處理該事件。引發事件需要調用將事件調度給偵聽器的方法。若要引發冒泡的事件,控件必須重寫OnBubbleEvent以調用引發此冒泡的事件的OnEventName方法。引發冒泡的事件的控件通常將冒泡的事件公開為頂級事件。以下代碼引發一個冒泡的事件。
情況二:控件進行一些處理并繼續使事件冒泡。若要實現這一點,控件必須重寫OnBubbleEvent,并從OnBubbleEvent調用RaiseBubbleEvent。以下代碼在檢查事件參數的類型后使事件冒泡。
為了使讀者能夠更好的理解冒泡法,下面利用冒泡法對上一小節示例進行了重新實現。控件類的源代碼如下所示,其中沒有改變的部分使用省略號表示。
本例的CompositeEvent類與上小節中的CompositeEvent類實現了同一功能。就控件呈現方面,兩個類沒有任何差別,差別主要表現在對于復合控件的事件實現方面。差別一:在本例的CreateChildControl方法中,為子控件_button設置了CommandName屬性,其屬性值為Submit。差別二:刪除了_button_Click事件處理程序。差別三:重寫了Control基類的OnBubbleEvent方法,檢查事件參數是否是CommandEventArgs類的實例。如果是,使用事件參數的CommandName成員確定是否需要引發事件處理程序OnSubmit,并返回true。
小結
本文重點介紹了復合控件的事件實現方法,并通過典型示例說明了這些實現方法的具體應用。總體而言,為復合控件實現事件并不是特別困難的事情。關鍵是開發人員必須在領會為普通控件實現事件的基礎之上,掌握包含法和冒泡法的實現要點。
復合控件的事件處理簡介
談到自定義控件的事件處理問題,這在前面的系列文章中已經進行講解。由前文可知,實現控件事件的核心主要是定義事件屬性結構和事件處理程序等。然而,這些內容是構建所有自定義服務器控件的基礎。僅僅依靠這些方法是無法實現復合控件的事件的。因為,復合控件中包含子控件,這就使得復合控件的事件處理變得復雜起來。顯而易見,在復合控件的事件實現過程中,需要面臨的最大問題是:由于不允許開發人員直接訪問子控件(雖然通過Controls集合訪問的方法可以實現,但是破壞了程序的封裝性,因此是不被允許的),如果子控件的事件不能作為頂級事件引發,那么將無法實現子控件的事件處理。簡單的說,即如何實現子控件的事件上傳。所謂事件上傳是指把子控件的事件暴露為頂級事件,這樣父控件可以檢查到事件,并按照定義來執行相關事件處理程序。
由以上內容可知,復合控件的事件處理,主要是實現子控件事件上傳的過程。下面將介紹兩種常用的事件上傳實現方法:包含法和冒泡法。這兩種方法實現機理不同,然而,完成了同樣的功能。在下文中,我們將通過理論結合示例的方法展開講解。
包含法
包含法的核心是,通過在子控件的事件處理程序中調用復合控件的頂層事件處理程序,以完成子控件的事件上傳。在執行過程中,當引發子控件事件后,子控件的事件處理程序將自動調用相關頂層事件處理程序。
包含法的關鍵步驟如下:
· 在CreateChildControls方法中,為子控件添加事件處理程序。
· 定義頂層事件及其事件處理程序OnEventName。
· 在子控件的事件處理程序中調用OnEventName。
· 定義事件屬性結構。
由以上內容可知,包含法的步驟與前面文章中介紹的實現控件的方法基本類似。關鍵是多出了一個在CreateChildControls方法中,為子控件添加事件處理程序的步驟。為了讀者能夠更加清晰的理解包含法,下文列舉了一個利用包含法為復合控件實現事件的示例。
首先,利用上一篇文章中介紹的復合控件呈現方法,創建一個由文本框和按鈕組成的復合控件,然后,使用上文所述的包含法,將按鈕的Click事件上傳為頂層事件Submit。下面列舉了該控件的源代碼。
| using System; using System.Web.UI; using System.Web.UI.WebControls; using System.ComponentModel; using System.ComponentModel.Design; namespace WebControlLibrary{ public class CompositeEvent : CompositeControl { //聲明變量 private Button _button; private TextBox _textBox; private static readonly object EventSubmitKey = new object(); //定義屬性ButtonText,用于指定按鈕上的文字 [ Bindable(true), Category("Appearance"), DefaultValue(""), Description("獲取或設置顯示顯示在按鈕上的文字") ] public string ButtonText { get { EnsureChildControls(); return _button.Text; } set { EnsureChildControls(); _button.Text = value; } } //定義屬性Text,表示文本框的輸入 [ Bindable(true), Category("Appearance"), DefaultValue(""), Description("獲取或設置文本框輸入文本") ] public string Text { get { EnsureChildControls(); return _textBox.Text; } set { EnsureChildControls(); _textBox.Text = value; } } // 實現事件屬性結構 public event EventHandler Submit { add { Events.AddHandler(EventSubmitKey, value); } remove { Events.RemoveHandler(EventSubmitKey, value); } } // 實現OnSubmit protected virtual void OnSubmit(EventArgs e) { EventHandler SubmitHandler = (EventHandler)Events[EventSubmitKey]; if (SubmitHandler != null) { SubmitHandler(this, e); } } // 實現Submit事件引發的事件處理程序 private void _button_Click(Object source, EventArgs e) { OnSubmit(EventArgs.Empty); } // 重寫ICompositeControlDesignerAccessor接口的RecreateChildContrls方法 protected override void RecreateChildControls() { EnsureChildControls(); } //重寫CreateChildControls方法,將子控件添加到復合控件中 protected override void CreateChildControls() { Controls.Clear(); _button = new Button(); _textBox = new TextBox(); _button.ID = "btn"; _button.Click += new EventHandler(_button_Click); this.Controls.Add(_button); this.Controls.Add(_textBox); } //重寫Render方法,呈現控件中其他的HTML代碼 protected override void Render(HtmlTextWriter output) { output.AddAttribute(HtmlTextWriterAttribute.Border, "0px"); output.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "5px"); output.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0px"); output.RenderBeginTag(HtmlTextWriterTag.Table); output.RenderBeginTag(HtmlTextWriterTag.Tr); output.RenderBeginTag(HtmlTextWriterTag.Td); _textBox.RenderControl(output); output.RenderEndTag(); output.RenderBeginTag(HtmlTextWriterTag.Td); _button.RenderControl(output); output.RenderEndTag(); output.RenderEndTag(); output.RenderEndTag(); } } } |
如上代碼所示,復合控件CompositeEvent中包含兩個屬性:Text和ButtonText。前者用于獲取或者設置文本框中的文本內容,后者用于獲取或者設置按鈕的顯示文本。另外,復合控件類中還實現了一個Submit事件。相關重要邏輯包括:
第一、在重寫CreateChildControls方法中,為子控件Button添加事件處理程序_button_Click。
第二、和普通的自定義事件一樣,為復合控件定義一個頂層事件Submit。這其中包括定義事件屬性結構Submit,定義事件處理程序OnSubmit。
第三、實現_button_Click事件處理程序,調用頂層事件Submit的事件處理程序OnSubmit。
下面是為測試復合控件CompositeEvent而創建的Default.aspx文件代碼。
| <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <%@ Register TagPrefix="Sample" Assembly="WebControlLibrary" Namespace="WebControlLibrary" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server"> void demo1_Submit(object sender, EventArgs e) { lbMessage.Text = "您剛才輸入的是:" + demo1.Text; } </script> <html xmlns="http://www.w3.org/1999/xhtml"> <head id="Head1" runat="server"> <title>為復合控件實現事件-包含法</title> </head> <body> <form id="form1" runat="server"> <div> <Sample:CompositeEvent ID="demo1" runat="server" ButtonText="提交" OnSubmit="demo1_Submit" /> <br /> <asp:Label ID="lbMessage" runat="server"></asp:Label> </div> </form> </body> </html> |
示例效果如圖1所示。
![]() 圖1 效果圖 |
在以上應用中,當用戶單擊"提交"按鈕之后,將引發demo1_Submit處理程序的執行,由此顯示文本框輸入內容。
需要讀者注意的是其內部執行過程。控件定義的頂層事件是Submit,其對應事件處理程序是OnSubmit,而不是_button_Click。_button_Click是復合控件的子控件的Click事件處理程序。由于在控件實現的代碼中定義了子控件的事件處理程序_button_Click,所以,當用戶單擊按鈕后,將首先執行_button_Click,該方法要求調用頂層事件Submit的事件處理程序OnSubmit。從外部來看,子控件的事件即暴露為頂層事件。
從以上實現過程來看,包含法使用的是程序代碼上的小技巧來實現事件上傳功能。下面介紹的冒泡法則與此不同,它使用.NET框架提供的事件上傳機制來完成子控件的事件上傳。
冒泡法
冒泡法也稱"事件冒泡",其核心是使用ASP.NET 2.0框架提供的事件上傳機制。這種機制允許子控件將事件沿其包容層次結構向上傳播到合適的位置引發,并且允許將事件處理程序附加到原始控件以及公開冒泡的事件的控件上。
冒泡法的實現,使用Control基類中專門用于事件上傳的兩個方法:OnBubbleEvent和RaiseBubbleEvent。它們的聲明如下所示。
| // OnBubbleEvent方法定義 protected virtual bool OnBubbleEvent(object source,EventArgs args){ return false;} // RaiseBubbleEvent方法定義 protected void RaiseBubbleEvent(object source,EventArgs args){ Control currentTarget = _parent; while(currentTarget != null) { if(currentTarget.OnBubbleEvent(source,args) { return; } currentTarget = currentTarget.Parent; } } |
OnBubbleEvent方法用于確定子控件的事件是否沿復合控件層次結構向上傳遞。在該方法中,參數source表示事件源,參數args表示包含事件數據的EventArgs對象。如果子控件的事件向上傳遞,則為true;否則為false。默認值為false。RaiseBubbleEvent方法用于將所有事件源及其信息分配給控件的父級,并且不能被重寫。盡管無法重寫此方法,但創作的控件可以通過重寫 OnBubbleEvent 方法處理或引發冒泡事件。
復合控件的事件冒泡主要存在以下兩種情況:
情況一:控件停止事件冒泡并引發和/或處理該事件。引發事件需要調用將事件調度給偵聽器的方法。若要引發冒泡的事件,控件必須重寫OnBubbleEvent以調用引發此冒泡的事件的OnEventName方法。引發冒泡的事件的控件通常將冒泡的事件公開為頂級事件。以下代碼引發一個冒泡的事件。
| protected override bool OnBubbleEvent(object sender,EventArgs e){ bool handled = false; if(e is CommandEventArgs) { CommandEventArgs ce = (CommandEventArgs)e; if(ce.CommandName == "ButtonClick") { OnButtonClick(EventArgs.Empty); handled =true; } } return handled; } |
情況二:控件進行一些處理并繼續使事件冒泡。若要實現這一點,控件必須重寫OnBubbleEvent,并從OnBubbleEvent調用RaiseBubbleEvent。以下代碼在檢查事件參數的類型后使事件冒泡。
| protected override bool OnBubbleEvent(object sender,EventArgs e){ if(e is CommandEventArgs) { CommandEventArgs ce = (CommandEventArgs)e; RaiseBubbleEvent(this,ce); return true; } return false; } |
為了使讀者能夠更好的理解冒泡法,下面利用冒泡法對上一小節示例進行了重新實現。控件類的源代碼如下所示,其中沒有改變的部分使用省略號表示。
| using System; using System.Web.UI; using System.Web.UI.WebControls; using System.ComponentModel; using System.ComponentModel.Design; namespace WebControlLibrary{ public class CompositeEvent : CompositeControl { //聲明變量 private Button _button; private TextBox _textBox; private static readonly object EventSubmitKey = new object(); //定義屬性ButtonText,用于指定按鈕上的文字 [ Bindable(true), Category("Appearance"), DefaultValue(""), Description("獲取或設置顯示顯示在按鈕上的文字") ] public string ButtonText { ...... } //定義屬性Text,表示文本框的輸入 [ Bindable(true), Category("Appearance"), DefaultValue(""), Description("獲取或設置文本框輸入文本") ] public string Text { ...... } // 實現事件屬性結構 public event EventHandler Submit { add { Events.AddHandler(EventSubmitKey, value); } remove { Events.RemoveHandler(EventSubmitKey, value); } } // 實現OnSubmit protected virtual void OnSubmit(EventArgs e) { EventHandler SubmitHandler = (EventHandler)Events[EventSubmitKey]; if (SubmitHandler != null) { SubmitHandler(this, e); } } // 刪除_button_Click // 重寫ICompositeControlDesignerAccessor接口的RecreateChildContrls方法 protected override void RecreateChildControls() { ...... } //重寫CreateChildControls方法,將子控件添加到復合控件中 protected override void CreateChildControls() { Controls.Clear(); _button = new Button(); _textBox = new TextBox(); _button.ID = "btn"; _button.CommandName = "Submit"; this.Controls.Add(_button); this.Controls.Add(_textBox); } // 重寫OnBubbleEvent方法,執行事件冒泡 protected override bool OnBubbleEvent(object source, EventArgs e) { bool handled = false; if (e is CommandEventArgs) { CommandEventArgs ce = (CommandEventArgs)e; if (ce.CommandName == "Submit") { OnSubmit(EventArgs.Empty); handled = true; } } return handled; } //重寫Render方法,呈現控件中其他的HTML代碼 protected override void Render(HtmlTextWriter output) { ...... } } } |
本例的CompositeEvent類與上小節中的CompositeEvent類實現了同一功能。就控件呈現方面,兩個類沒有任何差別,差別主要表現在對于復合控件的事件實現方面。差別一:在本例的CreateChildControl方法中,為子控件_button設置了CommandName屬性,其屬性值為Submit。差別二:刪除了_button_Click事件處理程序。差別三:重寫了Control基類的OnBubbleEvent方法,檢查事件參數是否是CommandEventArgs類的實例。如果是,使用事件參數的CommandName成員確定是否需要引發事件處理程序OnSubmit,并返回true。
小結
本文重點介紹了復合控件的事件實現方法,并通過典型示例說明了這些實現方法的具體應用。總體而言,為復合控件實現事件并不是特別困難的事情。關鍵是開發人員必須在領會為普通控件實現事件的基礎之上,掌握包含法和冒泡法的實現要點。
