INamingContainer
BookCounter类中首先需要注意的是它实现了INamingContainer界面,这是一个没有方法的“记分器”界面。这一界面的目的是识别创建新的ID名字空间的容器控件,保证所有的子控件都有对于应用程序是唯一的ID。
包含CountedButton
BookCounter类包含有CountedButton的一个实例:
CountedButton btn = new CountedButton("inquiries");
或:
Public btn As CountedButton = New CountedButton("inquiries")
btn成员是在由System.Control继承的CreateChildControls方法中被初始化的:
protected override void CreateChildControls( )
{
Controls.Add(btn);
}
在VB.NET中,相应的代码为:
Protected Overrides Sub CreateChildControls( )
Controls.Add(btn)
End Sub
CreateChildControls是在绘制的准备工作时被调用的,它使BookCounter类能够添加btn对象作为被包含的控件。
BookCounter无需覆盖Render方法,它唯一需要绘制的是CountedButton。Render缺省的操作是绘制所有的子控件,因此无需对它作任何修改就能完成其功能。
BookCounter还有二个特性:BookName和Count。BookName是在控件中显示的一个字符串,而且通过ViewState进行管理,其C#代码如下所示:
public string BookName
{
get
{
return (string) ViewState["BookName"];
}
set
{
ViewState["BookName"] = value;
}
}
相应的VB.NET源代码为:
Public Property BookName( ) As String
Get
Return CStr(ViewState("BookName"))
End Get
Set(ByVal Value As String)
ViewState("BookName") = Value
End Set
End Property
Count是对一本特定的书查询的数量,记录这一数量的任务由CountedButton完成。其C#代码如下所示:
public int Count
{
get
{
return btn.Count;
}
set
{
btn.Count = value;
}
}
相应的VB.NET源代码为:
Public Property Count( ) As Integer
Get
Return btn.Count
End Get
Set(ByVal Value As Integer)
btn.Count = Value
End Set
End Property
无需将该值放在ViewState中,因为按钮本身就可以对其数据进行管理。
BookInquiryList复合控件的创建
每个BookCounter对象都被包含在BookInquiryList控件集合中,该控件没有特性,只有一个Render方法,其C#和VB.NET代码如下所示:
BookInquiryList的C#代码:
[ControlBuilderAttribute(typeof(BookCounterBuilder)),ParseChildren(false)]
public class BookInquiryList : System.Web.UI.WebControls.WebControl,
INamingContainer
{
protected override void Render(HtmlTextWriter output)
{
int totalInquiries = 0;
BookCounter current;
// 输出头部
output.Write("<Table border='1' width='90%' cellpadding='1'" +
"cellspacing='1' align = 'center' >");
output.Write("<TR><TD colspan = '2' align='center'>");
output.Write("<B> Inquiries </B></TD></TR>");
// 如果没有被包含的控件,输出相应的信息
if (Controls.Count == 0)
{
output.Write("<TR><TD colspan = '2'> align='center'");
output.Write("<B> No books listed </B></TD></TR>");
}
// 否则绘制每个包含的控件
else
{
// 遍历控件集,显示每个控件的书名,然后让每个被包含的控件自己绘制自己
for (int i = 0; i < Controls.Count; i++)
{
current = (BookCounter) Controls[i];
totalInquiries += current.Count;
output.Write("<TR><TD align='left'>" +
current.BookName + "</TD>");
output.RenderBeginTag("TD");
current.RenderControl(output);
output.RenderEndTag( );
output.Write("</tr>");
}
output.Write("<TR><TD colspan='2' align='center'> " +
" Total Inquiries: " +
totalInquiries + "</TD></TR>");
}
output.Write("</TABLE>");
}
}
BookInquiryList的VB.NET源代码:
Imports System.ComponentModel
Imports System.Web.UI
<ControlBuilder(GetType(BookCounterBuilder)), ParseChildren(False)> _
Public Class BookInquiryList
Inherits System.Web.UI.WebControls.WebControl
Implements INamingContainer
Protected Overrides Sub Render(ByVal output As HtmlTextWriter)
Dim totalInquiries As Integer = 0
' 输出头部
output.Write("<Table border='1' width='90%' cellpadding='1'" & _
"cellspacing='1' align = 'center' >")
output.Write("<TR><TD colspan = '2' align='center'>")
output.Write("<B> Inquiries </B></TD></TR>")
' 如果没有被包含的控件,输出相应的信息
If Controls.Count = 0 Then
output.Write("<TR><TD colspan = '2'> align='center'")
output.Write("<B> No books listed </B></TD></TR>")
' 否则绘制每个包含的控件
Else
' 遍历控件集,显示每个控件的书名,然后让每个被包含的控件自己绘制自己
Dim current As BookCounter
For Each current In Controls
totalInquiries += current.Count
output.Write("<TR><TD align='left'>" & _
current.BookName + "</TD>")
output.RenderBeginTag("TD")
current.RenderControl(output)
output.RenderEndTag()
output.Write("</tr>")
Next
Dim strTotalInquiries As String
strTotalInquiries = totalInquiries.ToString
output.Write("<TR><TD colspan='2' align='center'> " & _
" Total Inquiries: " & _
CStr(strTotalInquiries) & "</TD></TR>")
End If
output.Write("</TABLE>")
End Sub
End Class
Friend Class BookCounterBuilder
Inherits ControlBuilder
Public Overrides Function GetChildControlType( _
ByVal tagName As String, ByVal attributes As IDictionary) As Type
If tagName = "BookCounter" Then
Dim x As BookCounter
Return x.GetType
Else
Return Nothing
End If
End Function
Public Overrides Sub AppendLiteralString(ByVal s As String)
End Sub
End Class
ControlBuilder和ParseChildren属性
BookCounter类必须与BookInquiryClass联合使用,ASP.NET才能将.aspx网页中的元素转换为适当的代码。完成这一工作需要用到ControlBuilder属性:
[ControlBuilderAttribute(typeof(BookCounterBuilder)),ParseChildren(false)]
ControlBuilderAttribute的参数是一个通过传递BookCounterBuilder类获取的Type对象。下面是使用C#和VB.NET编写的BookCounterBuilder源代码:
C#语言版的BookCounterBuilder
internal class BookCounterBuilder : ControlBuilder
{
public override Type GetChildControlType(
string tagName, IDictionary attributes)
{
if (tagName == "BookCounter")
return typeof(BookCounter);
else
return null;
}
public override void AppendLiteralString(string s)
{
}
}
VB.NET版的BookCounterBuilder
Friend Class BookCounterBuilder
Inherits ControlBuilder
Public Overrides Function GetChildControlType(_
ByVal tagName As String, ByVal attributes As Idictionary) As Type
If tagName = "BookCounter" Then
Dim x As BookCounter
Return x.GetType
Else
Return Nothing
End If
End Function
Public Overrides Sub AppendLiteralString(ByVal s As String)
End Sub
End Class
ASP.NET将使用由ControlBuilder中派生出的BookCounterBuilder来判断由BookCounter标记批指示的对象的类型。通过这种结合,每个BookCounter对象才能被实例化,并添加到BookInquiryClass的控件集合中。
第二个参数ParseChildren必须被设置成false,让ASP.NET知道我们已经对子属性进行了处理,无需再对它进行进一步地解析了。false值表明子属性不是外部对象的特性,而仅仅是子控件的特性。
Render
BookInquiryClass中的唯一方法是覆盖Render的方法,Render的作用是使用由每个BookCounter子控件管理的数据绘制图9中的表格。
BookInquiryClass提供了一个总的查询数字,如下图所示:
Figure 14-16. Total inquiries displayed
通过将totalInquiries整型变量初始化为0,然后依次遍历每个控件,将其Count特性与totalInquiries相加,就可以得到查询的总数了。除了C#中语句结束时的分号为,实现这一功能的C#和VB.NET语句相同:
totalInquiries += current.Count;
表现输出内容
通过遍历每个控件,下面的代码能够绘制出每个子控件:
for (int i = 0; i < Controls.Count; i++)
{
current = (BookCounter) Controls[i];
totalInquiries += current.Count;
output.Write("<TR><TD align='left'>" +
current.BookName + "</TD>");
output.RenderBeginTag("TD");
current.RenderControl(output);
output.RenderEndTag( );
output.Write("</tr>");
}
相应的VB.NET代码为:
For Each current in Controls
totalInquiries += current.Count
output.Write("<TR><TD align='left'>" & _
current.BookName + "</TD>")
output.RenderBeginTag("TD")
current.RenderControl(output)
output.RenderEndTag( )
output.Write("</tr>")
Next
局部的BookCounter对象型current变量的值被赋为控件集合中的每个对象:
for (int i = 0; i < Controls.Count; i++)
{
current = (BookCounter) Controls[i];
然后就可以利用下面的代码计算totalInquiries:
totalInquiries += current.Count;
然后我们就可以继续绘制对象了。通过利用current的BookName特性,HtmlTextWriter可以用来创建一个行并显示书的名字:
output.Write("<TR><TD align='left'>" + current.BookName + "</TD>");
接下来,我们绘制一个TD标记,在该标记内我们让BookCounter对象绘制自己。最后,使用RenderEndTag来绘制一个结束性的TD标记,并使用HTMLTextWriter的Write方法绘制行结束标记。
output.RenderBeginTag("TD");
current.RenderControl(output);
output.RenderEndTag( );
output.Write("</tr>");
下面的代码使被包含的控件自己绘制自己:
current.RenderControl(output);
上面的代码会调用BookCounter的Render方法。由于我们没有覆盖该方法,调用的仍然是基础类中的Render 类。唯一被包含的对象是CountedButton,由于我们没有覆盖CountedButton中的Render方法,在绘制该按钮时调用的仍然是基础类Button中的Render方法。
查询总数的绘制
所有的子控件绘制完毕后,BookInquiryList将创建一个新的行,显示总的查询次数:
output.Write("<TR><TD colspan='2' align='center'> " +
" Total Inquiries: " +
totalInquiries + "</TD></TR>");
至此,作为合同编程人员的你,就完成了任务。
结束语
在上面的文章中,我们讨论了开发定制控件的三种途径,尤其是通过一个例子,详细论述了建立复合控件的过程。在具体的工作中,灵活地运用这三种方式创建定制控件,使我们的开发工作事半功倍。
标签:
本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com