1)开发复合控件
每个控件都具有从 System.Web.UI.Control 继承的 Controls 属性。这是表示控件的子控件(如果有)的集合属性。如果控件未用 ParseChildrenAttribute 标记,或是标记为 ParseChildrenAttribute(Childrenasproperties = false),则当控件在页上以声明方式使用时,ASP.NET 页框架将应用以下分析逻辑:
如果分析器在控件的标记内遇到嵌套控件,它将创建嵌套控件的实例并将它们添加到控件的 Controls 属性。标记之间的文本添加为 LiteralControl。任何其他嵌套元素都生成分析器错误。
如果自定义控件是从 WebControl 派生的,它将不具有示例中描述的分析逻辑,因为 WebControl 是用 ParseChildrenAttribute(ChildrenAsProperties = true) 标记的,用它可以指示当在页上以声明方式使用控件时,嵌套在服务器控件标记内的 XML 元素是应视为属性还是应视为子控件。无法继承此类。
以常见的DropDownList为例,下面是DropDownList控件基本使用模式:
<asp:DropDownList id=”drop” ruant=”server”>
<asp:ListItem>item1<asp:ListItem>
… …
<asp:ListItem>itemn<asp:ListItem>
</asp:DropDownList>
我们说过web服务器控件都是从WebControl类派生,这样.NET框架解析该控件时,ASP.NET自动将DropDownList的ChildrenAsProperties 属性设置为true,告诉框架在建立DropDownList时,ListItem应该看成是子控件而不是DropDownList的属性。这样框架就会建立子控件ListItem对象。
一个控件到底是属性还是子控件有什么区别吗?其实属性和自控件主要区别是包容容器的不同。当一个字段被当作属性时,这个属性其实是该类的一个成员,例如DropDownList的ID,它是DropDownList的属性,因此DropDownList的定义应该类似如下:
class DropDownList:WebControl
{int ID;
… …}
而ListItem是子控件使得ListItem和DropDownList是逻辑上分离的,也就是说ListItem提供一种接口,这样复合这种接口的控件,都可以作为它的父控件(或者说容器),
正式因为这个原因,所以对于ListBox控件可能有如下使用方式
<asp:ListBox id=”list” ruant=”server”>
<asp:ListItem>item1<asp:ListItem>
… …
<asp:ListItem>itemn<asp:ListItem>
</asp:ListBox>
读者可以看到,作为子控件的ListItem很容易“融合”在ListBox里,把ListBox当作其容器。
接下来我们要开发功能和DropDownList控件类似的Sorter控件,所以读者页可能已经感觉到,将要建立的控件使用应该类似如下:
<Community:Sorter id=”sorter” ruant=”server”>
<asp:ListItem>item1<asp:ListItem>
… …
<asp:ListItem>itemn<asp:ListItem>
</Community:Sorter>
注意:可以通过使用类撰写组合现有控件来创作新控件。复合控件等效于使用 ASP.NET 页语法创作的用户控件。用户控件和复合控件之间的主要差异是用户控件保持为 .ascx 文本文件,而复合控件则经过编译并保持在程序集中。
在开发Sorter自定义复合控件前,先看一下常规开发的两个主要步骤:
A)重写从 Control 继承的受保护的 CreateChildControls 方法,以创建子控件的实例并将它们添加到控件集合。前面说过既然ListItem是子控件就需要将它添加到父容器里,例如如果是DropDownList控件其父容器是DropDownList,如果是ListBox控件,则父容器是ListBox。下面显示了如何重新CreateChildControls
public class Composition1 : Control, INamingContainer {
... ...
protected override void CreateChildControls() {
//加入第一个控件
this.Controls.Add(new LiteralControl("<h3>" + "值:"));
//加入第二个控件
TextBox box = new TextBox();
box.Text = "0";
this.Controls.Add(box);
//加入第三个控件
this.Controls.Add(new LiteralControl("</h3>"));
}
这样在控件树里加入了三个控件:两个LiteralControl和一个TextBox控件。其中第一个LiteralControl的值为“<h3>值”,第二个LiteralControl的值为“</h3>”,TextBox的值为0。这样如果在服务器发送到浏览器后,生成的HTML代码就是“<h3>0</h3>”。
当将子控件加入控件树以后,就可以从索引为零的编号获取控件树里的控件,加入后页面控件树示意图如下:
Page
|
|--LiteralControl (Controls[0])
|
|--TextBox (Controls[1])
|
|--LiteralControl (Controls[2])
其中Page是整个控件树的树根,每一个子控件根据加入顺序的位置的不同依次加入控件树。当需要获取控件树时,可以使用索引获取,例如读写TextBox的代码如下:
//读取TextBox控件的值,并赋给value变量
string value=((TextBox)Controls[1]).Text;
//将value值写入TextBox控件里
((TextBox)Controls[1]).Text = value.ToString();
这里Controls[1]获取的就是第二个控件,也就是TextBox,然后使用TextBox类进行强制转换,获取Text值后赋值给value。写如的方法和此类似。
B)如果复合控件的新实例将在页上重复创建,请实现 System.Web.UI.INamingContainer 接口。这是不具有方法的标记接口。当用控件实现时,ASP.NET 页框架将在此控件下创建新的命名范围。这确保了子控件在控件层次结构树中具有唯一的 ID。 因为子控件会提供呈现逻辑,所以不必重写 Render 方法。可以公开合成子控件属性的属性。
在ASP.NET提供的服务器控件里都实现了InamingContainer接口,所以您可以在一个页面生多次使用button、TextBox等这样的控件。但是在下面介绍的Sorter自定义控件里,根据实际需求,一个页面只要一个Sorter自定义控件就可以了,所以Sorter自定义控件没有实现InamingContainer接口。
由于将要开发的复合控件还需要实现数据回发、事件处理等,下面再介绍这些基础的内容。
2)开发处理回发数据的自定义(非复合)控件 Sorter
检查回发(输入)数据的控件必须实现 System.Web.UI.IPostBackDataHandler 接口。这将向 ASP.NET 页框架发出信号,指出控件应参与回发数据处理。页框架将输入数据作为键/值对传递给此接口的 LoadPostData 方法。请看下面代码:
using System;
using System.Web;
using System.Web.UI;
using System.Collections.Specialized;
namespace CustomControls{
public class MyTextBox: Control, IPostBackDataHandler {
public String Text {
get { return (String) ViewState["Text"]; }
set { ViewState["Text"] = value; }
}
public event EventHandler TextChanged;
public virtual bool LoadPostData(string postDataKey, NameValueCollection values)
{
String presentValue = Text;
String postedValue = values[postDataKey];
if (!presentValue.Equals(postedValue)){
Text = postedValue;
return true;
}
return false;
}
public virtual void RaisePostDataChangedEvent() {
OnTextChanged(EventArgs.Empty);
}
protected virtual void OnTextChanged(EventArgs e){
if (TextChanged != null)
TextChanged(this,e);
}
protected override void Render(HtmlTextWriter output) {
output.AddAttribute(HtmlTextWriterAttribute.Type, "text");
output.AddAttribute(HtmlTextWriterAttribute.Value, this.Text);
output.AddAttribute(HtmlTextWriterAttribute.Name, this.UniqueID);
output.RenderBeginTag(HtmlTextWriterTag.Input);
output.RenderEndTag();
}
}
}
若要使控件能够检查客户端发回的窗体数据,控件必须实现 System.Web.UI.IPostBackDataHandler 接口。此接口的协定允许控件确定是否在回发后改变其状态以及引发相应的事件。IPostBackDataHandler 接口包含两个方法。
public interface IPostBackDataHandler{
public bool LoadPostData(string postDataKey, NameValueCollection postCollection);
public void RaisePostDataChangedEvent();
}
回发后,页框架在发送的内容中搜索与实现 IPostBackDataHandler 的服务器控件的 UniqueID 匹配的值。然后,页框架按顺序在每个实现该接口的控件上调用 LoadPostData。LoadPostData 的两个参数是:标识控件的关键字以及包含发送数据的集合 NameValueCollection。通常实现 LoadPostData,以便在回发后更新控件的状态。以下示例说明用于自定义文本框 (TextBox) 控件的 LoadPostData 实现。
public virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) {
string presentV
该文章转载自1024k:http://www.1024k.cn/control/2007/200702/1271.html
alue = text;
string postedValue = postCollection[postDataKey];
if (!presentValue.Equals(postedValue)){
Text = postedValue;
return true;
}
return false;
}
如果控件状态因回发而更改,则 LoadPostData 返回 true;否则返回 false。页框架跟踪所有返回 true 的控件并在这些控件上调用 RaisePostDataChangedEvent。更改事件(如果有)就是从该方法引发的。因此,回发数据处理分两个阶段进行,即更新状态和引发更改通知。这可防止在加载回发数据过程中引发更改通知,在该过程中,更改通知可能在各控件加载回发数据之前错误地修改状态。以下代码片段显示了用于自定义文本框 (TextBox) 控件的 RaisePostDataChanged 实现。
public virtual void RaisePostDataChangedEvent() {
OnTextChanged(EventArgs.Empty);
}
呈现逻辑必须为控件的名称特性分配 UniqueID。否则,页框架就无法将回发数据传送给控件。如果控件发出多个窗体元素,则至少有一个元素必须具有与控件 UniqueID 对应的名称特性。以下代码片段将 UniqueID 分配给名称特性。
protected override void Render(HtmlTextWriter output)
{
output.AddAttribute(HtmlTextWriterAttribute.Type, "text");
output.AddAttribute(HtmlTextWriterAttribute.Value, this.Text);
output.AddAttribute(HtmlTextWriterAttribute.Name, this.UniqueID);
output.RenderBeginTag(HtmlTextWriterTag.Input);
output.RenderEndTag();
}
<%@ Register TagPrefix="Custom" Namespace="CustomControls" Assembly = "CustomControls" %>
<html>
<script language="C#" runat=server>
private StringBuilder message = new StringBuilder("");
private void Text_Changed(Object sender,EventArgs e){
message.Append("The text in" + sender.ToString()+ " was changed.");
message.Append("<br>You entered " + Server.HtmlEncode(Box.Text) +".");
}
protected override void Render(HtmlTextWriter output) {
base.Render(output);
output.Write(message.ToString());
}
</script>
<body>
<form method="POST" action="MyTextBox.aspx" runat=server>
Enter your name: <Custom:MyTextBox Text=" " OnTextChanged = "Text_Changed" id = "Box" runat=server/>
<br><br>
<asp:Button Text = "Submit" runat = server/>
</form>
</body>
</html>
这里再次强调一下UniqueID,上面我们使用了一个ID为Box的MyTextBox控件,介绍我们在服务器控件使用了两个MyTextBox控件如下
<Custom:MyTextBox Text="this is a mytextbox1 " id = "Box1" runat=server/>
<Custom:MyTextBox Text="this is amytextbox2" id = "Box1" runat=server/>
那么当页面回发时如何区分获取这两个控件值呢?
这里首先说明,在上面MyTextBox自定义控件里并没有实现InamingContainer接口,所以不能够再页面实现两个MyTextBox控件,现在是假设实现了InamingContainer接口,如何分别获取Box1和Box2的值。
首先可以更改LoadPostData方法如下如下:
public virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) {
string presentValue = Text;
string postedValue = postCollection[UniqueID];
if (!presentValue.Equals(postedValue)){
Text = postedValue;
return true;
}
return false;
}
LoadPostData的第二个参数是NameValueCollection类型,可以使用名值对的方式获取数据。
对于第一个MyTextBox来说,其ID为Box1,所以此时UniqueID就是Box1,通过postCollection[UniqueID]就可以获取传递的文本值“this is a mytextbox1”。
对于第二个MyTextBox来说,其ID为Box2,所以此时UniqueID就是Box2,通过postCollection[UniqueID]就可以获取传递的文本值“this is a mytextbox2”。
那么数据又如何改变呢?
对于第一个MyTextBox,它首先的文本是“this is a mytextbox1”,假设我们在*.aspx里改变其文本为“mytextbox1 values”,那么数据回发到服务器时,会将现在的文本和原来的文本进行毕竟,那么原来的文本是怎么保存的呢?答案是通过ViewState,读者应该看到在MyTextBox里有如下代码
public String Text {
get { return (String) ViewState["Text"]; }
set { ViewState["Text"] = value; }
}
它会保存上一次的数据,也就是“this is a mytextbox1”,然后和本次提交的数据“mytextbox1 values”进行毕竟,比较是通过是否相等进行的(换句话说比较文本有没有改变),如下
if (!presentValue.Equals(postedValue)){
Text = postedValue;
return true;
}
如果当前的数据presentValue(它的现在的值是“this is a textbox1”)和postedValue(它的现在的值是“mytextbox1 values”)不等则将postedValue赋值给Text,以便回发到客户端进行更新TextBox的值。然后再返回true。
在返回true非常重要,因此系统再保存了当前值mytextbox1 values以后,并不意味用户的处理已经结束了,可能用户还定义了事件,此时就可以使用RaisePostDataChangedEvent进一步处理事件,如下
public virtual void RaisePostDataChangedEvent() {
OnTextChanged(EventArgs.Empty);
}
我们看到再该实现里调用了OnTextChanged方法,而在OnTextChanged方法里调用了TextChanged事件,所以用户就可以在*.aspx页面里使用如下方式触发自定义事件处理内容:
private void Text_Changed(Object sender,EventArgs e){}
…
<Custom:MyTextBox Text=" " OnTextChanged = "Text_Changed" id = "Box" runat=server/>
3)自定义复合控件处理事件
下面代码定义了一个复合控件 Composition2,该控件将两个按钮控件(名为 Add 和 Subtract)添加到复合控件里,并为按钮的 Click 事件提供事件处理方法。这些方法增加和减少 Composition2 的 Value 属性。Composition2 的 CreateChildControls 方法创建引用这些方法的事件处理程序(委托)的实例,并将委托附加到 Button 实例的 Click 事件。最后得到一个进行自己的事件处理的控件——单击 Add 按钮时,文本框中的值增加;单击 Subtract 按钮时,值减少。
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace CompositionSampleControls {
public class Composition2 : Control, INamingContainer {
public int Value {
get {
this.EnsureChildControls();
return Int32.Parse(((TextBox)Controls[1]).Text);
}
set {
this.EnsureChildControls();
((TextBox)Controls[1]).Text = value.ToString();
}
}
protected override void CreateChildControls() {
// 添加文本控件
this.Controls.Add(new LiteralControl("<h3>" + "值:"));
// 添加文本框
TextBox box = new TextBox();
box.Text = "0";
this.Controls.Add(box);
// 添加文本控件
this.Controls.Add(new LiteralControl("</h3>"));
// 添加“加”按钮
Button addButton = new Button();
&
该文章转载自1024k:http://www.1024k.cn/control/2007/200702/1271_2.html
该文章转载自1024k:http://www.1024k.cn/control/2007/200702/1271_2.html
nbsp; addButton.text = "加";
addButton.Click += new EventHandler(this.AddBtn_Click);
this.Controls.Add(addButton);
// 添加文本控件
this.Controls.Add(new LiteralControl(" | "));
// 添加“减”按钮
Button subtractButton = new Button();
subtractButton.Text = "减";
subtractButton.Click += new EventHandler(this.SubtractBtn_Click);
this.Controls.Add(subtractButton);
}
private void AddBtn_Click(Object sender, EventArgs e) {
this.Value++;
}
private void SubtractBtn_Click(Object sender, EventArgs e) {
this.Value--;
}
}
}
在这种复合控件里,事件的处理由控件内部进行处理,所以如果我们在页面*.aspx使用该控件可能类似的代码如下:
<CompositionSampleControls:Composition2 id="MyControl" runat=server/>
读者看到,这里仅仅引用了复合控件,而对于数据添加/删除处理都是由控件内部来实现。
4)复合控件可以定义自定义事件,通过引发该事件来响应其子控件引发的事件。
上面介绍的复合控件将事件的具体处理都在其内部实现了,所以使用面叫窄,例如如果用户每一次单击Add添加2,每一次单击Subtract减少2,那么我们还需要到自定义控件内部更改代码类似如下:
private void AddBtn_Click(Object sender, EventArgs e) {
this.Value +=2;
}
private void SubtractBtn_Click(Object sender, EventArgs e) {
this.Value-=2;
}
可以该控件使用人员还必须了解控件内部的运行机制,这非常不方便使用,所以我们应该自定义带有事件的复合控件。
下面的示例显示复合控件 Composition3,该控件引发自定义事件 Change 以响应 TextBox 子控件的 TextChanged 事件。
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace CompositionSampleControls {
public class Composition3 : Control, INamingContainer {
public event EventHandler Change;
public int Value {
get {
this.EnsureChildControls();
return Int32.Parse(((TextBox)Controls[1]).Text);
}
set {
this.EnsureChildControls();
((TextBox)Controls[1]).Text = value.ToString();
}
}
protected void OnChange(EventArgs e) {
Change(this, e);
}
protected override void CreateChildControls() {
// 添加文本控件
this.Controls.Add(new LiteralControl("<h3>" + "值:"));
// 添加文本框
TextBox box = new TextBox();
box.Text = "0";
box.TextChanged += new EventHandler(this.TextBox_Change);
this.Controls.Add(box);
// 添加文本控件
this.Controls.Add(new LiteralControl("</h3>"));
// 添加“加”按钮
Button addButton = new Button();
addButton.Text = "加";
addButton.Click += new EventHandler(this.AddBtn_Click);
this.Controls.Add(addButton);
// 添加文本控件
this.Controls.Add(new LiteralControl(" | "));
// 添加“减”按钮
Button subtractButton = new Button();
subtractButton.Text = "减";
subtractButton.Click += new EventHandler(this.SubtractBtn_Click);
this.Controls.Add(subtractButton);
}
private void TextBox_Change(Object sender, EventArgs e) {
OnChange(EventArgs.Empty);
}
private void AddBtn_Click(Object sender, EventArgs e) {
this.Value++;
OnChange(EventArgs.Empty);
}
private void SubtractBtn_Click(Object sender, EventArgs e) {
this.Value--;
OnChange(EventArgs.Empty);
}
}
}
实现方法如下:
(a)自定义 Change 事件通过标准事件模式定义。(该模式包括受保护的 OnChange 方法的定义,该方法将引发 Change 事件。)
public event EventHandler Change;
protected void OnChange(EventArgs e) {
Change(this, e);}
前面也介绍过了,OnChange这种命名方式是为了便于和微软的资料相互一致,我们定义了OnChange方法后,就可以在*.aspx里使用类似如下代码定义事件具体的处理:
<CompositionSampleControls:Composition3 id="MyControl" OnChange="Composition3_Change" runat=server/>
<script language="C#" runat=server>
private void Composition3_Change(Object sender, EventArgs e) {
if (MyControl.Value < 0) {
MyControl.Value = 0;
}
}
</script>
读者此时应该明白一直使用类似OnClick、OnLoad的意义:通过代理进行链接,所以上面代码也可以写成更为明了的方式
Composition3.Change += new EventHander(Composition3_Change)
(b) 为 TextBox 的 TextChanged 事件定义了一个事件处理方法。该方法通过调用 OnChange 方法来引发 Change 事件。
private void TextBox_Change(Object sender, EventArgs e) {
OnChange(EventArgs.Empty);
}
(c)CreateChildControls 方法创建一个事件处理程序的实例,该实例引用上述方法并将事件处理程序附加到 TextBox 实例的 TextChanged 事件。
protected override void CreateChildControls() {
..
TextBox box = new TextBox();
box.TextChanged += new EventHandler(this.TextBox_Change);
...
}
Change 事件可以由承载控件的页来处理,如下面的示例所示。在此示例中,页为 Change 事件提供事件处理方法。如果用户输入的数字为负,该事件将 Value 属性设置为零。
定义好的自定义控件,就可以按照如下代码引用
<%@ Register TagPrefix="CompositionSampleControls" Namespace="CompositionSampleControls" Assembly="CompositionSampleControls" %>
<html>
<script language="C#" runat=server>
private void Composition3_Change(Object sender, EventArgs e) {
if (MyControl.Value < 0) {
MyControl.Value = 0;
}
}
</script>
<body>
<form method="POST" action="Composition3.aspx" runat=server>
<CompositionSampleControls:Composition3 id="MyControl"
OnChange="Composition3_Change" runat=server/>
</form>
</body>
</html>
5)维护状态
每个 Web 窗体控件都有一个 State 属性(从 Control 继承),该属性使 Web 窗体控件能够参与 State
该文章转载自1024k:http://www.1024k.cn/control/2007/200702/1271_3.html
管理。State 的类型为 Sytem.Web.UI.StateBag,这是等效于哈希表的数据结构。控件可以将数据作为键/值对保存在 State 中。State 通过 ASP.NET 页框架保持为字符串变量,并以隐藏变量的形式与客户端之间往返。回发时,页框架分析来自隐藏变量的输入字符串,并在页的控件层次结构中填充每个控件的 State 属性。通过使用 State 属性,控件可以还原其状态(将属性和字段设置为它们回发前的值)。
5.9.3 Sorter自定义控件
下面介绍Sorter自定义控件,首先导入命名空间
namespace ASPNET.StarterKit.Communities {
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections;
using System.ComponentModel;
using System.Collections.Specialized;
Sorter类文件用于生成Sorter控件,
[ParseChildren(false), ControlBuilder(typeof(SorterControlBuilder)),
Designer(typeof(ASPNET.StarterKit.Communities.CommunityDesigner)) ]
public class Sorter : WebControl, IPostBackDataHandler {
在这段代码里,ParseChildren设置为false,这是因为Sorter控件从WebControl派生,默认ParseChildren将被设置为true。ParseChildren用于指示页面分析器如何分析Sorter直接的XML标记,当将ParseChildren显式设置为falsie,页框架将认为Sorter直接是属性而非子控件,下面是Sorter在页面里的典型应用
<community:Sorter id="Sorter" align="right" runat="Server">
<ListItem Text="Default Order" value="Default" />
<ListItem Text="Date Posted" value="DateCreated"/>
<ListItem Text="Title" value="Title"/>
<ListItem Text="Popularity" value="ViewCount"/>
<ListItem Text="Topic" value="Topic" />
<ListItem Text="Author" value="Author" />
<ListItem Text="Rating" value="Rating" />
</community:Sorter>
通过将ParseChildren设置为false告诉系统,<community:Sorter></community:Sorter>之间的<ListItem>
</ListItem>标记应该看生是Sorter属性,这和常规使用的DropDownList默认将ListItem当作子控件并不一样。那么为什么这里设置为false呢?
笔者认为,将ParseChildren设置为false能够提供更为广阔的自定义控件的灵活性,以常规的DropDownList为例,下面的使用是错误的:
<asp:DropDownList id="Drop" align="right" runat="Server">
<asp:ListItem Text="Default Order" value="Default" />
<asp:ListItem Text="Date Posted" value="DateCreated"/>
<ListItem Text="Title" value="Title"/>
this is a dropdown
</asp:DropDownList>
就是因为DropDownList将ParseChildren设置为true,它只能够解析内部的子控件,而对于<ListItem Text="Title" value="Title"/>和this is a dropdown这样的文本则不能够解析,提示发生错误。
但是如果使用Sorter自定义控件,则如下写法是可以的
<Community:Sorter id="Drop" align="right" runat="Server">
<asp:ListItem Text="Default Order" value="Default" />
<asp:ListItem Text="Date Posted" value="DateCreated"/>
<ListItem Text="Title" value="Title"/>
this is a dropdown
</Community:Sorter>
因此Sorter将其内部数据并不看成子控件,后来还可以看到,通过派生的SorterControlBuilder类(下叙述),使得代码对于类似this is a dropdown这样的文本进行了过滤。
ASP.NET 页框架使用称为控件生成器的类来处理页上控件标记中的声明。每个 Web 窗体控件都与默认的控件生成器类 System.Web.UI.ControlBuilder 关联。默认的控件生成器为它在控件标记中遇到的每个嵌套控件将子控件添加到 Controls 集合。另外,它为嵌套控件标记之间的文本添加 Literal 控件。通过将自定义控件生成器类与控件关联,可以重写此默认行为。这通过对控件应用控件生成器属性来实现,Sorter使用的重新代码如下:
[ControlBuilder(typeof(SorterControlBuilder))]
public class Sorter : WebControl, IPostBackDataHandler {...}
以上方括号里的元素为公共语言运行库属性,该属性将 SorterControlBuilder类与 Sorter控件关联。通过从 ControlBuilder 派生并重写SorterControlBuilder方法,自定义了Sorter的生成器。
在后面代码我们可以看到,Sorter定义一个自定义控件生成器,它重写从 ControlBuilder 继承的 GetChildControlType 方法。此方法返回要添加的控件类型,并可用来决定将要添加哪些控件。在Sorter中,控件生成器仅在标记名称为“listItem”或者“asp:listItem”时才添加子控件。
Designer可以扩展自定义web服务器控件的模式行为。在Sorter的使用类似如下
[Designer(typeof(ASPNET.StarterKit.Communities.CommunityDesigner)) ]
public class Sorter : WebControl, IPostBackDataHandler {…}
表示具体的扩展类由ASPNET.StarterKit.Communities.CommunityDesigner类实现,后面会由介绍
接下来定义一个OrderChanged事件,并定义了升序或者降序时显示的文本属性。
public event EventHandler OrderChanged
ListItemCollection _items = new ListItemCollection();
string _ascendingText = "Ascending";
public string AscendingText
{ get { return _ascendingText; } set { _ascendingText = value; } }
string _descendingText = "Descending";
public string DescendingText
{ get { return _descendingText; } set { _descendingText = value; } }
请读者明白一个Sorter控件其实包含了两个DropDownList控件,,但是对于有便那个下拉框它的文本显示是固定的,要么是升序(Ascending)要么是降序(Descending),所以在用户使用该控件值,这个DropDownList的值并不需要用户维护,而是由该Sorter控件内部自己维护,所以这里定义了AscendingText和DescendingText属性。