ASP.NET 定制控件的开发(二)
<p ><ccid_nobr><strong>复合控件的创建</strong></ccid_nobr><p > 创建定制控件的第三种方法是组合二个或二个以上的现有的控件。在下面的例子中,读者将以合同编程人员的身份出现,而我则是客户,我希望读者能够开发一个稍微复杂一些的控件,使我能够用来记录收到的对我的书的询价。<p > 作为客户,我将要求读者开发一个控件,使我能够输入一本或多本书籍,每当点击一本书时,控件就会记录下对该书的点击次数,如下图所示:<p ><ccid_nobr><center><imgsrc="http://www.hh010.com/upload_files/article/244/9_qh2iqi15279.gif"></center></ccid_nobr><p > 这一程序的.aspx文件如下所示,除@ Page命令外,该程序的C#和VB程序是相同的:<p ><ccid_nobr><strong>利便控件的.aspx文件</strong></ccid_nobr><p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> <%@Pagelanguage="c#" <br>Codebehind="WebForm1.aspx.cs" <br>AutoEventWireup="false" <br>Inherits="CustomControlWebPage.WebForm1"%><br><br><%@RegisterTagPrefix="OReilly"Namespace="CustomControls"Assembly="CustomControls"%><br><br><!DOCTYPEHTMLPUBLIC"-//W3C//DTDHTML4.0Transitional//EN"><br><HTML><br> <HEAD><br><metacontent="MicrosoftVisualStudio7.0"name=GENERATOR><br><metacontent=C#name=CODE_LANGUAGE><br><metacontent="JavaScript(ECMAScript)"name=vs_defaultClientScript><br><metacontent=http://schemas.microsoft.com/intellisense/ie5name=vs_targetSchema><br> </HEAD><br><bodyMS_POSITIONING="GridLayout"><br><formid=Form1method=postrunat="server"><br><br> <OReilly:BookInquiryList<br> Runat="Server"<br> id="bookInquiry1"><br><br> <OReilly:BookCounter <br> Runat="server" <br> BookName="ProgrammingASP.NET" <br> ID="Bookcounter1"/><br><br> <OReilly:BookCounter <br> Runat="server" <br> BookName="ProgrammingC#" <br> ID="Bookcounter2"/><br><br> <OReilly:BookCounter <br> Runat="server" <br> BookName="TeachYourselfC++21Days" <br> ID="BookCounter3"/><br><br> <OReilly:BookCounter <br> Runat="server" <br> BookName="TeachYourselfC++24Hours" <br> ID="Bookcounter4"/><br><br> <OReilly:BookCounter <br> Runat="server" <br> BookName="CloudsToCode" <br> ID="Bookcounter5"/><br><br> <OReilly:BookCounter <br> Runat="server" <br> BookName="C++FromScratch" <br> ID="Bookcounter6"/><br><br> <OReilly:BookCounter <br> Runat="server" <br> BookName="WebClassesFromScratch" <br> ID="Bookcounter7"/><br><br> <OReilly:BookCounter <br> Runat="server" <br> BookName="XMLWebDocumentsFromSrcatch" <br> ID="Bookcounter8"/><br><br> </OReilly:BookInquiryList> <br><br></FORM> <br> </body><br></HTML></td></tr></table></ccid_nobr><p > 在上面的代码中需要注意的是,BookInquiryList组件中包含许多BookCounter元素,其中有一个BookCounter元素是对应着我希望记录的书籍。这个控件非常灵活,我可以对任意数量的书进行记录。每个BookCounter元素有一个用来显示被记录书籍名字的BookName属性。<p > 从图9中我们可以看到,每本书都由一个CountedButton定制控件进行记录,但.aspx文件中没有CountedButton控件的定义,它被完整地封装在了BookCounter定制控件中。<p >整个体系结构如下所示:<p ><ccid_nobr><ol><li>BookInquiry利便控件是由WebControl派生的,实现了INamingContainer,在下面我们会提到。 </li><li>BookInquiry控件有一个由Control类派生的Controls特性。</li><li>在控件集合中有数量不等的BookCounter控件。</li><li>BookCounter本身也是一个由WebControl派生得来的复合控件,WebControl也实现了INamingContainer。</li><ol><li>BookContainer的每个实例有二个特性:BookName和Count。</li><li>Name特性是由Viewstate支持的,而且通过.aspx文件中的BookName BookName初始化。</li><li>Count特性授权给private性质的CountedButton对象</li></ol></ol></ccid_nobr><p > 使用BookInquiry对象的目的有二个:它是BookCounter对象的容器;它负责绘制它本身并确保它包含的BookCounter对象能够按需求绘制自己。<p ><ccid_nobr><strong>CountedButton控件的修改</strong></ccid_nobr><p > 我们需要对CountedButton控件进行一些很小的修改,下面分别是C#和VB.NET版的CountedButton控件。<p >修改后的CountedButton.cs文件<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><br>usingSystem;<br>usingSystem.Web.UI;<br>usingSystem.Web.UI.WebControls;<br>usingSystem.ComponentModel;<br><br>namespaceCustomControls<br>{<br> //由System.Web.UI.WebControls.Button派生出的定制控件<br> publicclassCountedButton:System.Web.UI.WebControls.Button<br> {<br><br> privatestringdisplayString;<br><br> //缺省的构造器<br> publicCountedButton( )<br> {<br> displayString="clicks";<br> InitValues( );<br> }<br><br> //重载,显示字符串<br> publicCountedButton(stringdisplayString)<br> {<br> this.displayString=displayString;<br> InitValues( );<br> }<br><br> //由构造器调用的函数<br> privatevoidInitValues( )<br> {<br> if(ViewState["Count"]==null)<br> ViewState["Count"]=0;<br> this.Text="Clickme";<br> }<br><br> //Count是ViewState中的一个特性<br> publicintCount <br> {<br> get<br> {<br> //在构造器中初始化,不能是NULL<br> return(int)ViewState["Count"];<br> }<br><br> set<br> {<br> ViewState["Count"]=value;<br> }<br> }<br><br> //覆盖OnClick事件处理程序,增大Count变量的值,并在更新按钮上的文本后调用基础类中的方法<br> protectedoverridevoidOnClick(EventArgse)<br> {<br> ViewState["Count"]= ((int)ViewState["Count"])+1;<br> this.Text=ViewState["Count"]+""+displayString;<br> base.OnClick(e);<br> }<br> }<br>} </td></tr></table></ccid_nobr><p >修改后的CountedButton.vb文件<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><br>ImportsSystem.ComponentModel<br>ImportsSystem.Web.UI<br>ImportsSystem.Web.UI.WebControls<br><br>'从System.Web.UI.WebControls.Button中派生的定制控件<br>PublicClassCountedButton<br> InheritsSystem.Web.UI.WebControls.Button<br><br> PrivatedisplayStringAsString<br><br> '构造器对ViewState进行初始化<br> PublicSubNew( )<br> displayString="clicks"<br> Init( )<br> EndSub<br><br> '重载,显示字符串<br> PublicSubNew(ByValdisplayStringAsString)<br> Me.displayString=displayString<br> Init( )<br> EndSub<br><br> '由构造器调用的方法<br> PrivateShadowsSubInit( )<br> IfViewState("Count")=IsNothingThen<br> ViewState("Count")=0<br> Me.Text="Clickme"<br> EndIf<br> EndSub<br><br> 'Count是ViewState中的一个特性<br> PublicPropertyCount( )AsInteger<br> Get<br> ReturnCInt(ViewState("Count"))<br> EndGet<br> Set(ByValValueAsInteger)<br> ViewState("Count")=Value<br> EndSet<br> EndProperty<br><br> ' 覆盖OnClick事件处理程序,增大Count变量的值,并在更新按钮上的文本后调用基础类中的方法<br> ProtectedOverridesSubOnClick(ByValeAsEventArgs)<br> ViewState("Count")=CInt(ViewState("Count"))+1<br> Me.Text=CStr(ViewState("Count")&""&displayString<br> MyBase.OnClick(e)<br> EndSub<br>EndClass?) </td></tr></table></ccid_nobr><p > 由于我们希望按钮上显示“5 Inquiries”而不是“5 clicks”字符串,因此必须修改OnClick方法中修改按钮字符串文本的代码:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> this.Text = ViewState["Count"] + " " + displayString; </td></tr></table></ccid_nobr><p > 相应的VB.NET代码是:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> Me.Text = ViewState("Count") & " " & displayString </td></tr></table></ccid_nobr><p > 我们还使用了一个private性质的成员变量displayString来存储传递给构造器中的数值:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> private string displayString; </td></tr></table></ccid_nobr><p >在VB.NET中的代码为:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> Private displayString As String </td></tr></table></ccid_nobr><p > 我们必须在构造器中设置这一字符串。为了保护已经使用了缺省的构造器的代码,我们必须重载构造器,新增加一个带有字符串参数的构造器:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> publicCountedButton(stringdisplayString)<br> {<br> this.displayString=displayString;<br> Init( );<br> }<br> </td></tr></table></ccid_nobr><p >在VB.NET中的代码是:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> PublicSubNew(ByValdisplayStringAsString)<br> Me.displayString=displayString<br> Initialize( )<br>EndSub<br> </td></tr></table></ccid_nobr><p > 我们可以对缺省的构造器进行修改,将displayString成员变量有值设置为一个合理的缺省值。其C#代码如下:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> publicCountedButton( )<br>{<br> displayString="clicks";<br> InitValues( );<br>} </td></tr></table></ccid_nobr><p >相应的VB.NET代码是:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><br>PublicSubNew( )<br> displayString="clicks"<br> Init( )<br>EndSub</p> </td></tr></table></ccid_nobr><p > 二个构造器的代码都没有考虑private性质的辅助方法Init,它能够保证Count特性被初始化为0,并设置最初时按钮显示的字符串:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> privatevoidInit( )<br>{<br> if(ViewState["Count"]==null)<br> ViewState["Count"]=0;<br> this.Text="Clickme";<br>} </td></tr></table></ccid_nobr><p >在VB.NET中,相应的代码为:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><br>PrivateShadowsSubInit( )<br> IfViewState("Count")=NothingThen<br> ViewState("Count")=0<br> Me.Text="Clickme"<br> EndIf<br>EndSub </td></tr></table></ccid_nobr><p > 作了上述的修改后,我们就可以在第一个复合控件━━BookCounter中使用CountedButton了。<p ><ccid_nobr><strong>BookCounter复合控件的创建</strong></ccid_nobr><p > BookCounter复合控件用于记录和显示对某一本书查询的次数,下面分别是C#和VB.NET版的BookCounter复合控件的源代码:<p >C#版本的BookCounter控件源文件:BookCounter.cs <p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><br>usingSystem;<br>usingSystem.Web.UI;<br>usingSystem.Web.UI.WebControls;<br>usingSystem.ComponentModel;<br><br>namespaceCustomControls<br>{<br> publicclassBookCounter: <br> System.Web.UI.WebControls.WebControl, <br> INamingContainer<br> {<br><br> //初始化按钮成员<br> CountedButtonbtn=newCountedButton("inquiries");<br><br> publicstringBookName <br> {<br> get<br> {<br> return(string)ViewState["BookName"];<br> }<br><br> set<br> {<br> ViewState["BookName"]=value;<br> }<br> }<br><br> publicintCount<br> {<br> get<br> {<br> returnbtn.Count;<br> }<br> set<br> {<br> btn.Count=value;<br> }<br> }<br><br> publicvoidReset( )<br> {<br> btn.Count=0;<br> }<br><br> protectedoverridevoidCreateChildControls( )<br> {<br> Controls.Add(btn);<br> }<br> }<br>} </td></tr></table></ccid_nobr><p >VB.NET版的BookCounter控件的源代码: BookCounter.vb <p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><br>ImportsSystem<br>ImportsSystem.Web.UI<br>ImportsSystem.Web.UI.WebControls<br>ImportsSystem.ComponentModel<br><br>PublicClassBookCounter<br> InheritsSystem.Web.UI.WebControls.WebControl<br> ImplementsINamingContainer<br><br> '初始化按钮成员<br> PublicbtnAsCountedButton=NewCountedButton("inquiries")<br><br> PublicPropertyBookName( )AsString<br> Get<br> ReturnCStr(ViewState("BookName"))<br> EndGet<br> Set(ByValValueAsString)<br> ViewState("BookName")=Value<br> EndSet<br> EndProperty<br><br> PublicPropertyCount( )AsInteger<br> Get<br> Returnbtn.Count<br> EndGet<br> Set(ByValValueAsInteger)<br> btn.Count=Value<br> EndSet<br> EndProperty<br><br> PublicSubReset( )<br> btn.Count=0<br> EndSub<br><br> ProtectedOverridesSubCreateChildControls( )<br> Controls.Add(btn)<br> EndSub<br><br>EndClass<br> </td></tr></table></ccid_nobr><p ><ccid_nobr><strong>INamingContainer</strong></ccid_nobr><p > BookCounter类中首先需要注意的是它实现了INamingContainer界面,这是一个没有方法的“记分器”界面。这一界面的目的是识别创建新的ID名字空间的容器控件,保证所有的子控件都有对于应用程序是唯一的ID。<p ><ccid_nobr><strong>包含CountedButton</strong></ccid_nobr><p > BookCounter类包含有CountedButton的一个实例:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> CountedButton btn = new CountedButton("inquiries"); </td></tr></table></ccid_nobr><p >或:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> Public btn As CountedButton = New CountedButton("inquiries") </td></tr></table></ccid_nobr><p >btn成员是在由System.Control继承的CreateChildControls方法中被初始化的:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> protectedoverridevoidCreateChildControls( )<br>{<br> Controls.Add(btn);<br>}<br> </td></tr></table></ccid_nobr><p >在VB.NET中,相应的代码为:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><br>ProtectedOverridesSubCreateChildControls( )<br> Controls.Add(btn)<br>EndSub </td></tr></table></ccid_nobr><p > CreateChildControls是在绘制的准备工作时被调用的,它使BookCounter类能够添加btn对象作为被包含的控件。<p > BookCounter无需覆盖Render方法,它唯一需要绘制的是CountedButton。Render缺省的操作是绘制所有的子控件,因此无需对它作任何修改就能完成其功能。<p > BookCounter还有二个特性:BookName和Count。BookName是在控件中显示的一个字符串,而且通过ViewState进行管理,其C#代码如下所示:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><br>publicstringBookName <br>{<br> get<br> {<br> return(string)ViewState["BookName"];<br> }<br><br> set<br> {<br> ViewState["BookName"]=value;<br> }<br>} </td></tr></table></ccid_nobr><p >相应的VB.NET源代码为:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><br>PublicPropertyBookName( )AsString<br> Get<br> ReturnCStr(ViewState("BookName"))<br> EndGet<br> Set(ByValValueAsString)<br> ViewState("BookName")=Value<br> EndSet<br>EndProperty </td></tr></table></ccid_nobr><p > Count是对一本特定的书查询的数量,记录这一数量的任务由CountedButton完成。其C#代码如下所示:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> publicintCount<br>{<br> get<br> {<br> returnbtn.Count;<br> }<br> set<br> {<br> btn.Count=value;<br> }<br>} </td></tr></table></ccid_nobr><p >相应的VB.NET源代码为:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><br>PublicPropertyCount( )AsInteger<br> Get<br> Returnbtn.Count<br> EndGet<br> Set(ByValValueAsInteger)<br> btn.Count=Value<br> EndSet<br>EndProperty </td></tr></table></ccid_nobr><p > 无需将该值放在ViewState中,因为按钮本身就可以对其数据进行管理。<p ><ccid_nobr><strong> BookInquiryList复合控件的创建</strong></ccid_nobr><p > 每个BookCounter对象都被包含在BookInquiryList控件集合中,该控件没有特性,只有一个Render方法,其C#和VB.NET代码如下所示:<p ><ccid_nobr><I> BookInquiryList的C#代码:</I></ccid_nobr><p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><br><br>publicclassBookInquiryList:System.Web.UI.WebControls.WebControl,INamingContainer<br>{<br><br> protectedoverridevoidRender(HtmlTextWriteroutput)<br> {<br> inttotalInquiries=0;<br> BookCountercurrent;<br><br> //输出头部<br> output.Write("<Tableborder='1'width='90%'cellpadding='1'"+<br> "cellspacing='1'align='center'>");<br> output.Write("<TR><TDcolspan='2'align='center'>");<br> output.Write("<B>Inquiries</B></TD></TR>");<br><br> //如果没有被包含的控件,输出相应的信息<br> if(Controls.Count==0)<br> {<br> output.Write("<TR><TDcolspan='2'>align='center'");<br> output.Write("<B>Nobookslisted</B></TD></TR>"); <br> }<br> //否则绘制每个包含的控件<br> else<br> {<br> //遍历控件集,显示每个控件的书名,然后让每个被包含的控件自己绘制自己<br> for(inti=0;i<Controls.Count;i++)<br> {<br> current=(BookCounter)Controls;<br> totalInquiries+=current.Count;<br> output.Write("<TR><TDalign='left'>"+<br> current.BookName+"</TD>");<br> output.RenderBeginTag("TD");<br> current.RenderControl(output);<br> output.RenderEndTag( ); <br> output.Write("</tr>");<br> }<br> output.Write("<TR><TDcolspan='2'align='center'>"+<br> "TotalInquiries:"+<br> totalInquiries+"</TD></TR>");<br> }<br> output.Write("</TABLE>");<br> }<br>} </td></tr></table></ccid_nobr><p ><ccid_nobr><I> BookInquiryList的VB.NET源代码:</I></ccid_nobr><p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> ImportsSystem.ComponentModel<br>ImportsSystem.Web.UI<br><br><ControlBuilder(GetType(BookCounterBuilder)),ParseChildren(False)>_<br>PublicClassBookInquiryList<br> InheritsSystem.Web.UI.WebControls.WebControl<br> ImplementsINamingContainer<br><br> ProtectedOverridesSubRender(ByValoutputAsHtmlTextWriter)<br><br> DimtotalInquiriesAsInteger=0<br><br> '输出头部<br> output.Write("<Tableborder='1'width='90%'cellpadding='1'"&_<br> "cellspacing='1'align='center'>")<br> output.Write("<TR><TDcolspan='2'align='center'>")<br> output.Write("<B>Inquiries</B></TD></TR>")<br><br> '如果没有被包含的控件,输出相应的信息<br> IfControls.Count=0Then<br> output.Write("<TR><TDcolspan='2'>align='center'")<br> output.Write("<B>Nobookslisted</B></TD></TR>")<br> '否则绘制每个包含的控件<br> Else<br> '遍历控件集,显示每个控件的书名,然后让每个被包含的控件自己绘制自己<br> DimcurrentAsBookCounter<br><br> ForEachcurrentInControls<br> totalInquiries+=current.Count<br> output.Write("<TR><TDalign='left'>"&_<br> current.BookName+"</TD>")<br> output.RenderBeginTag("TD")<br> current.RenderControl(output)<br> output.RenderEndTag() <br> output.Write("</tr>")<br> Next<br> DimstrTotalInquiriesAsString<br> strTotalInquiries=totalInquiries.ToString<br> output.Write("<TR><TDcolspan='2'align='center'>"&_<br> "TotalInquiries:"&_<br> CStr(strTotalInquiries)&"</TD></TR>")<br> EndIf<br> output.Write("</TABLE>")<br> EndSub<br><br>EndClass<br><br>FriendClassBookCounterBuilder<br> InheritsControlBuilder<br><br> PublicOverridesFunctionGetChildControlType(_<br> ByValtagNameAsString,ByValattributesAsIDictionary)AsType<br> IftagName="BookCounter"Then<br> DimxAsBookCounter<br> Returnx.GetType<br> Else<br> ReturnNothing<br> EndIf<br> EndFunction<br><br> PublicOverridesSubAppendLiteralString(ByValsAsString)<br> EndSub<br><br>EndClass </td></tr></table></ccid_nobr><p ><ccid_nobr><strong>ControlBuilder和ParseChildren属性</strong></ccid_nobr><p > BookCounter类必须与BookInquiryClass联合使用,ASP.NET才能将.aspx网页中的元素转换为适当的代码。完成这一工作需要用到ControlBuilder属性:<p ><p > ControlBuilderAttribute的参数是一个通过传递BookCounterBuilder类获取的Type对象。下面是使用C#和VB.NET编写的BookCounterBuilder源代码:<p ><ccid_nobr><I>C#语言版的BookCounterBuilder </I></ccid_nobr><p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> internalclassBookCounterBuilder:ControlBuilder<br>{<br> publicoverrideTypeGetChildControlType(<br> stringtagName,IDictionaryattributes)<br> {<br> if(tagName=="BookCounter")<br> returntypeof(BookCounter);<br> else<br> returnnull;<br> }<br><br> publicoverridevoidAppendLiteralString(strings)<br> {<br> }<br>}<br><br>VB.NET版的BookCounterBuilder <br><br>FriendClassBookCounterBuilder<br> InheritsControlBuilder<br><br> PublicOverridesFunctionGetChildControlType(_<br> ByValtagNameAsString,ByValattributesAsIdictionary)AsType<br> IftagName="BookCounter"Then<br> DimxAsBookCounter<br> Returnx.GetType<br> Else<br> ReturnNothing<br> EndIf<br> EndFunction<br><br> PublicOverridesSubAppendLiteralString(ByValsAsString)<br> EndSub<br><br>EndClass </td></tr></table></ccid_nobr><p > ASP.NET将使用由ControlBuilder中派生出的BookCounterBuilder来判断由BookCounter标记批指示的对象的类型。通过这种结合,每个BookCounter对象才能被实例化,并添加到BookInquiryClass的控件集合中。<p > 第二个参数ParseChildren必须被设置成false,让ASP.NET知道我们已经对子属性进行了处理,无需再对它进行进一步地解析了。false值表明子属性不是外部对象的特性,而仅仅是子控件的特性。<p ><ccid_nobr><strong> Render</strong></ccid_nobr><p > BookInquiryClass中的唯一方法是覆盖Render的方法,Render的作用是使用由每个BookCounter子控件管理的数据绘制图9中的表格。<p > BookInquiryClass提供了一个总的查询数字,如下图所示:<p ><ccid_nobr><center><imgsrc="http://www.hh010.com/upload_files/article/244/9_tawivi15280.gif"></center></ccid_nobr><p >Figure 14-16. Total inquiries displayed <p > 通过将totalInquiries整型变量初始化为0,然后依次遍历每个控件,将其Count特性与totalInquiries相加,就可以得到查询的总数了。除了C#中语句结束时的分号为,实现这一功能的C#和VB.NET语句相同:<p >totalInquiries += current.Count;<p ><ccid_nobr><strong>表现输出内容</strong></ccid_nobr><p > 通过遍历每个控件,下面的代码能够绘制出每个子控件:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> for(inti=0;i<Controls.Count;i++)<br>{<br> current=(BookCounter)Controls;<br> totalInquiries+=current.Count;<br> output.Write("<TR><TDalign='left'>"+<br> current.BookName+"</TD>");<br> output.RenderBeginTag("TD");<br> current.RenderControl(output);<br> output.RenderEndTag( ); <br> output.Write("</tr>");<br>} </td></tr></table></ccid_nobr><p >相应的VB.NET代码为:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> ForEachcurrentinControls<br> totalInquiries+=current.Count<br> output.Write("<TR><TDalign='left'>"&_<br> current.BookName+"</TD>")<br> output.RenderBeginTag("TD")<br> current.RenderControl(output)<br> output.RenderEndTag( ) <br> output.Write("</tr>")<br>Next<br></td></tr></table></ccid_nobr><p > 局部的BookCounter对象型current变量的值被赋为控件集合中的每个对象:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> for(inti=0;i<Controls.Count;i++)<br>{<br> current=(BookCounter)Controls;<br> </td></tr></table></ccid_nobr><p >然后就可以利用下面的代码计算totalInquiries:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> totalInquiries += current.Count; </td></tr></table></ccid_nobr><p > 然后我们就可以继续绘制对象了。通过利用current的BookName特性,HtmlTextWriter可以用来创建一个行并显示书的名字:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><ccid_code>output.Write("<TR><TD align='left'>" + current.BookName + "</TD>");</ccid_code></td></tr></table></ccid_nobr><p > 接下来,我们绘制一个TD标记,在该标记内我们让BookCounter对象绘制自己。最后,使用RenderEndTag来绘制一个结束性的TD标记,并使用HTMLTextWriter的Write方法绘制行结束标记。<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> output.RenderBeginTag("TD");<br>current.RenderControl(output);<br>output.RenderEndTag( ); <br>output.Write("</tr>"); </td></tr></table></ccid_nobr><p >下面的代码使被包含的控件自己绘制自己:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> current.RenderControl(output); </td></tr></table></ccid_nobr><p > 上面的代码会调用BookCounter的Render方法。由于我们没有覆盖该方法,调用的仍然是基础类中的Render 类。唯一被包含的对象是CountedButton,由于我们没有覆盖CountedButton中的Render方法,在绘制该按钮时调用的仍然是基础类Button中的Render方法。<p ><ccid_nobr><strong> 查询总数的绘制</strong></ccid_nobr><p > 所有的子控件绘制完毕后,BookInquiryList将创建一个新的行,显示总的查询次数:<p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="0" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"> output.Write("<TR><TDcolspan='2'align='center'>"+<br> "TotalInquiries:"+ <br> totalInquiries+"</TD></TR>"); </td></tr></table></ccid_nobr><p > 至此,作为合同编程人员的你,就完成了任务。<p ><ccid_nobr><strong>结束语</strong></ccid_nobr><p > 在上面的文章中,我们讨论了开发定制控件的三种途径,尤其是通过一个例子,详细论述了建立复合控件的过程。在具体的工作中,灵活地运用这三种方式创建定制控件,使我们的开发工作事半功倍。<p ><p >(责任编辑 <ccid_nobr>赵正北</ccid_nobr>) <p align="center"></p></p>
页:
[1]