在上面一篇文章中,我們討論了有關建立複合控制項的基本理論,並且通過一個典型應用掌握了複合控制項的呈現方法。本文將繼續講解有關建立複合控制項的內容,重點是為複合控制項實現事件的具體方法。
複合控制項的事件處理簡介
談到自訂控制項的事件處理問題,這在前面的系列文章中已經進行講解。由前文可知,實現控制項事件的核心主要是定義事件屬性結構和事件處理常式等。然而,這些內容是構建所有自訂伺服器控制項的基礎。僅僅依靠這些方法是無法實現複合控制項的事件的。因為,複合控制項中包含子控制項,這就使得複合控制項的事件處理變得複雜起來。顯而易見,在複合控制項的事件實現過程中,需要面臨的最大問題是:由於不允許開發人員直接存取子控制項(雖然通過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所示。
在以上應用中,當使用者單擊"提交"按鈕之後,將引發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。
小結
本文重點介紹了複合控制項的事件實現方法,並通過典型樣本說明了這些實現方法的具體應用。總體而言,為複合控制項實現事件並不是特別困難的事情。關鍵是開發人員必須在領會為普通控制項實現事件的基礎之上,掌握包含法和冒泡法的實現要點。