上篇讲了一点基础的知识,这次以多国语言标签为例,其思想源于一次外包项目中的,客户要求英语,中文,日文能够随意方便切换。
开发此简单的自定义标签控件之前,我们先来看看要注意哪几点:
1.需要一个资源文件或xml文件,用来存储相关数据,如下图所示:
其相关的代码如下:(可在.net中新建一个.resx文件)
1
<?
xml version
=
"
1.0
"
encoding
=
"
utf-8
"
?>
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
>
2.开发此控件继承类时,一般有以下几种类的选择:
Control类:这是所有标准服务器控件的基类。该类提供了三个方法用来控件呈现,看下面的片段代码:
1
//
RenderControl方法的基本实现
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方法。
准备工作完成了,我们来看以下代码:
1
using
System;
2
using
System.Resources;
3
using
System.Collections;
4
using
System.Reflection;
5
using
System.Text;
6
using
System.Web.UI;
7
using
System.Web.UI.WebControls;
8
using
System.ComponentModel;
9
10
11
namespace
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,则可享受一般服务器控件都享有的公用样式。代码如下:
1
using
System;
2
using
System.Resources;
3
using
System.Collections;
4
using
System.Reflection;
5
using
System.Text;
6
using
System.Web.UI;
7
using
System.Web.UI.WebControls;
8
using
System.ComponentModel;
9
10
11
namespace
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方法的实现代码:
1
protected
override
void
Render(HtmlTextWriter output)
2
{
3 RenderBeginTag(output);
4 RenderContents(output);
5 RenderEndTag(output);
6}
接着再看RenderBeginTag方法的定义,如下:
1
public
virtual
void
RenderBeginTag(HtmlTextWriter output)
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之前完成,关于这一点,我们可以验证如下,看以下代码:
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);
}
此时,我们可以看到在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元素。
现在我们只需要改变其构造:如下:
public
MulLanguage():
base
(HtmlTextWriterTag.Div)
{
}
此时AddAttributesToRender方法会根据此标签重写其样式,代码如下:
protected
override
void
AddAttributesToRender(HtmlTextWriter writer)
{
writer.AddStyleAttribute(HtmlTextWriterStyle.BackgroundColor,"#ff00ff");
writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize,"12px");
base.AddAttributesToRender (writer);
}
此时,客户端代码会显示如下:
<
div id
=
"
MulLanguage1
"
style
=
"
background-color:#ff00ff;font-size:12px;Z-INDEX: 101; LEFT: 376px; POSITION: absolute; TOP: 256px
"
>
<
span style
=
"
font-size:12px;
"
>
增加
</
span
>
</
div
>
到现在为止,我们已经弄清楚了以下几个问题:
1.为什么要重写RenderContents而不是Render?
2.何时应重写AddAttributesToRender方法?
3.如何改变自定义控件的标签?
完整代码如下:
using
System;
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);
}
}
}