下載本文源代碼 在前面的文章中,主要介紹了服務(wù)器控件的基本概念、基本理論,這些內(nèi)容是構(gòu)建所有自定義服務(wù)器控件的基石。然而,僅僅依靠這些知識還不足以創(chuàng)建出優(yōu)秀的服務(wù)器控件。因為,不同類型的服務(wù)器控件具有不同的創(chuàng)建方法,開發(fā)人員必須在掌握基本概念和理論之后,掌握不同類型服務(wù)器控件的開發(fā)方法。本文及其隨后幾篇文章將詳細介紹與創(chuàng)建復(fù)合控件相關(guān)的內(nèi)容。本文重點介紹有關(guān)復(fù)合控件的概念、創(chuàng)建方法等理論,然后,通過一個典型示例加深讀者對于復(fù)合控件創(chuàng)建方法的理解。
復(fù)合控件概述 復(fù)合控件中的“復(fù)合”一詞表明該類型控件本質(zhì)上由多個組件組合而成。同時,復(fù)合控件對外暴露的成員對象通常由構(gòu)成組件的方法和屬性提供,并且可能加入一些新的成員。復(fù)合控件也可以實現(xiàn)自定義事件,并處理并引發(fā)子控件所引起的事件。就功能方面而言,復(fù)合控件的功能要比簡單組合幾個控件的功能要強大的多,而且很多時候具有一定的專項性。例如,ASP.NET 2.0新增的Login控件就是一個典型的復(fù)合控件。該控件用戶界面由多個單獨控件組合而成,并且使用單一的API對控件進行設(shè)置和訪問。另外,Login控件由于與成員資格等功能集成的原因,因此,其具有快速實現(xiàn)用戶登錄的功能。
可能部分有經(jīng)驗的讀者在了解了復(fù)合控件的基本概念之后會有所疑惑:復(fù)合控件與用戶控件好像非常相似,那么它們之間有什么區(qū)別嗎?到底什么時候創(chuàng)建復(fù)合控件,什么時候創(chuàng)建用戶控件呢?回答這個問題,我們必須從用戶控件的基本概念入手進行研究。
簡單而言,用戶控件是指在一個項目中,由于同樣一些功能模塊在多處引用,例如,導(dǎo)航菜單等,可以把這一塊代碼做成一個用戶控件,然后,在需要引用的頁面中注冊后,直接按控件使用的方式引用,省去了重復(fù)編寫相同代碼的工作。就復(fù)合控件與用戶控件的區(qū)別而言,主要可以總結(jié)為以下幾點:
一、復(fù)合控件創(chuàng)作的最短設(shè)計時支持,用戶控件創(chuàng)作的完全設(shè)計時支持。在可視化設(shè)計器中,創(chuàng)作用戶控件與創(chuàng)作ASP.NET頁沒有差別。
二、復(fù)合控件是以目標為公共語言運行庫的面向?qū)ο蟮木幊陶Z言,如C#,是用編程方式創(chuàng)作的用戶控件是使用ASP.NET頁語法和腳本塊聲明性地創(chuàng)作的。
三、復(fù)合控件是作為
程序集(.dll)編譯和保持的。用戶控件是帶有.ascx擴展名的文本文件。
四、復(fù)合控件非常適于創(chuàng)作一般的可重新發(fā)布的控件,用戶控件適合應(yīng)用程序特定的功能。
五、可將復(fù)合控件添加到可視化設(shè)計器的工具箱中并拖放到頁面上,使用時可以在屬性框中設(shè)計,用戶控件只能在HTML中編寫。
通過以上內(nèi)容,相信讀者已經(jīng)能夠基本了解了復(fù)合控件。下面介紹一下創(chuàng)建復(fù)合控件的實現(xiàn)方法。在這個過程中,開發(fā)人員必須把握以下幾個要點:
第一、通常情況,復(fù)合控件類必須派生自
System.Web.UI.WebControls.CompositeControl類。這一點與ASP.NET 1.x環(huán)境下開發(fā)復(fù)合控件有些不同。在ASP.NET 1.x中,復(fù)合控件必須
實現(xiàn)System.Web.UI.INamingContainer接口。然而,在ASP.NET 2.0下,復(fù)合控件類的基類則發(fā)生了變化。下面簡單介紹一下CompositeControl類。
CompositeControl類是一個抽象類,該類可為自定義控件提供命名容器和控件設(shè)計器功能,并且可包含全部子控件或使用其他控件功能。CompositeControl類的聲明代碼如下所示:
public abstract class CompositeControl : WebControl, INamingContainer, ICompositeControlDesignerAccessor |
如上代碼所示,CompositeControl類基層自WebControl基類,并且實現(xiàn)INamingContainer和ICompositeControlDesignerAccessor接口。INamingContainer是一個沒有方法的標記接口。當(dāng)控件在實現(xiàn)INamingContainer時,頁框架可在該控件下創(chuàng)建新的命名范圍,因此,能夠確保子控件在控件的分層樹中具有唯一的名稱。當(dāng)復(fù)合控件公開模板屬性,提供數(shù)據(jù)綁定或需要傳送事件到子控件時,這是非常重要的。ICompositeControlDesignerAccessor接口使復(fù)合控件設(shè)計器可以在設(shè)計時重新創(chuàng)建其關(guān)聯(lián)控件的子控件。該接口包含一個需要實現(xiàn)的方法RecreateChildControls。該方法使復(fù)合控件的設(shè)計器可以在設(shè)計時重新創(chuàng)建該控件的子控件。
另外,如果創(chuàng)建的是數(shù)據(jù)綁定復(fù)合控件,那么自定義控件類的基類應(yīng)該是CompositeDataBoundControl。有關(guān)該類的具體內(nèi)容,請讀者查閱相關(guān)資料。
第二、必須重寫Control基類的CreateChildControls方法,以便對子控件進行初始化、實例化,并將其添加到控件樹中。CreateChildControls用于通知使用基于合成實現(xiàn)的服務(wù)器控件,創(chuàng)建它們包含的任何子控件,以便為回發(fā)或呈現(xiàn)做準備。重寫該方法是實現(xiàn)復(fù)合控件的關(guān)鍵所在。這種類撰寫的方法將通知.NET框架有關(guān)復(fù)合控件中包含哪些子控件,以及各個子控件在控件樹中的位置和關(guān)系等內(nèi)容。通過這種方法,復(fù)合控件將復(fù)用子控件提供的實現(xiàn)來進行呈現(xiàn)、事件處理、樣式及其他功能。
在實現(xiàn)復(fù)合控件過程中,除了掌握CompositeControl基類和CreateChildControls方法之外,ASP.NET 2.0還提供了與復(fù)合控件相關(guān)的其他方法和屬性,掌握這些成員對于開發(fā)復(fù)合控件也很重要。下面列舉了這些常見方法和屬性。
· protected virtual void EnsureChildControls()
該方法用于確定服務(wù)器控件是否包含子控件。如果不包含,則創(chuàng)建子控件。該方法首先檢查 ChildControlsCreated 屬性的當(dāng)前值。如果此值為假,則調(diào)用CreateChildControls方法。當(dāng)需要確保已創(chuàng)建子控件時,將調(diào)用該方法。大多數(shù)情況下,自定義服務(wù)器控件的開發(fā)人員無需重寫此方法。如果確實重寫了此方法,請按與其默認行為相似的方式來使用。
· public virtual Control FindControl(string)
該方法用于在當(dāng)前的命名容器中搜索指定的服務(wù)器控件。
· public virtual bool HasControls()
該方法用于確定服務(wù)器控件是否包含任何子控件。如果控件包含其他控件,則為true;否則為 false。由于該方法僅確定是否存在任何子控件,它可以通過允許您避免不必要的Controls.Count屬性調(diào)用來改進性能。調(diào)用此屬性要求實例化ControlCollection對象。如果沒有子級,則創(chuàng)建該對象會浪費服務(wù)器資源。
· protected virtual void DataBindChildren ()
該方法是ASP.NET 2.0新增內(nèi)容,其用于將數(shù)據(jù)源綁定到服務(wù)器控件的子控件。這為開發(fā)數(shù)據(jù)綁定類型的復(fù)合控件提供了便利。然而,需要注意的是,在服務(wù)器控件上調(diào)用此方法時,此方法不會將數(shù)據(jù)綁定到控件。若要綁定服務(wù)器控件及其所有子控件,請調(diào)用DataBind方法。
· protected bool HasEvents ()
這也是一個ASP.NET 2.0新增的方法,其用于返回一個值,該值指示是否為控件或任何子控件注冊事件。如果注冊事件,則為true;否則為false。
· Controls屬性
該屬性的數(shù)據(jù)類型為ControlCollection,其用于獲取ControlCollection對象,該對象表示 UI 層次結(jié)構(gòu)中指定服務(wù)器控件的子控件。其屬性值指定服務(wù)器控件的子控件集合。
· NamingContainer屬性
該屬性的數(shù)據(jù)類型為Control,其用于獲取對服務(wù)器控件的命名容器的引用,此引用創(chuàng)建唯一的命名空間,以區(qū)分具有相同Control.ID屬性值的服務(wù)器控件。
· ChildControlsCreated屬性
該屬性的數(shù)據(jù)類型為bool,其用于獲取一個值,該值指示是否已創(chuàng)建服務(wù)器控件的子控件。如果已創(chuàng)建子控件則為true;否則為false。
典型應(yīng)用
上文介紹了有關(guān)創(chuàng)建復(fù)合控件的一些基本知識,下面將通過一個典型應(yīng)用加深讀者對于復(fù)合控件實現(xiàn)方法的理解,其重點放在針對復(fù)合控件的呈現(xiàn)方法上。
多數(shù)控件呈現(xiàn)通過重寫Render方法實現(xiàn),然而,在復(fù)合控件中則大有不同。復(fù)合控件由多個子控件組合而成,其呈現(xiàn)邏輯是由子控件提供的。
據(jù)此,在少數(shù)較為簡單的情況下,復(fù)合控件不用重寫Render方法,例如,創(chuàng)建一個包含文本框和按鈕的復(fù)合控件,這時,只要通過類撰寫的方法在CreateChildControls中添加相關(guān)控件即可,而無需Render方法。但是,在多數(shù)情況下,復(fù)合控件中既包含子控件,又包含用于格式化和布局的HTML。
針對這種情況,如果只采取類撰寫的實現(xiàn)方法,那么很容易造成錯誤,并且生成的復(fù)合控件性能受到很大影響。最好的解決方法是重寫CreateChildControls方法,同時也重寫Render方法。在CreateChildControls方法中,為復(fù)合控件添加子控件;在Render方法中,為HTTP輸出流添加用于格式化和布局的HTML。
下面列舉了呈現(xiàn)復(fù)合控件的關(guān)鍵步驟:
· 控件基類繼承自CompositeControl基類。這是在ASP.NET 2.0中創(chuàng)建復(fù)合控件的關(guān)鍵所在。
· 重寫CreateChildControls方法,完成實例化、初始化子控件,并且將子控件添加到控件集合中。
· 重寫ICompositeControlDesignerAccessor接口的RecreateChildContrls方法。
· 如果復(fù)合控件中存在用于格式化和布局的HTML,那么建議將這些內(nèi)容寫入Render方法中,而不要在CreateChildControls方法中創(chuàng)建和添加所需的LiteralControl實例。另外,在添加相關(guān)HTML代碼過程中,為了讓子控件使用默認生成方法,必須使每個子控件調(diào)用RenderControl方法。
為了便于讀者更好的理解以上內(nèi)容,下面舉例說明。在此示例中,Register控件使用子控件創(chuàng)建用戶界面(UI),用于輸入用戶信息,以向網(wǎng)站注冊。此用戶界面包括兩個TextBox控件(一個用于輸入用戶名,另一個用于輸入用戶的電子郵件地址)和一個用于提交信息的Button控件。Register還將RequiredFieldValidator控件與兩個TextBox控件關(guān)聯(lián)起來,以確保用戶輸入了名稱和電子郵件地址。復(fù)合控件Register源代碼如下所示:
using System; using System.ComponentModel; using System.Drawing; using System.Security.Permissions; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace WebControlLibrary{ [ DefaultProperty("ButtonText"), ToolboxData("<{0}:Register runat=\"server\"> </{0}:Register>"), ]
public class Register : CompositeControl { // 定義私有字段 private Button submitButton; private TextBox nameTextBox; private Label nameLabel; private TextBox emailTextBox; private Label emailLabel; private RequiredFieldValidator emailValidator; private RequiredFieldValidator nameValidator; // 實現(xiàn)屬性ButtonText
[ Bindable(true), Category("Appearance"), DefaultValue(""), Description("按鈕上的文字內(nèi)容.") ]
public string ButtonText { get { EnsureChildControls(); return submitButton.Text; } set { EnsureChildControls(); submitButton.Text = value; } }
// 實現(xiàn)屬性Name
[ Bindable(true), Category("Default"), DefaultValue(""), Description("用戶名.") ]
public string Name { get { EnsureChildControls(); return nameTextBox.Text; } set { EnsureChildControls(); nameTextBox.Text = value; } }
// 實現(xiàn)屬性NameErrorMessage
[ Bindable(true), Category("Appearance"), DefaultValue(""), Description("用戶名驗證錯誤信息.") ]
public string NameErrorMessage { get { EnsureChildControls(); return nameValidator.ErrorMessage; } set { EnsureChildControls(); nameValidator.ErrorMessage = value; nameValidator.ToolTip = value; } }
// 實現(xiàn)屬性NameLabelText
[ Bindable(true), Category("Appearance"), DefaultValue(""), Description("用戶名文本框旁的文字.") ]
public string NameLabelText { get { EnsureChildControls(); return nameLabel.Text; } set { EnsureChildControls(); nameLabel.Text = value; } }
// 實現(xiàn)屬性Email
[ Bindable(true), Category("Default"), DefaultValue(""), Description("郵件地址.") ]
public string Email { get { EnsureChildControls(); return emailTextBox.Text; } set { EnsureChildControls(); emailTextBox.Text = value; } }
// 實現(xiàn)屬性EmailErrorMessage
[ Bindable(true), Category("Appearance"), DefaultValue(""), Description("郵件地址驗證錯誤信息.") ]
public string EmailErrorMessage { get { EnsureChildControls(); return emailValidator.ErrorMessage; } set { EnsureChildControls(); emailValidator.ErrorMessage = value; emailValidator.ToolTip = value; } }
// 實現(xiàn)屬性EmailLabelText
[ Bindable(true), Category("Appearance"), DefaultValue(""), Description("電子郵件文本框旁的文字.") ]
public string EmailLabelText { get { EnsureChildControls(); return emailLabel.Text; } set { EnsureChildControls(); emailLabel.Text = value; } }
// 重寫ICompositeControlDesignerAccessor接口的RecreateChildContrls方法
protected override void RecreateChildControls() { EnsureChildControls(); }
// 重寫Control基類的CreateChildControls方法
protected override void CreateChildControls() { // 清除所有子控件 Controls.Clear(); nameLabel = new Label(); nameTextBox = new TextBox(); nameTextBox.ID = "nameTextBox"; nameValidator = new RequiredFieldValidator(); nameValidator.ID = "validator1"; nameValidator.ControlToValidate = nameTextBox.ID; nameValidator.Text = NameErrorMessage; nameValidator.Display = ValidatorDisplay.Static; emailLabel = new Label(); emailTextBox = new TextBox(); emailTextBox.ID = "emailTextBox"; emailValidator = new RequiredFieldValidator(); emailValidator.ID = "validator2"; emailValidator.ControlToValidate = emailTextBox.ID; emailValidator.Text = EmailErrorMessage; emailValidator.Display = ValidatorDisplay.Static; submitButton = new Button(); submitButton.ID = "button1"; this.Controls.Add(nameLabel); this.Controls.Add(nameTextBox); this.Controls.Add(nameValidator); this.Controls.Add(emailLabel); this.Controls.Add(emailTextBox); this.Controls.Add(emailValidator); this.Controls.Add(submitButton); }
// 重寫Render方法
protected override void Render(HtmlTextWriter writer) { AddAttributesToRender(writer); writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "1", false); writer.RenderBeginTag(HtmlTextWriterTag.Table); writer.RenderBeginTag(HtmlTextWriterTag.Tr); writer.RenderBeginTag(HtmlTextWriterTag.Td); nameLabel.RenderControl(writer); writer.RenderEndTag(); writer.RenderBeginTag(HtmlTextWriterTag.Td); nameTextBox.RenderControl(writer); writer.RenderEndTag(); writer.RenderBeginTag(HtmlTextWriterTag.Td); nameValidator.RenderControl(writer); writer.RenderEndTag(); writer.RenderEndTag(); writer.RenderBeginTag(HtmlTextWriterTag.Tr); writer.RenderBeginTag(HtmlTextWriterTag.Td); emailLabel.RenderControl(writer); writer.RenderEndTag(); writer.RenderBeginTag(HtmlTextWriterTag.Td); emailTextBox.RenderControl(writer); writer.RenderEndTag(); writer.RenderBeginTag(HtmlTextWriterTag.Td); emailValidator.RenderControl(writer); writer.RenderEndTag(); writer.RenderEndTag(); writer.RenderBeginTag(HtmlTextWriterTag.Tr); writer.AddAttribute(HtmlTextWriterAttribute.Colspan, "2", false); writer.AddAttribute(HtmlTextWriterAttribute.Align, "right", false); writer.RenderBeginTag(HtmlTextWriterTag.Td); submitButton.RenderControl(writer); writer.RenderEndTag(); writer.RenderBeginTag(HtmlTextWriterTag.Td); writer.Write(" "); writer.RenderEndTag(); writer.RenderEndTag(); writer.RenderEndTag(); } } } |
以上列舉了復(fù)合控件類Register的源代碼。雖然代碼有些冗長,然而還是比較容易理解的。下圖1列舉了Register類結(jié)構(gòu)圖。
如圖1并結(jié)合代碼可知,Register類繼承自CompositeControl基類,其實現(xiàn)了7個屬性和3個方法。屬性包括ButtonText、Email、EmailErrorMessage、EmailLabelText、Name、NameErrorMessage和NameLabelText。這些屬性通過命名,相信讀者基本可以了解其意義。
另外,Register類中重寫了來自不同對象的3個方法。
?。?)Render方法隸屬于Control基類,在本例中主要在該方法中定義了一些與控件布局相關(guān)的HTML等內(nèi)容。
?。?)CreateChildControls方法隸屬于Control基類。在本示例中通過重寫該方法,實現(xiàn)了將子控件添加到復(fù)合控件樹中的任務(wù)。請讀者牢記:每當(dāng)需要Controls集合時,例如,在數(shù)據(jù)綁定期間(如果適用),服務(wù)器控件結(jié)構(gòu)都會依賴對CreateChildControls的調(diào)用。為此,必須在CreateChildControls方法中添加子控件。
?。?)RecreateChildControls方法來自CompositeControl基類的ICompositeControlDesignerAccessor接口。通過實現(xiàn)這個方法可使復(fù)合控件的設(shè)計器可以在設(shè)計時重新創(chuàng)建該控件的子控件。
下面列舉了為測試Register控件而創(chuàng)建的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"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>實現(xiàn)復(fù)合控件呈現(xiàn)</title> </head> <body> <form id="form1" runat="server"> <div> <Sample:Register id="demo1" runat="server" ButtonText="注冊" EmailLabelText="電子郵件" NameLabelText="用戶名" EmailErrorMessage="不能為空" NameErrorMessage="不能為空" /> </div> </form> </body> </html> |
示例效果如圖2所示。
圖2 應(yīng)用程序效果圖
如圖2所示,復(fù)合控件Register在頁面中顯示了文本、文本框、按鈕等,同時,還提供了輸入驗證功能。然而,當(dāng)用戶單擊“注冊”按鈕之后,即使在用戶名和電子郵件的輸入都通過驗證的情況下,頁面仍然沒有引發(fā)相應(yīng)的事件處理程序。這是由于在本例中沒有實現(xiàn)按鈕的事件處理內(nèi)容。有關(guān)復(fù)合控件的事件實現(xiàn)將在隨后的文章中進行詳細講解。
小結(jié) 復(fù)合控件是通過將其他控件聚合在某一公用API下創(chuàng)建而成的控件。復(fù)合控件將保留自己子控件的活動實例,并且不僅限于呈現(xiàn)這些實例。使用復(fù)合控件可以帶來幾點好處,例如可以簡化對事件和回發(fā)的處理。然而,本文并沒有對復(fù)合控件的事件實現(xiàn)等相關(guān)內(nèi)容進行講解,請讀者繼續(xù)關(guān)注本系列的隨后文章。