开发此简单的自定义标签控件之前,我们先来看看要注意哪几点:
1.需要一个资源文件或xml文件,用来存储相关数据,如下图所示:
其相关的代码如下:(可在.net中新建一个.resx文件)
2<root>
3 <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
4 <xsd:element name="root" msdata:IsDataSet="true">
5 <xsd:complexType>
6 <xsd:choice maxOccurs="unbounded">
7 <xsd:element name="data">
8 <xsd:complexType>
9 <xsd:sequence>
10 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
11 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
12 </xsd:sequence>
13 <xsd:attribute name="name" type="xsd:string" />
14 <xsd:attribute name="type" type="xsd:string" />
15 <xsd:attribute name="mimetype" type="xsd:string" />
16 </xsd:complexType>
17 </xsd:element>
18 <xsd:element name="resheader">
19 <xsd:complexType>
20 <xsd:sequence>
21 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
22 </xsd:sequence>
23 <xsd:attribute name="name" type="xsd:string" use="required" />
24 </xsd:complexType>
25 </xsd:element>
26 </xsd:choice>
27 </xsd:complexType>
28 </xsd:element>
29 </xsd:schema>
30 <resheader name="ResMimeType">
31 <value>text/microsoft-resx</value>
32 </resheader>
33 <resheader name="Version">
34 <value>1.0.0.0</value>
35 </resheader>
36 <resheader name="Reader">
37 <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
38 </resheader>
39 <resheader name="Writer">
40 <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
41 </resheader>
42 <data name="Add">
43 <value>增加</value>
44 </data>
45 <data name="Delete">
46 <value>删除</value>
47 </data>
48 <data name="UserName">
49 <value>用户名</value>
50 </data>
51 <data name="Pwd">
52 <value>密码</value>
53 </data>
54</root>
Control类:这是所有标准服务器控件的基类。该类提供了三个方法用来控件呈现,看下面的片段代码:
2 public void RenderControl(HtmlTextWriter writer)
3 {
4 if(Visible)
5 {
6 Render(writer);
7 }
8 }
9 //Render方法基本实现
10 protected virtual void Render(HtmlTextWriter writer)
11 {
12 RenderChildren(writer);
13 }
14 //RenderChildren方式基本实现
15 protected virtual void RenderChildren(HtmlTextWriter writer)
16 {
17 foreach (Control c in Controls)
18 {
19 c.RenderControl(writer);
20 }
21 }
22
(1)RenderControl方法
先判断其Visible然后调用Render方法
(2) Render方法
使用TextWriter将标记字符和文本输出然后调用RenderChildren方法
(3)RenderChildren方法
判断当前控件是否有子控件,然后再调用RenderControl方法根据子控件的Visible值输出子控件.
注意:
如果我们重写了RenderControl,则享受不到Visible属性给我们带来的便利,所以如果我们继承Control类,只能重写Render方法。
准备工作完成了,我们来看以下代码:
2using System.Resources;
3using System.Collections;
4using System.Reflection;
5using System.Text;
6using System.Web.UI;
7using System.Web.UI.WebControls;
8using System.ComponentModel;
9
10
11namespace Component
12{
13 /// <summary>
14 /// myResources 的摘要说明。
15 /// </summary>
16 /// [DefaultProperty("Text"),
17 [DefaultProperty("Text"),
18 ToolboxData("<{0}:myResources runat=server></{0}:myResources>")]
19 public class myResources:Control
20 {
21 Assembly assembly;
22 ResourceManager rmManager;
23 private string text;
24 string s="";
25 private const string Defalut="默认值";
26 [Bindable(true),
27 Category("Appearance"),
28 DefaultValue("")]
29 public string Text
30 {
31 get
32 {
33 return text;
34 }
35 set
36 {
37 text=value;
38 }
39 }
40
41 public myResources()
42 {
43 //
44 // TODO: 在此处添加构造函数逻辑
45 //
46 }
47 protected override void Render(HtmlTextWriter output)
48 {
49 assembly = Assembly.GetExecutingAssembly();
50 rmManager = new ResourceManager("Component.cn",assembly);
51
52 if(Text==null)
53 {
54 output.Write(Defalut);
55 }
56 else
57 {
58
59 s = rmManager.GetString(Text);
60 if(s==null)
61 {
62 output.Write(Defalut);
63 }
64 else
65 {
66 output.Write(s);
67 }
68 }
69 base.Render(output);
70 }
71 }
72}
73
以上代码实现了当当在Label的Text中输入Add时,界面会显示增加,如果要换成其它版本的,直接修改.resx文件即可。
如果需要在项目中选择日文版本,可根据需要动态加载不同的.resx文件。(读者自行在项目中扩展)。
4.现在当我们运行以上事例,希望能够调整服务器控件都具有的公共样式时,发现并没有类似于以下图的样式:
如果我们此时改变继承的基类,将Control类改为WebControl类,呈现控件时不用Render了,改用RenderContents,则可享受一般服务器控件都享有的公用样式。代码如下:
2using System.Resources;
3using System.Collections;
4using System.Reflection;
5using System.Text;
6using System.Web.UI;
7using System.Web.UI.WebControls;
8using System.ComponentModel;
9
10
11namespace Component
12{
13 /// <summary>
14 /// myResources 的摘要说明。
15 /// </summary>
16 /// [DefaultProperty("Text"),
17 [DefaultProperty("Text"),
18 ToolboxData("<{0}:myResources runat=server></{0}:myResources>")]
19 public class myResources:WebControl
20 {
21 Assembly assembly;
22 ResourceManager rmManager;
23 private string text;
24 string s="";
25 private const string Defalut="默认值";
26 [Bindable(true),
27 Category("Appearance"),
28 DefaultValue("")]
29 public string Text
30 {
31 get
32 {
33 return text;
34 }
35 set
36 {
37 text=value;
38 }
39 }
40
41 public myResources()
42 {
43 //
44 // TODO: 在此处添加构造函数逻辑
45 //
46 }
47 protected override void RenderContents(HtmlTextWriter output)
48 {
49 assembly = Assembly.GetExecutingAssembly();
50 rmManager = new ResourceManager("Component.cn",assembly);
51
52 if(Text==null)
53 {
54 output.Write(Defalut);
55 }
56 else
57 {
58
59 s = rmManager.GetString(Text);
60 if(s==null)
61 {
62 output.Write(Defalut);
63 }
64 else
65 {
66 output.Write(s);
67 }
68 }
69 base.Render(output);
70 }
71 }
72}
73
为什么只有继承WebControl类并且一定要重写RenderContents而不是Render方法呢?
看下面WebControl基类中的Render方法的实现代码:
2{
3 RenderBeginTag(output);
4 RenderContents(output);
5 RenderEndTag(output);
6}
接着再看RenderBeginTag方法的定义,如下:
2 {
3 //添加呈现控件的属性和样式
4 //AddAttributesToRender为WebControl类中的方法
5 AddAttributesToRender(output);
6 //呈现控件标签
7 //如label控件呈现<span >
8 //textbox控件呈现<input >
9 HtmlTextWriterTag tagKey = TagKey;
10 if (tagKey != HtmlTextWriterTag.Unknown)
11 {
12 output.RenderBeginTag(tagKey);
13 }
14 else
15 {
16 output.RenderBeginTag(this.TagName);
17 }
18 }
如果你还不能理解,你可以这样:将以上多国语言控件修改后测试,你会发现如下几点:
(1).如果继承了WebControl类,重写Render方法时,当在页面中使用时,公用样式无效。
(2).如果继承了WebControl类,重写RenderContents方法时,当在页面中使用时,会自动加上默认的样式<span>....</span>,这样将Label当成了一个整体,此时公用样式就有效了。
(3).如果呈现的是TextBox,会自动加上<input>标签。
现在,当我们希望改变自定义标签的样式时,应该怎么办呢?
通过上面的RenderBeginTag方法,我们可以看到首先调用的是
//添加呈现控件的属性和样式
//AddAttributesToRender为WebControl类中的方法
AddAttributesToRender(output);
在 AddAttributesToRender(output);事件完成后,就进入了HtmlTextWrite的Html tag的步骤,任何绘制属性的动作就不能进行了,因此这些动作在绘制RenderBeginTag之前完成,关于这一点,我们可以验证如下,看以下代码:
{
assembly = Assembly.GetExecutingAssembly();
rmManager = new ResourceManager("Component.cn",assembly);
if(Text==null)
{
writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize,"12px");
writer.RenderBeginTag(HtmlTextWriterTag.Span);
writer.Write(Defalut);
writer.RenderEndTag();
}
else
{
s = rmManager.GetString(Text);
if(s==null)
{
writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize,"12px");
writer.RenderBeginTag(HtmlTextWriterTag.Span);
writer.Write(Defalut);
writer.RenderEndTag();
}
else
{
writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize,"12px");
writer.RenderBeginTag(HtmlTextWriterTag.Span);
writer.Write(s);
writer.RenderEndTag();
}
}
base.RenderContents(writer);
}
此时,我们可以看到在RenderBeginTag方法之前,我们定义了标签样式:writer.AddStyleAttribute(...)这样当我们运行此控件后,查看源代码时会发现如下样式:
<span id="MulLanguage1" style="background-color:#ff00ff;Z-INDEX: 101; LEFT: 400px; POSITION: absolute; TOP: 288px">
默认值
</span>
此时我们可以看到样式起作用了。细心的朋友可能会注意了,此控件的标签为<span>...</span>,大家知道,有时我们并不希望用<span>标签,如果我们希望改变这个标签,希望改变此标签为<div>呢,关于为何要用<div>而不用<span>,原因如下:
DIV 和 SPAN 元素最大的特点是默认都没有对元素内的对象进行任何格式化渲染。主要用于应用样式表。两者最明显的区别在于DIV是块元素,而SPAN是行内元素(也译作内嵌元素)。大家可以写个测例看看两者的区别:
(1).所谓块元素,是以另起一行开始渲染的元素,行内元素则不需另起一行,测试一下下面的代码你会有更形象的理解:
测试<span>紧跟前面的"测试"显示</span><div>这里会另起一行显示</div>
(2).块元素和行内元素也不是一成不变的,通过定义CSS的display属性值可以互相转化,如:
测试<div style="display:inline">紧跟前面的"测试"显示</div><span style="display:block">这里会另起一行显示</span>
提示:如果不对DIV元素定义任何CSS属性,其显示效果将行将于P元素。
现在我们只需要改变其构造:如下:
{
}
此时AddAttributesToRender方法会根据此标签重写其样式,代码如下:
{
writer.AddStyleAttribute(HtmlTextWriterStyle.BackgroundColor,"#ff00ff");
writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize,"12px");
base.AddAttributesToRender (writer);
}
此时,客户端代码会显示如下:
<span style="font-size:12px;">增加</span>
</div>
到现在为止,我们已经弄清楚了以下几个问题:
1.为什么要重写RenderContents而不是Render?
2.何时应重写AddAttributesToRender方法?
3.如何改变自定义控件的标签?
完整代码如下:
using System.Resources;
using System.Collections;
using System.Reflection;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
namespace Component
{
/// <summary>
/// MulLanguage 的摘要说明。
/// </summary>
[DefaultProperty("Text"),
ToolboxData("<{0}:MulLanguage runat=server></{0}:MulLanguage>")]
public class MulLanguage : System.Web.UI.WebControls.WebControl
{
Assembly assembly;
ResourceManager rmManager;
private string text;
string s="";
private const string Defalut="默认值";
[Bindable(true),
Category("Appearance"),
DefaultValue("")]
public string Text
{
get
{
return text;
}
set
{
text=value;
}
}
public MulLanguage():base(HtmlTextWriterTag.Div)
{
}
protected override void RenderContents(HtmlTextWriter writer)
{
assembly = Assembly.GetExecutingAssembly();
rmManager = new ResourceManager("Component.cn",assembly);
if(Text==null)
{
writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize,"12px");
writer.RenderBeginTag(HtmlTextWriterTag.Span);
writer.Write(Defalut);
writer.RenderEndTag();
}
else
{
s = rmManager.GetString(Text);
if(s==null)
{
writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize,"12px");
writer.RenderBeginTag(HtmlTextWriterTag.Span);
writer.Write(Defalut);
writer.RenderEndTag();
}
else
{
writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize,"12px");
writer.RenderBeginTag(HtmlTextWriterTag.Span);
writer.Write(s);
writer.RenderEndTag();
}
}
base.RenderContents(writer);
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
writer.AddStyleAttribute(HtmlTextWriterStyle.BackgroundColor,"#ff00ff");
writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize,"12px");
base.AddAttributesToRender (writer);
}
}
}