1. 程式人生 > >asp.net WebForm之使用者自定義控制元件

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服務端控制元件。