top
Loading...
ASP.NET2.0服務器控件之復合控件概述
下載本文源代碼

在前面的文章中,主要介紹了服務器控件的基本概念、基本理論,這些內容是構建所有自定義服務器控件的基石。然而,僅僅依靠這些知識還不足以創建出優秀的服務器控件。因為,不同類型的服務器控件具有不同的創建方法,開發人員必須在掌握基本概念和理論之后,掌握不同類型服務器控件的開發方法。本文及其隨后幾篇文章將詳細介紹與創建復合控件相關的內容。本文重點介紹有關復合控件的概念、創建方法等理論,然后,通過一個典型示例加深讀者對于復合控件創建方法的理解。

復合控件概述

復合控件中的“復合”一詞表明該類型控件本質上由多個組件組合而成。同時,復合控件對外暴露的成員對象通常由構成組件的方法和屬性提供,并且可能加入一些新的成員。復合控件也可以實現自定義事件,并處理并引發子控件所引起的事件。就功能方面而言,復合控件的功能要比簡單組合幾個控件的功能要強大的多,而且很多時候具有一定的專項性。例如,ASP.NET 2.0新增的Login控件就是一個典型的復合控件。該控件用戶界面由多個單獨控件組合而成,并且使用單一的API對控件進行設置和訪問。另外,Login控件由于與成員資格等功能集成的原因,因此,其具有快速實現用戶登錄的功能。

可能部分有經驗的讀者在了解了復合控件的基本概念之后會有所疑惑:復合控件與用戶控件好像非常相似,那么它們之間有什么區別嗎?到底什么時候創建復合控件,什么時候創建用戶控件呢?回答這個問題,我們必須從用戶控件的基本概念入手進行研究。

簡單而言,用戶控件是指在一個項目中,由于同樣一些功能模塊在多處引用,例如,導航菜單等,可以把這一塊代碼做成一個用戶控件,然后,在需要引用的頁面中注冊后,直接按控件使用的方式引用,省去了重復編寫相同代碼的工作。就復合控件與用戶控件的區別而言,主要可以總結為以下幾點:

一、復合控件創作的最短設計時支持,用戶控件創作的完全設計時支持。在可視化設計器中,創作用戶控件與創作ASP.NET頁沒有差別。

二、復合控件是以目標為公共語言運行庫的面向對象的編程語言,如C#,是用編程方式創作的用戶控件是使用ASP.NET頁語法和腳本塊聲明性地創作的。

三、復合控件是作為程序集(.dll)編譯和保持的。用戶控件是帶有.ascx擴展名的文本文件。

四、復合控件非常適于創作一般的可重新發布的控件,用戶控件適合應用程序特定的功能。

五、可將復合控件添加到可視化設計器的工具箱中并拖放到頁面上,使用時可以在屬性框中設計,用戶控件只能在HTML中編寫。

通過以上內容,相信讀者已經能夠基本了解了復合控件。下面介紹一下創建復合控件的實現方法。在這個過程中,開發人員必須把握以下幾個要點:

第一、通常情況,復合控件類必須派生自System.Web.UI.WebControls.CompositeControl類。這一點與ASP.NET 1.x環境下開發復合控件有些不同。在ASP.NET 1.x中,復合控件必須實現System.Web.UI.INamingContainer接口。然而,在ASP.NET 2.0下,復合控件類的基類則發生了變化。下面簡單介紹一下CompositeControl類。

CompositeControl類是一個抽象類,該類可為自定義控件提供命名容器和控件設計器功能,并且可包含全部子控件或使用其他控件功能。CompositeControl類的聲明代碼如下所示:

public abstract class CompositeControl : WebControl, INamingContainer, ICompositeControlDesignerAccessor

如上代碼所示,CompositeControl類基層自WebControl基類,并且實現INamingContainer和ICompositeControlDesignerAccessor接口。INamingContainer是一個沒有方法的標記接口。當控件在實現INamingContainer時,頁框架可在該控件下創建新的命名范圍,因此,能夠確保子控件在控件的分層樹中具有唯一的名稱。當復合控件公開模板屬性,提供數據綁定或需要傳送事件到子控件時,這是非常重要的。ICompositeControlDesignerAccessor接口使復合控件設計器可以在設計時重新創建其關聯控件的子控件。該接口包含一個需要實現的方法RecreateChildControls。該方法使復合控件的設計器可以在設計時重新創建該控件的子控件。

另外,如果創建的是數據綁定復合控件,那么自定義控件類的基類應該是CompositeDataBoundControl。有關該類的具體內容,請讀者查閱相關資料。

第二、必須重寫Control基類的CreateChildControls方法,以便對子控件進行初始化、實例化,并將其添加到控件樹中。CreateChildControls用于通知使用基于合成實現的服務器控件,創建它們包含的任何子控件,以便為回發或呈現做準備。重寫該方法是實現復合控件的關鍵所在。這種類撰寫的方法將通知.NET框架有關復合控件中包含哪些子控件,以及各個子控件在控件樹中的位置和關系等內容。通過這種方法,復合控件將復用子控件提供的實現來進行呈現、事件處理、樣式及其他功能。

在實現復合控件過程中,除了掌握CompositeControl基類和CreateChildControls方法之外,ASP.NET 2.0還提供了與復合控件相關的其他方法和屬性,掌握這些成員對于開發復合控件也很重要。下面列舉了這些常見方法和屬性。

· protected virtual void EnsureChildControls()

該方法用于確定服務器控件是否包含子控件。如果不包含,則創建子控件。該方法首先檢查 ChildControlsCreated 屬性的當前值。如果此值為假,則調用CreateChildControls方法。當需要確保已創建子控件時,將調用該方法。大多數情況下,自定義服務器控件的開發人員無需重寫此方法。如果確實重寫了此方法,請按與其默認行為相似的方式來使用。

· public virtual Control FindControl(string)

該方法用于在當前的命名容器中搜索指定的服務器控件。

· public virtual bool HasControls()

該方法用于確定服務器控件是否包含任何子控件。如果控件包含其他控件,則為true;否則為 false。由于該方法僅確定是否存在任何子控件,它可以通過允許您避免不必要的Controls.Count屬性調用來改進性能。調用此屬性要求實例化ControlCollection對象。如果沒有子級,則創建該對象會浪費服務器資源。

· protected virtual void DataBindChildren ()

該方法是ASP.NET 2.0新增內容,其用于將數據源綁定到服務器控件的子控件。這為開發數據綁定類型的復合控件提供了便利。然而,需要注意的是,在服務器控件上調用此方法時,此方法不會將數據綁定到控件。若要綁定服務器控件及其所有子控件,請調用DataBind方法。

· protected bool HasEvents ()

這也是一個ASP.NET 2.0新增的方法,其用于返回一個值,該值指示是否為控件或任何子控件注冊事件。如果注冊事件,則為true;否則為false。

· Controls屬性

該屬性的數據類型為ControlCollection,其用于獲取ControlCollection對象,該對象表示 UI 層次結構中指定服務器控件的子控件。其屬性值指定服務器控件的子控件集合。

· NamingContainer屬性

該屬性的數據類型為Control,其用于獲取對服務器控件的命名容器的引用,此引用創建唯一的命名空間,以區分具有相同Control.ID屬性值的服務器控件。

· ChildControlsCreated屬性

該屬性的數據類型為bool,其用于獲取一個值,該值指示是否已創建服務器控件的子控件。如果已創建子控件則為true;否則為false。

典型應用

上文介紹了有關創建復合控件的一些基本知識,下面將通過一個典型應用加深讀者對于復合控件實現方法的理解,其重點放在針對復合控件的呈現方法上。

多數控件呈現通過重寫Render方法實現,然而,在復合控件中則大有不同。復合控件由多個子控件組合而成,其呈現邏輯是由子控件提供的。

據此,在少數較為簡單的情況下,復合控件不用重寫Render方法,例如,創建一個包含文本框和按鈕的復合控件,這時,只要通過類撰寫的方法在CreateChildControls中添加相關控件即可,而無需Render方法。但是,在多數情況下,復合控件中既包含子控件,又包含用于格式化和布局的HTML。

針對這種情況,如果只采取類撰寫的實現方法,那么很容易造成錯誤,并且生成的復合控件性能受到很大影響。最好的解決方法是重寫CreateChildControls方法,同時也重寫Render方法。在CreateChildControls方法中,為復合控件添加子控件;在Render方法中,為HTTP輸出流添加用于格式化和布局的HTML。

下面列舉了呈現復合控件的關鍵步驟:

· 控件基類繼承自CompositeControl基類。這是在ASP.NET 2.0中創建復合控件的關鍵所在。

· 重寫CreateChildControls方法,完成實例化、初始化子控件,并且將子控件添加到控件集合中。

· 重寫ICompositeControlDesignerAccessor接口的RecreateChildContrls方法。

· 如果復合控件中存在用于格式化和布局的HTML,那么建議將這些內容寫入Render方法中,而不要在CreateChildControls方法中創建和添加所需的LiteralControl實例。另外,在添加相關HTML代碼過程中,為了讓子控件使用默認生成方法,必須使每個子控件調用RenderControl方法。

為了便于讀者更好的理解以上內容,下面舉例說明。在此示例中,Register控件使用子控件創建用戶界面(UI),用于輸入用戶信息,以向網站注冊。此用戶界面包括兩個TextBox控件(一個用于輸入用戶名,另一個用于輸入用戶的電子郵件地址)和一個用于提交信息的Button控件。Register還將RequiredFieldValidator控件與兩個TextBox控件關聯起來,以確保用戶輸入了名稱和電子郵件地址。復合控件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;
// 實現屬性ButtonText

[
Bindable(true),
Category("Appearance"),
DefaultValue(""),
Description("按鈕上的文字內容.")
]

public string ButtonText {
get {
EnsureChildControls();
return submitButton.Text;
}
set {
EnsureChildControls();
submitButton.Text = value;
}
}

// 實現屬性Name

[
Bindable(true),
Category("Default"),
DefaultValue(""),
Description("用戶名.")
]

public string Name {
get {
EnsureChildControls();
return nameTextBox.Text;
}
set {
EnsureChildControls();
nameTextBox.Text = value;
}
}

// 實現屬性NameErrorMessage

[
Bindable(true),
Category("Appearance"),
DefaultValue(""),
Description("用戶名驗證錯誤信息.")
]

public string NameErrorMessage {
get {
EnsureChildControls();
return nameValidator.ErrorMessage;
}
set {
EnsureChildControls();
nameValidator.ErrorMessage = value;
nameValidator.ToolTip = value;
}
}

// 實現屬性NameLabelText

[
Bindable(true),
Category("Appearance"),
DefaultValue(""),
Description("用戶名文本框旁的文字.")
]

public string NameLabelText {
get {
EnsureChildControls();
return nameLabel.Text;
}
set {
EnsureChildControls();
nameLabel.Text = value;
}
}

// 實現屬性Email

[
Bindable(true),
Category("Default"),
DefaultValue(""),
Description("郵件地址.")
]

public string Email {
get {
EnsureChildControls();
return emailTextBox.Text;
}
set {
EnsureChildControls();
emailTextBox.Text = value;
}
}

// 實現屬性EmailErrorMessage

[
Bindable(true),
Category("Appearance"),
DefaultValue(""),
Description("郵件地址驗證錯誤信息.")
]

public string EmailErrorMessage {
get {
EnsureChildControls();
return emailValidator.ErrorMessage;
}
set {
EnsureChildControls();
emailValidator.ErrorMessage = value;
emailValidator.ToolTip = value;
}
}

// 實現屬性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();
}
}
}

以上列舉了復合控件類Register的源代碼。雖然代碼有些冗長,然而還是比較容易理解的。下圖1列舉了Register類結構圖。


圖1 類結構圖

如圖1并結合代碼可知,Register類繼承自CompositeControl基類,其實現了7個屬性和3個方法。屬性包括ButtonText、Email、EmailErrorMessage、EmailLabelText、Name、NameErrorMessage和NameLabelText。這些屬性通過命名,相信讀者基本可以了解其意義。

另外,Register類中重寫了來自不同對象的3個方法。

(1)Render方法隸屬于Control基類,在本例中主要在該方法中定義了一些與控件布局相關的HTML等內容。

(2)CreateChildControls方法隸屬于Control基類。在本示例中通過重寫該方法,實現了將子控件添加到復合控件樹中的任務。請讀者牢記:每當需要Controls集合時,例如,在數據綁定期間(如果適用),服務器控件結構都會依賴對CreateChildControls的調用。為此,必須在CreateChildControls方法中添加子控件。

(3)RecreateChildControls方法來自CompositeControl基類的ICompositeControlDesignerAccessor接口。通過實現這個方法可使復合控件的設計器可以在設計時重新創建該控件的子控件。

下面列舉了為測試Register控件而創建的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>實現復合控件呈現</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 應用程序效果圖

如圖2所示,復合控件Register在頁面中顯示了文本、文本框、按鈕等,同時,還提供了輸入驗證功能。然而,當用戶單擊“注冊”按鈕之后,即使在用戶名和電子郵件的輸入都通過驗證的情況下,頁面仍然沒有引發相應的事件處理程序。這是由于在本例中沒有實現按鈕的事件處理內容。有關復合控件的事件實現將在隨后的文章中進行詳細講解。

小結

復合控件是通過將其他控件聚合在某一公用API下創建而成的控件。復合控件將保留自己子控件的活動實例,并且不僅限于呈現這些實例。使用復合控件可以帶來幾點好處,例如可以簡化對事件和回發的處理。然而,本文并沒有對復合控件的事件實現等相關內容進行講解,請讀者繼續關注本系列的隨后文章。
作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗