ASP.NET 控制項開發速成教程:產生複合控制項

來源:互聯網
上載者:User
ASP.NET 控制項開發速成教程:產生複合控制項 來自MSDN

發布日期 : 2006-6-24 | 更新日期 : 2006-6-24

適用於:
Microsoft ASP.NET 2.0
Visual Basic 2005
Visual C# 2005
.NET Frameworks
Visual Web Developer 2005

摘要: Dino Esposito 一直在編寫有關 ASP.NET 控制項開發的系列教程,並在以下第四部分中介紹了如何使用和建立複合控制項。

隨本文提供了 Visual Basic C# 兩種原始碼。請從 此處 下載。

本頁內容

簡介
複合控制項的要點是什嗎?
複合控制項的常見方案
複合控制項的呈現引擎
用於解決設計時問題的 CompositeControl
產生資料繫結複合控制項
結論

簡介

複合控制項只不過是普通的 ASP.NET 控制項,還不屬於要論及的另一種類型的 ASP.NET 伺服器控制項。既然這樣,為什麼在各書籍和文檔中總要留出專門的章節來論述複合控制項呢?ASP.NET 複合控制項有什麼特別之處呢?

顧名思義,複合控制項是將多個其他控制項聚集在某單一頂部和單一 API 下的控制項。如果某個自訂控制項由一個標籤和一個文字框組成,就可以說該控制項是一個複合控制項。“複合”一詞表明該控制項本質上是由其他構成組件在運行時組合而成。複合控制項所暴露的方法集和屬性集通常(但不是必須)由構成組件的方法和屬性提供,並加入一些新成員。複合控制項也可以引發自訂事件,還可以處理並激起子控制項所引起的事件。

複合控制項在 ASP.NET 中如此特別並不是因為其有可能成為伺服器控制項新類型的代表。更確切的說是因為它在呈現時獲得了 ASP.NET 運行時的支援。

複合控制項是一個功能強大的工具,可以產生豐富複雜的組件,這些組件產生自使用中的物件的相互作用而不是某些字串產生器對象的標記輸出。複合控制項以構成控制項樹的形式呈現,每個構成控制項都有其自己的生命週期和事件,並且所有構成控制項都聯合構成一個全新的 API,並按需要儘可能地抽象化。

在本文中,我將論述複合控制項的內部體繫結構,以闡明它在多種情況下為您帶來的好處。接下來,我將產生一個複合清單控制項,與我在以前文章中所述控制項的功能集相比,此控制項的功能集更為豐富。

返回頁首 複合控制項的要點是什嗎?

前一段時間,我曾經自己嘗試在 ASP.NET. 中研究複合控制項。我從 MSDN 文檔學習理論和實踐知識,並也設計出一些不錯的控制項。但是,只有當我有一次在純屬偶然的情況下看到以下樣本時,我才真正領悟到複合控制項的要點(和優點)。設想一下由兩個其他控制項(LabelTextBox)的組合產生的迄今為止最簡單(也是最常見)的控制項。以下介紹了一種編寫這種控制項的可行方法。我們將其命名為 LabelTextBox

public class LabelTextBox :WebControl, INamingContainer{public string Text {get {object o = ViewState["Text"];if (o == null)return String.Empty;return (string) o;      }set { ViewState["Text"] = value; }   }public string Title {get {object o = ViewState["Title"];if (o == null)return String.Empty;return (string) o;      }set { ViewState["Title"] = value; }   }protected override void CreateChildControls()   {Controls.Clear();CreateControlHierarchy();ClearChildViewState();   }protected virtual void CreateControlHierarchy()   {TextBox t = new TextBox();Label l = new Label();t.Text = Text;l.Text = Title;Controls.Add(l);Controls.Add(t);   }}

該控制項具備兩個公用屬性(TextTitle)以及一個呈現引擎。這兩個屬性儲存在檢視狀態中,並分別表示 TextBoxLabel 的內容。該控制項對於 Render 方法沒有替換方法,並通過 CreateChildControls 替換方法來產生其自己的標記。我馬上就會詳述呈現階段的例行過程。CreateChildControls 的代碼首先清除子控制項的集合,然後為當前控制項輸出的構成控制項產生控制項樹。CreateControlHierarchy 是一種特定於控制項的方法,不要求必須標記為受保護和虛擬。但請注意,大多數內建複合控制項(例如 DataGrid)只是通過一個類似的虛擬方法來暴露用於產生控制項樹的邏輯。

CreateControlHierarchy 方法會根據需要執行個體化多個構成組件,然後合成最終輸出。完成之後,各控制項將被添加到當前控制項的 Controls 集合。如果希望控制項的輸出結果是一個 HTML 表,則可以建立一個 Table 控制項,並相應添加含有各自內容的行和儲存格。所有行、儲存格和所含控制項都是最外部表格的子項。這時,您只需將 Table 控制項添加到 Controls 集合中即可。在上述代碼中,Label 和 TextBox 是 LabelTextBox 控制項的直接子項並直接添加到集合中。控制項的呈現狀態和運行狀態都很正常。

單純從效能上看,建立控制項的暫態執行個體不如呈現一些純文字的效率高。讓我們考慮一種無需子控制項就能編寫上述控制項的替代方法。這次讓我們將其命名為 TextBoxLabel

public class LabelTextBox :WebControl, INamingContainer{   :protected override void Render(HtmlTextWriter writer)   {string markup = String.Format("<span>{0}</span><input type=text value='{1}'>",Title, Text);writer.Write(markup);   }}

該控制項具備同樣的兩個屬性(TextTitle)並替換了 Render 方法。正如您所看到的那樣,其實現過程相當簡單並且代碼運行速度也略勝一籌。您可以通過在字串產生器中合成文本並為瀏覽器輸出最終標記來取代合成子控制項的這種方法。同樣,此時控制項的呈現狀態良好。但我們真的可以說它的運行狀態也同樣良好嗎?圖 1 顯示了在樣本頁中啟動並執行兩個控制項。

圖 1:使用不同呈現引擎的相似控制項

在頁面中啟用跟蹤功能並重新運行。當頁面顯示在瀏覽器中時,將其向下滾動並查看控制項樹。它將如下所示:

圖 2:由兩個控制項產生的控制項樹

複合控制項由構成組件的活動執行個體組成。ASP.NET 運行時會發現這些子控制項,並可以在處理已發佈資料時同它們進行直接通訊。其結果是,子控制項可以自己處理檢視狀態並自動激起事件。

對於基於標記合成的控制項,情況則不同。中所示,該控制項是一個帶有空 Controls 集合的代碼基本單位。如果標記在頁面中注入互動元素(文字框、按鈕、下拉式菜單),則 ASP.NET 在不涉及控制項本身的情況下無法處理回傳資料及事件。

嘗試在兩個文字框中輸入一些文本並單擊圖 1 中的[重新整理] 按鈕,這樣就可以發生一個回傳。第一個控制項(即複合控制項)在經過回傳後會正確保留所分配的文本。使用 Render 方法的第二個控制項在經過回傳後會丟失新文本。為什麼會這樣呢?其中兼有兩個原因。

第一個原因是,在上述標記中我沒有為 <input> 標記命名。這樣,它的內容就不會回傳。請注意,必須使用 name 屬性來為元素命名。讓我們對 Render 方法做如下修改。

protected override void Render(HtmlTextWriter writer){string markup = String.Format("<span>{0}</span><input type=text value='{1}' name='{2}'>",Title, Text, ClientID);writer.Write(markup);}

注入用戶端頁面的 <input> 元素現在與伺服器控制項使用相同的 ID。頁面回傳時,ASP.NET 運行時可發現一個與發行欄位的 ID 相匹配的伺服器控制項。但它並不知道如何處理該控制項。要使 ASP.NET 將所有的用戶端更改都應用於伺服器控制項,該控制項必須實現 IPostBackDataHandler 介面。

包含 TextBox 的複合控制項無需擔心回傳問題,因為所嵌入的控制項會使用 ASP.NET 自動解決該問題。呈現 TextBox 的控制項需要與 ASP.NET 進行互動,以確保可以正確處理回傳值並正常引發事件。以下代碼錶明了如何擴充 TextBoxLabel 控制項以使其完全支援回傳。

bool LoadPostData(string postDataKey, NameValueCollection postCollection){string currentText = Text;string postedText = postCollection[postDataKey];if (!currentText.Equals(postedText, StringComparison.Ordinal))    {Text = postedText;return true;    }return false;}void IPostBackDataHandler.RaisePostDataChangedEvent(){return;}
返回頁首 複合控制項的常見方案

複合控制項是適合用於構建複雜組件的工具,在複合控制項中,多個子控制項彙總到一起,並在彼此之間以及與外部之間進行互動。呈現控制項則只用於唯讀式控制項彙總,其輸出不包括互動元素(例如下拉框或文字框)。

如果您對事件處理和回傳資料感興趣,我強烈建議您選擇複合控制項。如果使用子控制項,則產生複雜的控制項樹會更加輕鬆,而且最終結果也更清晰簡潔。此外,只有需要提供附加功能時才需要處理回傳介面。

呈現控制項不但需要實現附加介面,還要將含有屬性值的標記靜態部分縫合到一起。

複合控制項的優點還表現在可以呈現多個同類項,這與在 DataGrid 控制項中的情況類似。將每個構成項作為使用中的物件啟用使您可以引發建立事件並以編程方式訪問它們的屬性。在 ASP.NET 2.0 中,對於要完全實現實際的資料繫結複合控制項(上述控制項只是隨便的舉例)所需的樣板代碼,絕大部分都隱藏在新基類的摺疊部分中:CompositeDataBoundControl

返回頁首 複合控制項的呈現引擎

在深入探討 ASP.NET 2.0 編碼技術之前,讓我們回顧一下複合控制項的內部例行過程。我們提到過,複合控制項的呈現是集中圍繞 CreateChildControls 方法進行的,該方法從 Control 基類繼承而來。您可能會認為,要使伺服器控制項呈現其內容,替換 Render 方法是必不可少的一步。正如我們先前所看到的,如果 CreateChildControls 被替換,則並不總是需要執行這一步。但是,何時在控制項調用棧中調用 CreateChildControls 呢?

中所示,在頁面第一次顯示時,會在預呈現階段調用 CreateChildControls

圖 3:在預呈現階段調用 CreateChildControls

特別是,請求處理代碼(在 Page 類中)在將 PreRender 事件引發至頁面和每個子控制項之前會直接調用 EnsureChildControls。換言之,如果控制項樹還未完全產生,則不會呈現任何控制項。

以下程式碼片段例示了 EnsureChildControls(在 Control 基礎上定義的另一種方法)的虛擬碼。

protected virtual void EnsureChildControls(){if (!ChildControlsCreated)   {try {CreateChildControls();       }finally {ChildControlsCreated = true;       }   }}

此方法可能會在頁面和控制項的生命週期內反覆調用。為避免控制項重複,ChildControlsCreated 屬性被設為 true。如果此屬性返回 true,則該方法會立即退出。

當頁面回傳時,ChildControlsCreated 會在周期前期調用。 4 所示,它在已發佈資料處理階段調用。

圖 4:發生回傳時在已發佈資料處理階段調用

當 ASP.NET 頁面開始處理從用戶端發布的資料時,它會嘗試尋找一個其 ID 與發行欄位的名稱相匹配的伺服器控制項。在執行此步驟期間,頁面代碼會調用 Control 類中的 FindControl 方法。反之,該方法需要確保在進行操作之前控制項樹已完全產生,因此它調用 EnsureChildControls 並按需要產生控制項階層。

那麼要在 CreateChildControls 方法內部執行的代碼是怎樣的呢?儘管沒有正式的指南可供遵循,但通常認為 CreateChildControls 至少必須完成以下任務:清除 Controls 集合,產生控制項樹,並清除子控制項的檢視狀態。並不嚴格要求必須從 CreateChildControls 方法內部設定 ChildControlsCreated 屬性。實際上,ASP.NET 頁面架構始終通過 EnsureChildControls(此方法可自動化佈建布爾標記)來調用 CreateChildControls

返回頁首 用於解決設計時問題的 CompositeControl

隨 ASP.NET 2.0 一同提供了一個名為 CompositeControl 的基類。因此,新的非資料繫結複合控制項應該從該類派生而不是從 WebControl 派生。在開發控制項方面,CompositeControl 的用法變動不大。您仍然需要替換 CreateChildControls 並按先前所述方式編碼。那麼 CompositeControl 的作用是什嗎?讓我們先從其原型著手:

public class CompositeControl :WebControl, INamingContainer, ICompositeControlDesignerAccessor

使用該類就無需再用 INamingContainer 裝飾控制項,但這實際上並不是很重要,因為介面只是一個標記並且不包含任何方法。更為重要的是,該類實現了一個名為 ICompositeControlDesignerAccessor 的全新介面。

public interface ICompositeControlDesignerAccessor{void RecreateChildControls();}

此介面由複合控制項的標準設計器用於在設計時重建控制項樹。以下是 CompositeControl 中方法的預設實現過程。

void ICompositeControlDesignerAccessor.RecreateChildControls(){base.ChildControlsCreated = false;EnsureChildControls();}

簡言之,如果您從 CompositeControl 派生複合控制項,就不會遇到設計時的故障,而且無需採用技巧和妙計就可以使控制項在運行時和設計時都能正常運行。

要充分理解此介面的重要性,可試以寄存某 LabelTextBox 複合控制項的樣本頁為例,並將其轉換為設計模式。控制項在運行時工作正常,但在設計時卻不可見。

圖 5:只有複合控制項從 CompositeControl 派生才對它們進行特殊的設計時處理

如果只是用 CompositeControl 替換 WebControl,則控制項在運行時仍然保持正常工作,而在設計時也會運行良好。

圖 6:在設計時運行良好的複合控制項 返回頁首 產生資料繫結複合控制項

大多數複雜的伺服器控制項都已綁定資料(也可能已經模板化),並且由各種子控制項構成。這些控制項保留了一個構成項(通常為表的行或儲存格)的列表。該列表在經過回傳後會儲存在檢視狀態中,並且從綁定資料產生或從檢視狀態重建。該控制項還在檢視狀態中儲存其構成項的數量,以便在頁面中其他控制項引起回傳時可以正確重建表結構。我將用 DataGrid 控制項舉例說明。

DataGrid 由一列行構成,每一行都代表綁定資料來源中的一個記錄。每個網格行都通過一個 DataGridRow 對象(從 TableRow 派生的一個類)表示。在各網格行建立完成並被添加到最終網格表時,諸如 ItemCreatedItemDataBound 之類的相應事件將被引發至頁面。當通過資料繫結建立 DataGrid 時,其行數由繫結項目數和頁面大小決定。如果帶有 DataGrid 的頁面回傳會怎樣?

這種情況下,如果是由 DataGrid 自身引起的回傳(例如,使用者單擊以進行排序或標頁),則新頁面會再次通過資料繫結來呈現 DataGrid。這是顯而易見的,因為 DataGrid 需要重新整理資料進行顯示。如果是首頁回傳,則情況就不同了,因為單擊了頁面上的另一個控制項(例如某按鈕)。這種情況下,DataGrid 不綁定到資料並且必須從檢視狀態進行重建。(如果禁用了檢視狀態,就是另外一種情況了,這時只能通過資料繫結顯示網格。)

資料來源不儲存在檢視狀態中。作為複合控制項,DataGrid 包含子控制項,其中每個子控制項都將自己的狀態儲存到檢視狀態並從檢視狀態恢複。DataGrid 只需跟蹤在所有行和所包含控制項從檢視狀態恢複之前它所必須重複執行的次數。此次數與所顯示繫結項目的數量一致,並且必須作為控制項狀態的一部分儲存到檢視狀態中。在 ASP.NET 1.x 中,您必須自己學習並實現此模式。在 ASP.NET 2.0 中,從新類 CompositeDataBoundControl 派生您的複合控制項就可以了。

讓我們嘗試使用一種顯示可擴充資料繫結新聞標題列的網格類控制項。在此過程中,我們將再度使用在前文中論及的 Headline 控制項。

public class HeadlineListEx :CompositeDataBoundControl{  :}

HeadlineListEx 控制項包含了一個收集了所有綁定資料項目的 Items 集合屬性。該集合為公用集合,並且可在與多數清單控制項一起運行時通過編程方式填充。對典型資料繫結的支援是通過一對屬性(DataTextFieldDataTitleField)實現的。這兩個屬性工作表明了資料來源中將用於填充新聞標題和文本的欄位。Items 集合被儲存到檢視狀態中。

要將 HeadlineListEx 控制項轉換為真正的複合控制項,您首先需要從 CompositeDataBoundControl 將其派生出來,然後再替換 CreateChildControls。有意思的是,你會注意到 CreateChildControls 是重載方法。

override int CreateChildControls()override int CreateChildControls(IEnumerable data, bool dataBinding)

第一個重載方法替換了在 Control 類中定義的方法。第二個重載方法是每個複合控制項都必須替換的一種抽象方法。實際上,複合控制項的開發工作簡化為兩大主要任務:

  • 替換 CreateChildControls

  • 實現 Rows 集合屬性以跟蹤控制項的所有構成項。

Rows 屬性不同於 Items,因為它不儲存在檢視狀態中,且具有與請求相同的生存期,並引用協助程式對象而不是綁定資料項目。

public virtual HeadlineRowCollection Rows{get    {if (_rows == null)_rows = new HeadlineRowCollection();return _rows;     }}

Rows 集合在控制項產生時填充。讓我們看一下 CreateChildControls 的替換方法。該方法採用了兩個參數:繫結項目和一個布爾標記,其中布爾標記用於指明該控制項是通過資料繫結建立還是通過檢視狀態建立。(請注意樣本程式檔案中的程式員注釋使用的是英文,本文中將其譯為中文是為了便於參考。)

override int CreateChildControls(IEnumerable dataSource, bool dataBinding){if (dataBinding)   {string textField = DataTextField;string titleField = DataTitleField;if (dataSource != null)      {foreach (object o in dataSource)         {HeadlineItem elem = new HeadlineItem();elem.Text = DataBinder.GetPropertyValue(o, textField, null);elem.Title = DataBinder.GetPropertyValue(o, titleField, null);Items.Add(elem);         }      }   } // 開始產生控制項階層Table t = new Table();Controls.Add(t);Rows.Clear();int itemCount = 0;foreach(HeadlineItem item in Items)   {HeadlineRowType type = HeadlineRowType.Simple;HeadlineRow row = CreateHeadlineRow(t, type, item, itemCount, dataBinding);_rows.Add(row);itemCount++;    }return itemCount;}

在資料繫結的情況下,首先要填充 Items 集合。遍曆綁定集合,提取資料,然後填充 HeadlineItem 類的建立執行個體。接下來,遍曆 Items 集合(該集合中可能包含以編程方式添加的附加項),並在控制項中建立行。

HeadlineRow CreateHeadlineRow(Table t, HeadlineRowType rowType, HeadlineItem dataItem, int index, bool dataBinding){// 為最外部表格建立新行HeadlineRow row = new HeadlineRow(rowType);// 為子控制項建立儲存格TableCell cell = new TableCell();row.Cells.Add(cell);Headline item = new Headline();cell.Controls.Add(item);// 此時引發 HeadlineRowCreated 事件// 將此行添加到所建立的 HTML 表t.Rows.Add(row);// 處理資料對象綁定if (dataBinding)   {row.DataItem = dataItem;Headline ctl = (Headline) cell.Controls[0];ctl.Text = dataItem.Text;ctl.Title = dataItem.Title;                // 此時引發 HeadlineRowDataBound 事件    }return row;}

CreateHeadlineRow 方法會建立並返回 HeadlineRow 類(從 TableRow 派生而來)的一個執行個體。在這種情況下,此行會包含一個由 Headline 控制項填充的儲存格。在其他情況下,您可以更改此部分代碼以根據需要添加多個儲存格並相應填充內容。

重要的是,要將所需完成的任務分為兩個不同的步驟:建立和資料繫結。首先,建立行的布局,引發行建立事件(如果有),並最後將其添加到父表中。接下來,如果要將控制項綁定到資料,則設定對綁定資料敏感的子控制項屬性。完成操作後,則引發一個行資料繫結事件(如果有)。

請注意,該模式更準確描述了 ASP.NET 內建複合控制項的內部體繫結構。

可以使用以下代碼來引發事件。

HeadlineRowEventArgs e = new HeadlineRowEventArgs();e.DataItem = dataItem;e.RowIndex = index;e.RowType = rowType;e.Item = row;OnHeadlineRowDataBound(e);

請注意,只在要引發資料繫結事件時才設定 DataItem 屬性。事件數目據結構被任意設定為以下形式。如果您認為有必要,盡可以對其變更。

public class HeadlineRowEventArgs :EventArgs{public HeadlineItem DataItem;public HeadlineRowType RowType;public int RowIndex;public HeadlineRow Item;}

若要實際引發一個事件,通常的做法是使用一個如下定義的受保護方法。

protected virtual void OnHeadlineRowDataBound(HeadlineRowEventArgs e){if (HeadlineRowDataBound != null)HeadlineRowDataBound(this, e);}

若要聲明此事件,可在 ASP.NET 2.0 中使用新的一般事件處理常式委託。

public event EventHandler<HeadlineRowEventArgs> HeadlineRowDataBound;

在樣本頁中,一切均照常執行。您可在控制項標記上定義處理常式並將某方法寫入代碼檔案。樣本如下。

<cc1:HeadlineListEx runat="server" ID="HeadlineListEx1" DataTextField="notes" DataTitleField="lastname" DataSourceID="MySource" OnHeadlineRowDataBound="HeadlineRowCreated" />

HeadlineRowCreated 事件處理常式的代碼顯示如下。

protected void HeadlineRowCreated(object sender, HeadlineRowEventArgs e){if (e.DataItem.Title.Contains("Doe"))e.Item.BackColor = Color.Red;}

圖 7:運行中的 HeadlineListEx 控制項

通過掛接資料繫結事件,所有含有 Doe 的項都將以紅色背景呈現。

返回頁首 結論

複合控制項是通過將其他控制項彙總在某一公用 API 頂下建立而成的控制項。複合控制項將保留自己子控制項的活動執行個體,並且不僅限於呈現這些執行個體。通過檢查頁面跟蹤輸出中的控制項樹部分,您就可以很容易看到這一點。使用複合控制項可以帶來幾點好處,例如可以簡化對事件和回傳的處理。在 ASP.NET 1.x 中產生複雜的資料繫結控制項有點棘手,需要您深入瞭解一些實現細節。在引進 CompositeDataBoundControl 基類的情況下,這種複雜性在 ASP.NET 中基本可以迎刃而解。最後,如果在 ASP.NET 2.0 中需要非資料繫結的複合控制項,則可以使用 CompositeControl 基類。對於資料繫結複合控制項,則可以改為考慮 CompositeDataBoundControl。無論是哪種情況,您都必須提供一個 CreateChildControls 的有效替換方法,這是所有複合控制項的核心,用於建立子控制項階層。

作者簡介

Dino Esposito 是 Solid Quality Learning 顧問,並且是 "Programming Microsoft ASP.NET 2.0" (Microsoft Press, 2005)(英文)的作者。Dino 居住在意大利,經常就世界範圍的行業事件發表評論。請通過 cutting@microsoft.com 或者加入部落格 http://weblogs.asp.net/despos 與其聯絡。

轉到原英文頁面

返回頁首
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.