自定义服务器控件(扩展现有 Web 控件)
?????? 很多情況下,你并不需要從頭開始創(chuàng)建一個新控件。有些功能也許在 ASP.NET 的 Web 控件的基本集合中已經(jīng)存在了。所有這些控件都是普通類,你可以組合它們(使用其他類的實例來創(chuàng)建一個新類)或者繼承它們(擴展一個現(xiàn)存類和改變它的功能來創(chuàng)建一個新類)。
??????
組合控件
?????? ASP.NET 提供了這樣一個功能,即允許你從其他現(xiàn)存的 Web 控件構建你的控件類。
?????? 基本的技巧是從類 System.Web.UI.WebControls.CompositeControl (它自身繼承 WebControl)派生一個新類。然后,必須覆蓋 CreateChildControls()方法來添加子控件。此時,你可以創(chuàng)建一個或多個控件對象,設置它們的屬性和事件處理程序,最后把它們加入到當前控件的 Controls 集合中。這種方式最大的優(yōu)點在于你根本不需要定制呈現(xiàn)代碼,呈現(xiàn)工作被委托給了作為組成部件的服務器控件,你也不需要擔心觸發(fā)回傳和獲取回傳數(shù)據(jù)等細節(jié),因為子控件自身會處理這些細節(jié)。
?
?????? 下面創(chuàng)建一個 TitledTextBox 控件,這個控件組合一個 Lable 和 一個 TextBox:
public class TitledTextBox : CompositeControl { ... }?????? CompositeControl 控件實現(xiàn)了 INamingContainer 接口。這只是個標記接口,沒有任何方法,它的作用僅僅是告訴 ASP.NET 保證所有的子控件擁有唯一的 ID 值。(ASP.NET 通過在控件的 ID 之前附加服務器控件的 ID 來實現(xiàn)這一點,這確保了不會有任何命名沖突,即使你的頁面擁有不止一個 TitledTextBox 控件實例)。
?
?????? 為了簡單起見,應該使用成員變量跟蹤構成控件。這使得控件里的任何方法都能方便的訪問成員控件。但此時還不應該創(chuàng)建這些控件:
protected Label label; protected TextBox textBox;?
?????? 網(wǎng)頁無法直接訪問這些控件中的任何一個。如果允許訪問某個屬性,在控件類中必須添加屬性:
public string Title { get { return (string)ViewState["Title"]; } set { ViewState["Title"] = value; } } ? public string Text { get { return (string)ViewState["Text"]; } set { ViewState["Text"] = value; } }?????? 注意:這些屬性只是在視圖狀態(tài)里存儲信息,而不是直接訪問子控件。這是因為子控件也許還不存在。這些屬性將會在 CreateChildControls()方法里被應用到子控件上。
?????? 所有的控件都在 <span> 標簽里被呈現(xiàn),這樣做很有效。這保證了如果網(wǎng)頁針對 TitledTextBox 控件使用了 font、color、position 等特性時,所有的子控件都能實現(xiàn)預期的效果。
?
?????? 現(xiàn)在,可以創(chuàng)建子控件對象了。這些對象都獨立于另外一個控件對象:LiteralControl ,LiteralControl 僅表現(xiàn)為一小段 HTML 代碼,本例中 LiteralControl 包裝了兩個不換行空格:
protected override void CreateChildControls() { // Add the label. label = new Label(); label.EnableViewState = false; label.Text = Title; Controls.Add(label); ? // Add a space Controls.Add(new LiteralControl(" ")); ? // Add the text box. textBox = new TextBox(); textBox.EnableViewState = false; textBox.Text = Text; textBox.TextChanged += new EventHandler(OnTextChanged); Controls.Add(textBox); } ? public event EventHandler TextChanged; ? protected virtual void OnTextChanged(object sender, EventArgs e) { if (TextChanged != null) { TextChanged(this, e); } }?????? 查看下測試效果:
?????? 你可能會更喜歡使用 HtmlTextWriter 完全控制 HTML 的呈現(xiàn)。但是,如果你想處理回傳、事件以及創(chuàng)建復雜控件(如 GridView,或者導航助手),那么使用組合控件能極大化的簡化工作。
?
為 TitledTextBox 提供更好的設計時支持
?????? 對于這個示例還有一個值得添加的細節(jié)。如果在 CreateChildControls()方法已經(jīng)被調用來呈現(xiàn)控件之后修改了 Title 或者 Text 屬性,一定需要確保子控件被重新生成。
?????? 比如在按鈕回傳的事件中修改了這 2 個屬性后,回傳結束,頁面呈現(xiàn)時,仍能看見舊的屬性值。這是因為 CreateChildControls()方法優(yōu)先于按鈕事件執(zhí)行,因此控件屬性的值還是比較舊的 ViewState 中保存的值。此時按鈕事件執(zhí)行,對控件的 ViewState 賦予了新的值,因此這種變化需要再次回傳后才能發(fā)生。這并不是我們期待的效果和反應!
?????? 一定要有一種機制確保頁面呈現(xiàn)后,用戶修改了控件屬性時,重新創(chuàng)建子控件并更新所有的值。
?????? 下面這段代碼可以示例出這種機制(以 Title 舉例):
public string Title { get { return (string)ViewState["Title"]; } set { ViewState["Title"] = value; ? // 賦值時,如果控件已創(chuàng)建,那么控件沿用了上一次視圖狀態(tài)的值 // 因此需要重新創(chuàng)建子控件 if (this.ChildControlsCreated) { this.RecreateChildControls(); } } }?
派生控件
?????? 派生控件指從現(xiàn)存的控件類派生出一個更加專用的控件。你可以覆蓋或增加你需要的功能,而不需要重新創(chuàng)建整個控件,你可以省去很多工作。不過,此方式并非總是可行的,因為某些控件在私有方法里編寫了一些基礎設施的關鍵代碼,你無法覆蓋。
?????? 有時,你可創(chuàng)建一個派生控件以便能用某些樣式或格式化屬性預先初始化一個現(xiàn)存的控件。例如,你可以創(chuàng)建一個用于在 OnInit()方法里設置樣式的自定義控件 Calendar 或 GridView,這樣,當添加這些控件時,該實例就已經(jīng)是你所期望的樣式格式化了。
?
為特定數(shù)據(jù)創(chuàng)建標簽
?????? 創(chuàng)建自定義控件的一個普遍原因:為某些特定類型的數(shù)據(jù)而微調控件。
?????? 例如,標準的 Label 是一個靈活通用的工具,可以用來呈現(xiàn)文本內容并插入任意的 HTML 。然而,有時更需要一種能照顧到某些編碼方式的更高級的文本輸出方式。
?????? Xml 控件,它允許用一個 XSLT 樣式表在頁面里顯示 XML 內容。然而,XML 控件并未給出不使用 XSLT 樣式表先將其轉換來顯示任意的 XML 的方式。因此,加入你想復制 IE 的行為,該怎么做呢?
?????? IE 顯示了一個彩色字符編碼的 XML 標簽的樹形結構。你可以用 XSLT 樣式表實現(xiàn)這種方法。然而,另一個有趣的選擇是專門為 XML 內容創(chuàng)建一個自定義的 Label 控件,而這個 Label 控件可以自動應用你所需的格式。
?
示例介紹
?????? 首先,如果你不采取任何步驟,而試著顯示 XML 內容,所有的 XML 標簽都會被識別為無效的 HTML 標簽,因此不會被顯示,結果只會顯示一個雜亂無章的代表所有元素內容的文本塊:
?
?????? 可以通過 Server.HtmlEncode() 方法來稍微改善,但仍差強人意!所有的空白都被折疊了,所有的換行都被忽略了,導致了一個不可讀的長字符串:
?
?
自定義 XmlLabel 控件
?????? 自定義 XmlLabel 控件通過在 XML 的開始和結束標簽上應用格式化而解決了這個問題。這個功能被封裝到一個名為 ConvertXmlTextToHtmlText()的靜態(tài)方法中。之所以被實現(xiàn)為靜態(tài)方法而非實例方法,就是為了能從其他控件中調用它來格式化文本并展示。
?????? ConvertXmlTextToHtmlText()使用下面的正則表達式來查找字符串中所有 XML 標簽:
<([^>]+)> // 匹配小于號與大于號,及中間一系列非大于號的字符,遇到大于號匹配查找立即結束。
注意:
?????? 正則表達式使用的是所謂的貪婪匹配(greedy matching),這意味著總是盡可能多的進行匹配。因此這個簡單的 <.+> 表達式不可用。它將匹配文檔中第一個小于號和文檔末尾的大于號之間的所有內容。換言之,結果將只有一個匹配,這個匹配將所有內嵌的匹配都混為一談。
?
?????? 一旦找到了一個匹配,下一步就是用你想要的文本替換這個匹配文本。替換表達式如下:
<<b>$1</b>>
?????? 用 HTML 實體字符來替換掉小于號和大于號。中間的文本用加粗來格式化。$1 是一個反向引用,它引用搜索表達式中用括號括住的文本。
?
?????? 一旦標簽顯示為粗體,最后一步就是用 字符實體替換字符串中空格,用 <br /> 替換所有換行。下面是完整的代碼:
public class XmlLabel : Label { public static string ConvertXmlTextToHtmlText(string inputText) { string startPattern = "<([^>]+)>"; Regex regEx = new Regex(startPattern); string outputText = regEx.Replace(inputText, "<<b>$1</b>>"); ? outputText = outputText.Replace(" ", " "); outputText = outputText.Replace("\r\n", "<br />"); return outputText; } ? protected override void RenderContents(HtmlTextWriter output) { string xmlText = ConvertXmlTextToHtmlText(Text); output.Write(xmlText); } }?????? 這里沒有調用 RenderContents()的基本實現(xiàn)。這是因為 XmlLabel 控件的目標是替代標簽文本的呈現(xiàn)邏輯,而非補充!
?????? 在這個框架的基礎上,可以做很多工作來完善它,包括彩色編碼和自動縮進。
?????? 也可以使用類似的技巧創(chuàng)建標簽(Label),自動把郵件地址和 URL 轉換為鏈接(以 <a> 標簽包裝),格式化多行文本成無序列表等。
總結
以上是生活随笔為你收集整理的自定义服务器控件(扩展现有 Web 控件)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2012年度IT博客大赛50强报道:贾小
- 下一篇: 第49周星期三