UserControl 用户自定义控件
关于用户自定义控件,想必大家已经非常熟悉了。虽然说经常用过,但是只是简单的使用而已。在这里再次总结一下Asp.net中的UserControl,以便下次使用时
能够得心应手。本文将会介绍以下内容:
1,什么是UserControl?
2,如何定义一个UserControl?
3,如何使用UserControl?
4,如何通过UserControl属性来控制html?
5,如何实现<u1:Control>string</u1:Control>?
1,什么是UserControl?
关于UserControl的解释MSDN,跟Wikipedia都有介绍:
http://msdn.microsoft.com/en-us/library/system.web.ui.usercontrol.aspx
http://en.wikipedia.org/wiki/ASP.NET#User_controls
说白了,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服务端控件。
嗯,关于UserControl的内容就介绍到这里,有什么讲得不对的地方请大家指正,同时也期望得到大家的鼓励。
这里是源代码。