HTML Tags and JavaScript tutorial
Asp自定义控件(二)
又要转别人的文章,谁叫我又懒,水平又菜呢
browsable(false)表示该属性将来在VS.Net的属性窗体中不显示出来,true则为显示。
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)则表示该控件的这个属性不在asp.net的网页中显示出来,如<asp:mycontrol name='aaa'>,如果代码中的name属性前加入该选项,则在<asp:>标签中不能使用也不会显示name属性。
在前面的讲解中,控件的属性的类型都是基本类型、字符串等简单类型,这些类型我们不用做任何的额外工作,就可以把控件的属性持久化到控件的标签中(保存在aspx[Source]里),但是我们开发的控件的属性不可能全部是这样的简单类型,比如有时也会遇到属性为Size这样的复杂属性,为了使这样的属性持久化,我们还必须做其它的工作,就这是我们这章主要内容。
对复杂属性进行持久化,我们有两种方式(可以单独使用,也可以相互配合):
1、 使用TypeConvert把复杂属性转化为简单类型string,以能持久化到控件的标签中。
2、 串行化复杂属性的元数据。
上述的两种方式,并不是Asp.net控件所特有的,而是Component的特性,而Asp.net的服务器控件又是从Component派生下来的。
如果通过串行化复杂属性元数据的方式来持久化复杂属性,又有两种方式:
A、通过“属性名-子属性名”语法来表示,如:
Size-Height,Size就是复杂属性名,Height则是Size的属性,也就是Size的属性的元数据。
B、通过内部属性持久化(Inner property persistence),如:
可能读者已经注意到WebControl基类中包括的两个方法:Render和RenderContents。根据上文所介绍的内容可知,Control基类中包括Render方法。由于WebControl类继承自Control类,因此,WebControl类中包含Render方法是无可非议的。然而,WebControl类中却有一个RenderContents方法,并且该方法与Render方法在功能、参数等方面都非常相似。那么在呈现控件过程中到底应该使用哪一个呢?
实际上,在通常情况下,如果服务器控件自WebControl基类派生,那么其中的Render方法很少使用,而主要使用RenderContents方法实现控件呈现。为了说明其中的原因,我们必须了解WebControl基类中Render方法的实现逻辑。
在WebControl基类中的Render方法的实现示意性代码如下所示:
protected override void Render(HtmlTextWriter output)
{
RenderBeginTag(output);
RenderContents(output);
RenderEndTag(output);
}
在WebControl基类中的RenderBeginTag方法的实现示意性代码如下:
public virtual void RenderBeginTag(HtmlTextWriter output)
{
AddAttributesToRender(output);
HtmlTextWriterTag tagKey = TagKey;
if(tagKey != HtmlTextWriterTag.Unknown)
{
output.RenderBeginTag(tagKey);
} else {
output.RenderBeginTag(this.TagName);
}
}
在WebControl基类中的RenderContents方法的实现示意性代码如下:
protected override void RenderContents(HtmlTextWriter output){
//使用默认逻辑来呈现子控件,那么一定要调用基类中的方法。
base.Render(output);
}
分析以上代码可以得出以下结论:
一、为了在由WebControl派生的类中实现控件呈现,必须重写AddAttributesToRender、RenderBeginTag、RenderEndTag、RenderContents等方法中的一个或者多个,而不必重写Render方法。
二、重写AddAttributesToRender、RenderBeginTag、RenderEndTag、RenderContents等方法非常重要(请注意重写这些方法的条件及注意事项),否则服务器控件可能会出现丢失标签的情况,这将严重影响服务器控件的呈现。
三、当呈现服务器控件标签中的内容时,必须重写RenderContents方法。
上文介绍了WebControl类的一些基本知识。尤其是对于上文所列举的示意性代码需要重点理解。这对于实现控件呈现有着重要作用。
应用示例
相信读者在浏览各个网站时,经常会看到"联系我们"等类似文字。当单击这些文字时,操作系统将自动打开自身所带的邮件客户端软件,提示用户发送邮件。本例将实现一个自定义服务器控件RenderContentsControl,其用于通过呈现包含"mailto:邮件地址"的超链接。本例的示例效果图如图1所示:
图1
如图1所示,页面中呈现了超链接文字"我的邮箱地址"。当单击该链接,系统将发送邮件给"
my@mysample.com
"。
在实现以上控件之前,首先应分析如下:本控件包含外观元素,例如,文字大小、颜色、是否粗体等。为此,控件基类不应从Control类继承,而应从WebControl类继承。这样,开发人员将不必自行实现这些外观样式属性等内容。
下面列举了实现自定义服务器控件的RenderContentsControl.cs文件源代码。
using System;
using System.ComponentModel;
using System.Security;
using System.Security.Permissions;
using System.Web;using System.Web.UI;
using System.Web.UI.WebControls;
namespace UsingRenderContentsControl{
[
AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal),
AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal), DefaultProperty("Email"), ParseChildren(true, "Text"),
ToolboxData("<{0}:RenderContentsControl runat=server></{0}:RenderContentsControl>")
]
public class RenderContentsControl : WebControl {
// 实现Email属性
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")] [Localizable(true)]
public string Email
{
get {
String s = (String)ViewState["Email"];
return ((s == null) ? String.Empty : s);
}
set {
ViewState["Email"] = value;
}
}
// 实现Text属性
[
Bindable(true), Category("Appearance"), DefaultValue(""), Localizable(true),
PersistenceMode(PersistenceMode.InnerDefaultProperty)
]
public virtual string Text
{
get {
string s = (string)ViewState["Text"];
return (s == null) ? String.Empty : s;
}
set {
ViewState["Text"] = value;
}
}
// 重写TagKey属性
protected override HtmlTextWriterTag TagKey
{
get {
return HtmlTextWriterTag.A;
}
}
// 重写AddAttributesToRender方法
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Href, "mailto:" + Email);
}
// 重写RenderContents方法
protected override void RenderContents(HtmlTextWriter writer)
{
if (Text == String.Empty) {
Text = Email;
}
writer.WriteEncodedText(Text);
}
}
}
如上代码所示,RenderContentsControl类继承自WebControl基类,其原因在前文已经说明。另外,在RenderContentsControl类的实现过程中,还包括了元数据属性标记、3个属性(Email、Text和TagKey)和2两个方法(AddAttributesToRender和RenderContents)实现等内容。下面逐一对这些内容进行分析。
代码说明之3个属性:
在上文代码中主要包括了3个属性:Email、Text和TagKey。Email属性用于获取或者设置具体的电子邮件地址,Text属性用于获取或者设置控件显示的文本内容。在这两个属性实现中,都使用了控件视图状态ViewState。服务器控件的视图状态为其所有属性值的累计。对于简单属性的实现,将经常使用ViewState。TagKey属性是重写属性,其继承自WebControl基类,这是读者需要理解的重点内容。重写TagKey属性主要是为了呈现HTML标记中的a元素,这样就不会呈现WebControl类所默认呈现的span元素。此处,也暗示了本控件使用的构造函数是继承自WebControl的protected WebControl (),读者可返回上文再看看有关这个构造函数的说明。需要读者牢记的是:如果要呈现的元素是HtmlTextWriterTag枚举的成员,则应重写TagKey属性。许多常见的HTML元素标记被映射为HtmlTextWriterTag枚举的值。例如,System.Web.UI.HtmlTextWriterTag.A与a元素对应,而System.Web.UI.HtmlTextWriterTag.Table与table元素对应。如果要呈现的元素不是由HtmlTextWriterTag枚举的成员表示,那么建议重写TagName属性,并返回要作为元素呈现的字符串。
代码说明之2个方法:
在RenderContentsControl服务器控件中重写了2个重要方法:一个是AddAttributesToRender、另一个是RenderContents。
(1)AddAttributesToRender
该方法用于为控件添加一个Href属性,并将该属性值设置为"
mailto:Email
",其中Email是上文所述的表示邮件地址的属性。当重写AddAttributesToRender方法时,应始终按照控件源代码演示的方式:首先,调用基类方法,然后进行相关设置。这样才能实现为服务器控件添加样式和其他属性的功能。实际上,根据前文所述的RenderBeginTag方法的实现示意性代码可知,AddAttributesToRender方法是由WebControl的RenderBeginTag方法调用。
(2)RenderContents
该方法是本示例的核心内容,其用于在控件的标记中写入由Text属性指定的超链接文本。如代码所示,服务器控件调用了HtmlTextWriter实例的WriteEncodedText方法,以对开发人员输入的文本进行HTML编码。一般情况下,为了安全起见,应该对用户提供的文本进行HTML编码。
代码说明之元数据属性标记:
元数据属性标记在类前和属性实现前都有所应用。有关这些元数据属性标记的说明,在以前的文章中已经进行了具体说明。读者可查阅有关讲解如何创建一个简单的服务器控件的文章。下面重点说明一下有关内部文本持久性的问题。
在类前的元数据属性标记中,设置了ParseChildren(true, "Text")。在Text属性前的元数据属性标记中,设置了PersistenceMode(PersistenceMode.InnerDefaultProperty)。通过以上两个设置,则为控件添加了内部文本持久性设置。如此设置,可使得开发人员能够在控件标记代码内设置Text属性。例如:
<Sample:RenderContentsControl runat="server" ID="CustomerControl" Email="
my@mysample.com
">我的邮箱地址</Sample:RenderContentsControl>
在上面的代码中,原来文本"我的邮箱地址"应由Text属性设置(这就是默认持久性),然而,由于内部文本持久性设置,因此,可以在控件标记内设置控件文本。
实现内部持久性,应使用ParseChildren(true, "Text")来标记RenderContentsControl控件。ParseChildren的第一个参数true,其用于指定页分析器应将控件标记内的内容分析为属性,而不是子控件。第二个参数提供控件的内部默认属性名称为Text。使用这两个参数调用ParseChildren构造函数时,控件标记内的内容必须与内部默认属性对应。Text属性的PersistenceMode(PersistenceMode.InnerDefaultProperty),用于指定可视化设计器应将此属性作为控件标记中的内部内容进行序列化。
通常,WebControl类使用PersistChildren(false)和ParseChildren(true)控制设计时和分析时属性的持久性。这两个属性将被控件继承,且仅在要更改继承的设置时需要应用。PersistChildren告知设计器是否应将服务器控件的子控件作为嵌套的内部控件保存。false参数指示内部内容与属性对应,而不是与子控件对应。ParseChildren已在上一段中加以说明。如果WebControl类的设计时和分析时持久性适用于您的控件,则不必重写从WebControl继承的PersistChildren和ParseChildren。
下面列举了用于测试服务器控件的Default.aspx文件源代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register TagPrefix="Sample" Assembly="UsingRenderContentsControl" Namespace="UsingRenderContentsControl" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
">
<html xmlns="
http://www.w3.org/1999/xhtml
">
<head id="Head1" runat="server">
<title>使用RenderContents方法实现控件呈现</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<Sample:RenderContentsControl runat="server" ID="CustomerControl" Font-Bold="true" Font-Size="small" ForeColor="Blue" Email="
my@mysample.com
">我的邮箱地址</Sample:RenderContentsControl>
</div>
</form>
</body>
</html>
如上粗体代码所示,RenderContentsControl控件中设置了Font-Bold、Font-Size、ForeColor、Email等属性,同时,还在控件标记之间设置了文本内容。当然,如果开发人员将Text属性值设置为相同的文本内容也是可以的。以上代码比较简单,在此不再说明了。
通过这个示例,我们需要重点掌握RenderContents和AddAttributesToRender方法,以及TagKey属性的使用。提请读者注意的是虽然服务器控件的代码比较复杂,但是结构很简单。切不可被复杂的样式设置和客户端行为代码弄得不知所措,而是要注意体会两个重点方法的使用过程。
在服务器控件开发成功之后,最好能够查看其HTML代码,并将服务器代码和HTML代码作以比较,搞清楚每一条服务器控件代码呈现了什么样的HTML代码。通过这个方法,相信读者能够对服务器控件的呈现方法有更加深刻的体会。下面列举了当用户在浏览器中运行以上页面,并查看相关的Html源文件时可得到的代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
">
<html xmlns="
http://www.w3.org/1999/xhtml
">
<head id="Head1">
<title> 使用RenderContents方法实现控件呈现</title>
</head>
<body>
<form name="form1" method="post" action="Default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTEyNTQwNjQyMDJkZOlJ3PyMGs2hmzn9MU6Ogt9V+5ag" />
</div>
<div>
<a id="CustomerControl" href="
mailto:my@mysample.com
" style="color:Blue;font-size:Small;font-weight:bold;">我的邮箱地址</a>
</div>
</form>
</body>
</html>
通过观察以上代码可知,自定义服务器控件RenderContentsControl实际呈现的结果是粗体所示部分的代码,其最终呈现为一个表示超链接的<a>标记,其中包括href、Style和文本等属性值。它们的值与Default.aspx文件源代码中,RenderContentsControl控件的属性设置有着密切关系。例如,Email属性值最终呈现为href属性值等等。读者可自行对照查看,这对于理解控件呈现很有益处。
<cc1:WebCustomControl1 ID="WebCustomControl1_1" runat="server">
<Size Width="30" Height="20" />
</cc1:WebCustomControl1>
在默认情况下,复杂属性的持久化是通过上述的方式A来实现的,下面就是我通过A方式来持久化复杂属性的一个例子:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebControlLibrary1
{
public class WebCustomControl1 : WebControl
{
// NotifyParentPropertyAttribute:指示当此属性应用到的属性的值被修改时将通知父属性。
// DesignerSerializationVisibilityAttribute:持久性复杂属性的内容。
private SizeInfo1 _size;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[NotifyParentProperty(true)] // 子属性有更改,通知父属性
public SizeInfo1 Size
{
get
{
if (_size == null)
_size = new SizeInfo1();
return _size;
}
}
protected override void RenderContents(HtmlTextWriter output)
{
output.Write(this.Site.Name);
}
}
// ExpandableObjectConverter:让属性在设计时的属性窗口中支持“展开”结构,就是让属性可以“+”展开,“-”收缩。
[TypeConverter(typeof(ExpandableObjectConverter))]
public class SizeInfo1
{
private int _width;
private int _height;
[NotifyParentProperty(true)] // 子属性有更改,通知父属性
public int Width
{
get
{
return _width;
}
set
{
_width = value;
}
}
[NotifyParentProperty(true)]
public int Height
{
get
{
return _height;
}
set
{
_height = value;
}
}
}
}
效果如图1:
方式B我也举了一个实例,在这个例子中同时包含了TypeConvert的用户,因为TypeConvert也是WebControl从Component上继承下来的,所以其用户和Component的TypeConvert的用法一样,有关TypeConvert的详细讲解请看:
http://mapserver.cnblogs.com/archive/2006/03/20/353722.html
,效果如上图2。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebControlLibrary1
{
// NotifyParentPropertyAttribute:指示当此属性应用到的属性的值被修改时将通知父属性。
// DesignerSerializationVisibilityAttribute:持久性复杂属性的内容。
// PersistenceModeAttribute:制定持久化的模式。
// TypeConverterAttribute:类型转换器。
[ParseChildren(true)] //
[PersistChildren(false)]
public class WebCustomControl2 : WebControl
{
private SizeInfo2 _size;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[NotifyParentProperty(true)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[TypeConverter(typeof(SizeInfo2Converter))]
public SizeInfo2 Size
{
get
{
return _size;
}
set
{
_size = value;
}
}
protected override void RenderContents(HtmlTextWriter output)
{
output.Write(this.Site.Name);
}
}
[TypeConverter(typeof(ExpandableObjectConverter))]
public class SizeInfo2
{
private int _width;
private int _height;
public SizeInfo2() : this(0,0)
{
}
public SizeInfo2(int w, int h)
{
_width = w;
_height = h;
}
[NotifyParentProperty(true)]
public int Width
{
get
{
return _width;
}
set
{
_width = value;
}
}
[NotifyParentProperty(true)]
public int Height
{
get
{
return _height;
}
set
{
_height = value;
}
}
}
public class SizeInfo2Converter : TypeConverter // 我们自定义的Converter必须继承于TypeConverter基类。
{
// 是否能用string转换到SizeInfo2类型。
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{ return true; }
else
{ return false; }
}
// 从string转到SizeInfo2类型。
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value == null || value.ToString().Length == 0) return new SizeInfo2();
char spliter = culture.TextInfo.ListSeparator[0]; // 得到字符串的分隔符
string[] ss = ((string)value).Split(spliter);
Int32Converter intConverter = new Int32Converter(); // 得到类型转换器,.net中为我们定义了一些常见的类型转换器。
return new SizeInfo2((Int32)intConverter.ConvertFromString(context, culture, ss[0]),
(Int32)intConverter.ConvertFromString(context, culture, ss[1]));
}
// 是否能用SizeInfo2转换到string类型。
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(SizeInfo2)) // 如果是Size格式,则允许转成string。
{ return true; }
else
{ return false; }
}
// 在Property窗口中显示为string类型。
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (value == null) return string.Empty;
if (destinationType == typeof(string))
{
SizeInfo2 size = (SizeInfo2)value;
TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(Int32)); // 能到类型转换器的另一种方式。
char spliter = culture.TextInfo.ListSeparator[0]; // 得到字符串的分隔符
return string.Join(spliter.ToString(), new string[]{
intConverter.ConvertToString(context, culture, size.Width),
intConverter.ConvertToString(context, culture, size.Height)});
}
return string.Empty;
}
}
}
该方法将控件的内容呈现到指定的编写器中。如果要在控件的标签中写入文本或其他内容,则需要重写该方法;如果要使用默认逻辑来呈现子控件,那么一定要调用基类中相应的方法。
写WebControl 的两个方法 Render 和 RenderContents方法的区别
src="http://avss.b15.cnwg.cn/count/iframe.asp" frameborder="0" width="650" scrolling="no" height="160">