ASP.NET2.0服務器控件之復合控件樣式
源代碼下載
為了設置復合控件的外觀,復合控件必須提供一些樣式屬性,尤其是針對子控件的樣式屬性。在本文中,我們將重點介紹為復合控件實現樣式屬性的兩種方法。
1、上傳部分樣式屬性
在為復合控件實現樣式屬性之前,讀者應首先了解"樣式冒泡"的基本概念。樣式冒泡多用于實現復合控件的樣式屬性。由于在復合控件中包含多個子控件,因此,這些子控件的樣式屬性可能在一定情況下,干擾復合控件的樣式屬性,引起樣式屬性混亂。為了更加明確的定義復合控件的樣式屬性,可以采取將子控件的樣式屬性上傳為頂級樣式屬性的方法,這就是所謂的"樣式冒泡"。
通常情況下,開發人員可能面對兩種情況:一種是上傳子控件中少數樣式屬性,另一種是上傳子控件中所有樣式屬性。本小節只介紹針對第一種情況的實現方法,而另外一種將在后面一節中進行講解。
本節說明的這種實現樣式屬性的方法,其關鍵是通過為子控件的Attributes指定鍵/值對,引入樣式屬性,由此將子控件的樣式屬性上傳為復合控件頂級屬性。為了方便讀者理解這一方法,下面列舉了一個典型應用。
在本示例中,實現了一個復合控件MyControl,其子控件集合中包括一個Table控件。當前,需要將Table子控件的樣式屬性CellPadding和Border上傳為MyControl的頂級樣式屬性。具體源代碼如下所示。
以上代碼顯示了MyControl的一些關鍵源代碼,其重點在于說明實現部分樣式屬性冒泡的關鍵步驟。(1)初始化頂級樣式屬性的字段,如果有必要可以定義初始值。(2)定義與需要升級的子控件的樣式屬性相同名稱的屬性。上面的代碼中定義了屬性CellPadding和Border。(3)在子控件的Attributes的鍵/值對中引入第2步中定義的屬性。
當設置MyControl中的樣式屬性CellPadding和Border的屬性值時,實際是設置Table子控件的CellPadding和Border的屬性值。通過以上3個關鍵步驟就可實現樣式冒泡。
如果讀者仔細觀察可以發現,以上介紹的這種實現樣式冒泡的方法存在一些問題:一、這種方法只適用于升級子控件中少數樣式屬性。如果需要將子控件的所有樣式屬性都升級,而仍然使用這種方法,則實現起來非常繁瑣,容易產生錯誤。二、所實現的樣式屬性缺乏邏輯性和組織性。在某種情況下,例如,多個子控件的同一樣式屬性都需要升級為頂級屬性,這時使用該方法將會引起混亂。
為了解決這些問題,下面介紹一種上傳子控件全部樣式屬性的實現方法。
2、上傳全部樣式屬性
在上一節中,說明了有關實現復合控件樣式的內容,但是,那種實現方法只能實現子控件部分的樣式,并且缺乏邏輯性和組織性。本小節介紹的實現復合控件樣式屬性的方法有效避免了以上問題。它實現了多重委托的屬性,即對每個子控件分別定義Width、Height等樣式,更進一步的講,即實現每個子控件對應的Style類型的復雜樣式屬性,例如,TextBoxStyle、ButtonStyle。通過這種方式子控件的樣式屬性就上傳為頂層屬性,以便于設置子控件的外觀。
顯而易見,實現子控件的樣式屬性上傳的關鍵是實現Style類型的復雜樣式屬性。為此,開發人員必須為復雜樣式屬性提供自定義視圖狀態管理。需要讀者注意的是,復合控件的視圖狀態與普通控件視圖狀態有所不同。由于復合控件包含子控件,因此,相應的視圖狀態中既包括父控件的視圖狀態,也包括子控件對應的復雜樣式屬性的視圖狀態。例如,上文實例中控件的視圖狀態即包含3個部分:父控件自身的視圖狀態、ButtonStyle的視圖狀態和TextBoxStyle的視圖狀態。除此之外,具體的實現過程與實現普通的復雜屬性基本一致。
不知讀者是否還記得上一篇文章中的那個復合控件,即由一個文本框TextBox和一個按鈕Button組成的復合控件CompositeEvent。在此,我們對該控件添加設置了控件的頂層樣式屬性ButtonStyle和TextBoxStyle。下面列舉了控件類CompositeEvent的源代碼。
如果讀者看過前面的文章,那么應該對以上代碼不陌生。限于篇幅,本文不對先前說明過的內容進行講解,而重點說明有關樣式冒泡的內容。
與樣式冒泡相關的內容可以分成三個部分:一是定義Style類型的復雜樣式屬性:ButtonStyle和TextBoxStyle;二是在Render方法中為子控件應用復雜樣式屬性;三是實現復雜樣式屬性的自定義視圖狀態管理部分。以上三個部分的實現,實際是實現子控件樣式上傳過程中最為關鍵的三個步驟。前兩個部分的實現比較簡單,在此就不多加說明。下面重點說明最后一個部分的實現。
第三部分主要實現復雜樣式屬性的自定義狀態管理。在TrackViewState方法中,分別對基類、_textBoxStyle和_buttonStyle調用TrackViewState。在SaveViewState方法中,首先定義一個myState對象數組,然后按順序將基類、TextBox和Button的視圖狀態數據保存到myState中并返回。在LoadViewState方法中,實現將所保存的狀態數據(savedState)的第一部分加載入基類,第二部分加載給TextBoxStyle,第三部分加載給ButtonStyle,之所以按照如此順序加載是與SaveViewState方法中的保存順序對應的。
下面是CompositeEvent控件的應用代碼片斷。請讀者注意的是:ButtonStyle和TextBoxStyle都是作為內部嵌套形式屬性而標記,用戶通過設置樣式屬性即可完成對子控件的外觀設置。
下面列舉了示例應用效果圖。
如圖1所示,復合控件中的文本框和按鈕外觀由ButtonStyle和TextBoxStyle屬性設置。讀者可以如同設置單個控件的樣式那樣,設置文本框和按鈕的樣式。當然,這些屬性必須包含在<ButtonStyle>和<TextBoxStyle>標記中。這是必須牢記的。
3、小結
本章介紹了為復合控件實現樣式屬性的具體方法,其中包括上傳部分樣式屬性和上傳全部樣式屬性等。對于開發人員而言,需要根據具體情況選擇使用不同的方法。通常而言,對于簡單的復合控件,建議使用上傳部分樣式屬性的方法;而對于功能復雜的復合控件而言,則建議使用上傳全部樣式屬性。
為了設置復合控件的外觀,復合控件必須提供一些樣式屬性,尤其是針對子控件的樣式屬性。在本文中,我們將重點介紹為復合控件實現樣式屬性的兩種方法。
1、上傳部分樣式屬性
在為復合控件實現樣式屬性之前,讀者應首先了解"樣式冒泡"的基本概念。樣式冒泡多用于實現復合控件的樣式屬性。由于在復合控件中包含多個子控件,因此,這些子控件的樣式屬性可能在一定情況下,干擾復合控件的樣式屬性,引起樣式屬性混亂。為了更加明確的定義復合控件的樣式屬性,可以采取將子控件的樣式屬性上傳為頂級樣式屬性的方法,這就是所謂的"樣式冒泡"。
通常情況下,開發人員可能面對兩種情況:一種是上傳子控件中少數樣式屬性,另一種是上傳子控件中所有樣式屬性。本小節只介紹針對第一種情況的實現方法,而另外一種將在后面一節中進行講解。
本節說明的這種實現樣式屬性的方法,其關鍵是通過為子控件的Attributes指定鍵/值對,引入樣式屬性,由此將子控件的樣式屬性上傳為復合控件頂級屬性。為了方便讀者理解這一方法,下面列舉了一個典型應用。
在本示例中,實現了一個復合控件MyControl,其子控件集合中包括一個Table控件。當前,需要將Table子控件的樣式屬性CellPadding和Border上傳為MyControl的頂級樣式屬性。具體源代碼如下所示。
| public class MyControl : CompositeControl{ // 相關代碼 ...... // 定義初始值 private int _cellPadding = 0; private int _border = 1; ...... // 定義樣式屬性,它和Table控件的樣式屬性CellPadding和Border類似 public int CellPadding{ get { return _cellPadding; } set { _cellPadding = value; } // 實現屬性Border public int Border{ get { return _border; } set { _border = value; } ...... // 重寫CreateChildControls方法 protected override void CreateChildControls() { //相關代碼 ...... Table t = new Table(); //將前面定義的屬性添加到鍵/值對中 t.AddAttributes.Add("CellPadding",_cellPadding.ToString()); t.AddAttributes.Add("Border",_border.ToString()); ...... } } |
以上代碼顯示了MyControl的一些關鍵源代碼,其重點在于說明實現部分樣式屬性冒泡的關鍵步驟。(1)初始化頂級樣式屬性的字段,如果有必要可以定義初始值。(2)定義與需要升級的子控件的樣式屬性相同名稱的屬性。上面的代碼中定義了屬性CellPadding和Border。(3)在子控件的Attributes的鍵/值對中引入第2步中定義的屬性。
當設置MyControl中的樣式屬性CellPadding和Border的屬性值時,實際是設置Table子控件的CellPadding和Border的屬性值。通過以上3個關鍵步驟就可實現樣式冒泡。
如果讀者仔細觀察可以發現,以上介紹的這種實現樣式冒泡的方法存在一些問題:一、這種方法只適用于升級子控件中少數樣式屬性。如果需要將子控件的所有樣式屬性都升級,而仍然使用這種方法,則實現起來非常繁瑣,容易產生錯誤。二、所實現的樣式屬性缺乏邏輯性和組織性。在某種情況下,例如,多個子控件的同一樣式屬性都需要升級為頂級屬性,這時使用該方法將會引起混亂。
為了解決這些問題,下面介紹一種上傳子控件全部樣式屬性的實現方法。
2、上傳全部樣式屬性
在上一節中,說明了有關實現復合控件樣式的內容,但是,那種實現方法只能實現子控件部分的樣式,并且缺乏邏輯性和組織性。本小節介紹的實現復合控件樣式屬性的方法有效避免了以上問題。它實現了多重委托的屬性,即對每個子控件分別定義Width、Height等樣式,更進一步的講,即實現每個子控件對應的Style類型的復雜樣式屬性,例如,TextBoxStyle、ButtonStyle。通過這種方式子控件的樣式屬性就上傳為頂層屬性,以便于設置子控件的外觀。
顯而易見,實現子控件的樣式屬性上傳的關鍵是實現Style類型的復雜樣式屬性。為此,開發人員必須為復雜樣式屬性提供自定義視圖狀態管理。需要讀者注意的是,復合控件的視圖狀態與普通控件視圖狀態有所不同。由于復合控件包含子控件,因此,相應的視圖狀態中既包括父控件的視圖狀態,也包括子控件對應的復雜樣式屬性的視圖狀態。例如,上文實例中控件的視圖狀態即包含3個部分:父控件自身的視圖狀態、ButtonStyle的視圖狀態和TextBoxStyle的視圖狀態。除此之外,具體的實現過程與實現普通的復雜屬性基本一致。
不知讀者是否還記得上一篇文章中的那個復合控件,即由一個文本框TextBox和一個按鈕Button組成的復合控件CompositeEvent。在此,我們對該控件添加設置了控件的頂層樣式屬性ButtonStyle和TextBoxStyle。下面列舉了控件類CompositeEvent的源代碼。
| 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(); //聲明樣式變量 private Style _buttonStyle; private Style _textBoxStyle; //定義屬性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; } } // 定義ButtonStyle屬性 [ Category("Style"), Description("Button的樣式屬性"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerDefaultProperty) ] public virtual Style ButtonStyle { get { if (_buttonStyle == null) { _buttonStyle = new Style(); if (IsTrackingViewState) { ((IStateManager)_buttonStyle).TrackViewState(); } } return _buttonStyle; } } //定義TextStyle屬性 [ Category("Style"), Description("設置TextBox的樣式屬性"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty) ] public virtual Style TextBoxStyle { get { if (_textBoxStyle == null) { _textBoxStyle = new Style(); if (IsTrackingViewState) { ((IStateManager)_textBoxStyle).TrackViewState(); } } return _textBoxStyle; } } // 實現事件屬性結構 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); } } // 重寫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); _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) { AddAttributesToRender(output); if (_textBoxStyle != null) { _textBox.ApplyStyle(TextBoxStyle); } if (_buttonStyle != null) { _button.ApplyStyle(ButtonStyle); } 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(); } //復雜樣式屬性的狀態管理,重寫3個相關方法LoadViewState、SaveViewState、TrackViewState protected override void LoadViewState(object savedState) { if (savedState == null) { base.LoadViewState(null); return; } if (savedState != null) { object[] myState = (object[])savedState; if (myState.Length != 3) { throw new ArgumentException("無效的ViewState"); } base.LoadViewState(myState[0]); if (myState[1] != null) { ((IStateManager)TextBoxStyle).LoadViewState(myState[1]); } if (myState[2] != null) { ((IStateManager)ButtonStyle).LoadViewState(myState[2]); } } } protected override object SaveViewState() { object[] myState = new object[3]; myState[0] = base.SaveViewState(); myState[1] = (_textBoxStyle != null) ? ((IStateManager)_textBoxStyle).SaveViewState() : null; myState[2] = (_buttonStyle != null) ? ((IStateManager)_buttonStyle).SaveViewState() : null; for (int i = 0; i < 3; i++) { if (myState[i] != null) { return myState; } } return null; } protected override void TrackViewState() { base.TrackViewState(); if (_buttonStyle != null) { ((IStateManager)_buttonStyle).TrackViewState(); } if (_textBoxStyle != null) { ((IStateManager)_textBoxStyle).TrackViewState(); } } } } |
如果讀者看過前面的文章,那么應該對以上代碼不陌生。限于篇幅,本文不對先前說明過的內容進行講解,而重點說明有關樣式冒泡的內容。
與樣式冒泡相關的內容可以分成三個部分:一是定義Style類型的復雜樣式屬性:ButtonStyle和TextBoxStyle;二是在Render方法中為子控件應用復雜樣式屬性;三是實現復雜樣式屬性的自定義視圖狀態管理部分。以上三個部分的實現,實際是實現子控件樣式上傳過程中最為關鍵的三個步驟。前兩個部分的實現比較簡單,在此就不多加說明。下面重點說明最后一個部分的實現。
第三部分主要實現復雜樣式屬性的自定義狀態管理。在TrackViewState方法中,分別對基類、_textBoxStyle和_buttonStyle調用TrackViewState。在SaveViewState方法中,首先定義一個myState對象數組,然后按順序將基類、TextBox和Button的視圖狀態數據保存到myState中并返回。在LoadViewState方法中,實現將所保存的狀態數據(savedState)的第一部分加載入基類,第二部分加載給TextBoxStyle,第三部分加載給ButtonStyle,之所以按照如此順序加載是與SaveViewState方法中的保存順序對應的。
下面是CompositeEvent控件的應用代碼片斷。請讀者注意的是:ButtonStyle和TextBoxStyle都是作為內部嵌套形式屬性而標記,用戶通過設置樣式屬性即可完成對子控件的外觀設置。
| <%@ 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"> <TextBoxStyle Width="198px" Height="20px" BorderWidth="1px" BackColor="orange"> </TextBoxStyle> <ButtonStyle Width="84px" Height="24px" BorderWidth="1px" BorderStyle="dotted"></ButtonStyle> </Sample:CompositeEvent> <br /> <asp:Label ID="lbMessage" runat="server"> </asp:Label> </div> </form> </body> </html> |
下面列舉了示例應用效果圖。
![]() 圖1 效果圖 |
如圖1所示,復合控件中的文本框和按鈕外觀由ButtonStyle和TextBoxStyle屬性設置。讀者可以如同設置單個控件的樣式那樣,設置文本框和按鈕的樣式。當然,這些屬性必須包含在<ButtonStyle>和<TextBoxStyle>標記中。這是必須牢記的。
3、小結
本章介紹了為復合控件實現樣式屬性的具體方法,其中包括上傳部分樣式屬性和上傳全部樣式屬性等。對于開發人員而言,需要根據具體情況選擇使用不同的方法。通常而言,對于簡單的復合控件,建議使用上傳部分樣式屬性的方法;而對于功能復雜的復合控件而言,則建議使用上傳全部樣式屬性。
