asp.net WebForm之使用者自定義控制元件
關於使用者自定義控制元件,想必大家已經非常熟悉了。雖然說經常用過,但是隻是簡單的使用而已。在這裡再次總結一下Asp.net中的UserControl,以便下次使用時
能夠得心應手。本文將會介紹以下內容:
1,什麼是UserControl?
2,如何定義一個UserControl?
3,如何使用UserControl?
4,如何通過UserControl屬性來控制html?
5,如何實現<u1:Control>string</u1:Control>?
1,什麼是UserControl?
關於UserControl的解釋MSDN,跟Wikipedia都有介紹:
說白了,UserControl的存在就是為了重用html程式碼。有點類似php的include或者require,但是它比include或require更加靈活,它不當只是
單純的引入程式碼,而且通過設定UserControl的屬性來對html程式碼進行控制,從而更好的實現程式碼複用。基本UserControl的使用方法更aspx頁面
是一樣的,但是UserControl不可以通過url來訪問,只能在頁面或者其它使用者控制元件中訪問。
2,如何定義一個簡單UserControl?
新建UserControl方法:右鍵asp.net web專案->新增->新增新項->Web->Web使用者控制元件。開啟控制元件的後臺程式碼,我們可以看到,控制元件
繼承於System.Web.UI.UserControl類。新建好的控制元件除了字尾名更asp.net不同之外,其它結構都一樣,用法也基本一致。
這是,你就可以在ascx檔案新增html程式碼了。
3,如何使用UserControl?
在頁面中使用UserControl只需要在頁面的頭部新增Register程式碼段:
<%@ Register src="UserControl/UC_Demo.ascx" tagname="Demo" tagprefix="uc1" %>
src表示使用者控制元件所在的相對路徑。
新增完上述程式碼段之後,就可以你需要使用的地方使用了。
使用方法如下,我們可以看到uc1就是上面定義的tagprefix,Demo就是上面定義的tagname,由於使用者控制元件也是一種服務端控制元件
因此我們這裡必須加上runat="server",否則.net會認為是html標籤。
<uc1:Demo ID="aaa" runat="server">
</uc1:Demo>
4,如何自定義屬性來控制UserControl的html?
第2,3部分已經介紹瞭如何新建以及使用一個簡單的使用者控制元件。但是,上面的例子只能滿足對於簡單html的複用,比如說一些
頭部資訊或底部資訊。但對於一些複雜的模組,顯然我們需要一些屬性來控制它。比如說:我們定義一個使用者控制元件,
該控制元件需要使用者可以自定義主題。即使用者希望能夠這樣的使用:
<uc1:Demo ID="aaa" ThemeName="green" runat="server">
</uc1:Demo>
那麼該怎麼實現呢?首先,我們的第一印象就是,Theme應該是一個enum型別,可供使用者選擇,因此,我們首先定義一個Theme的列舉
public enum Theme
{
Brown = 10,
Cyan = 20,
Gray = 30,
Green = 40,
Leaf = 50,
Plain = 60,
Purple = 70
}
OK,列舉有了,那麼我們應該如何將該列舉變成UserControl中可設定的選項呢?在UserControl的後臺程式碼中(即ascx.cs檔案),
我們只需為該控制元件類定義一個public的共有變數即可。因此,我們只要在後臺類檔案中,新增如下程式碼就可以實現上述需要:
public Theme ThemeName;
我們可以通過私有成員來設定初始值,這時候程式碼就變成這樣:
private Theme _ThemeName = Theme.Cyan;
public Theme ThemeName
{
get { return this._ThemeName; }
set { _ThemeName = value; }
}
此時,使用者可以通過屬性來設定Theme了,Theme在頁面中設定後,使用者控制元件就可以通過Theme來控制它的html程式碼中的樣式了。
5,如何實現<u1:Control>string</u1:Control>?
這個是本文中要講的重點。在使用使用者控制元件的過程中,有時候是一個模組(如下圖),我只需要重用這個框,而這個框裡的內容
是要自定義的。那麼我們要怎麼辦呢?首先我們想到的是屬性,但是我們不可能將一大串的html程式碼作為屬性傳遞過去,
這樣程式碼就太噁心了。因此我們就想到了標題的那種方式。將html夾在控制元件中間。還是接著上面所說的例子,這時,我們希望
使用者可以這樣呼叫:
<uc1:Demo ID="aaa" ThemeName="green" runat="server">
<div style="width:100px;height:100px;backgroud:red;">
hello world!
</div>
</uc1:Demo>
通過google,我找到了解決辦法。asp.net提供了ITextControl介面,通過該介面我們就可以實現上述的功能。因此,我們需要做的
就是:第一步,讓UserControl實現ITextControl介面。第二步,實現ITextControl的Text欄位。第三步,重寫Control類中的AddParsedSubObject方法。
假設我們有好幾個這樣的控制元件,因此,我們將以上三步實現的內容抽象到一個類中,我們暫且叫做DemoWidget,程式碼如下:
[ParseChildren(false)]
public class DemoWidget : System.Web.UI.UserControl, ITextControl
{
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public virtual string Text
{
get
{
object obj2 = this.ViewState["Text"];
if (obj2 != null)
{
return (string)obj2;
}
return string.Empty;
}
set
{
if (this.HasControls())
{
this.Controls.Clear();
}
this.ViewState["Text"] = value;
}
}
protected override void AddParsedSubObject(object obj)
{
if (obj is LiteralControl)
{
HtmlContent.Append(((LiteralControl)obj).Text);
this.Text = HtmlContent.ToString();
}
else
{
if (obj != null)
{
HtmlContent.Append(GetControlHtml(obj as Control));
this.Text = HtmlContent.ToString();
}
}
}
protected StringBuilder HtmlContent = new StringBuilder();
protected string GetControlHtml(Control ctl)
{
StringBuilder sb = new StringBuilder();
StringWriter tw = new StringWriter();
HtmlTextWriter writer = new HtmlTextWriter(tw);
ctl.RenderControl(writer);
sb.Append(writer.InnerWriter.ToString());
return sb.ToString();
//base.AddParsedSubObject(new LiteralControl(tmpStr));
//this.Text = tmpStr;
}
}
我們來看一下,DemoWidget是如何實現上述的三個步驟的。很明顯第一步已經完成。
第二步我們可以看到也實現了ITextControl的Text欄位,Text欄位就是用於儲存夾在使用者控制元件中間的文字,從程式碼中我們可以看到,我將這些文字資訊儲存到viewstate中。
而Text上方的屬性[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
又表示什麼呢?msdn的解釋如下:
InnerDefaultProperty 指定屬性在 ASP.NET 伺服器控制元件中保持為內部文字。還指示將該屬性定義為元素的預設屬性。只能指定一個屬性為預設屬性。
(http://msdn.microsoft.com/zh-cn/library/system.web.ui.persistencemode.aspx)
也就是說,Text將作為DemoWidget的預設屬性使用,而且只能有一個這樣的屬性。
第三步,重寫AddParsedSubObject方法
為什麼要重新AddParsedSubObject方法呢?我們首先新增一個空的AddParsedSubObject方法,設定一個斷點,然後除錯,我們可以發現,當DemoWidget中間有文字時,
都會呼叫該函式,因此,這裡我們可以將html賦值給Text。預設,傳遞進該函式的物件是LiteralControl型別的。因此我們實現如下程式碼:
該方法的具體解釋請檢視msdn:http://msdn.microsoft.com/zh-cn/library/system.web.ui.control.addparsedsubobject.aspx
protected override void AddParsedSubObject(object obj)
{
if (obj is LiteralControl)
{
this.Text = ((LiteralControl)obj).Text;
}
}
這裡還需要注意的是:我們必須為DemoWidget類新增屬性[ParseChildren(false)],ParseChildren屬性表示是否將伺服器控制元件標記內的元素解釋為屬性,因此,這裡應該為false。
http://msdn.microsoft.com/zh-cn/library/system.web.ui.parsechildrenattribute.aspx
好了,之前提到的三步我們都已經完成了,可是我發現類還有其它程式碼,其它程式碼又是幹嘛的呢?此時,我們發現如果剛剛三步雖實現了一開始提出的需求,
但是,上述類還有一定的侷限性,就是控制元件標記內只能包含html文字,當控制元件標記內需要包含另外一個控制元件的時候就有問題了。
<uc1:Demo ID="aaa" ThemeName="green" runat="server">
<div style="width:100px;height:100px;backgroud:red;">
hello world!
</div>
<uc1:Demo ID="bbb" ThemeName="Leaf" runat="server">
<div style="width:100px;height:100px;backgroud:red;">
hello world!
</div>
</uc1:Demo>
</uc1:Demo>
由於控制元件標記中包含了其它控制元件,因此,AddParsedSubObject中的引數obj就有可能是子控制元件型別,因此我們必須修改AddParsedSubObject函式,並新建HtmlContent成員,用於儲存子控制元件的html程式碼
然後再將該子控制元件生成的html程式碼賦值給控制元件的Text屬性,GetControlHtml方法就是用於獲取子控制元件生成的html程式碼。而AddParsedSubObject則變成如下所示:
protected override void AddParsedSubObject(object obj)
{
if (obj is LiteralControl)
{
HtmlContent.Append(((LiteralControl)obj).Text);
this.Text = HtmlContent.ToString();
}
else
{
if (obj != null)
{
HtmlContent.Append(GetControlHtml(obj as Control));
this.Text = HtmlContent.ToString();
}
}
}
這時,你就可以隨意在UserControl標籤裡新增任何東西了,你可以新增html程式碼,也可以新增自定義控制元件,甚至還可以新增asp.net服務端控制元件。