赞助广告 赞助广告
  当前位置:网络学院冲浪宝典QQ专栏 → ASP.NET 定制控件的开发(二)
ASP.NET 定制控件的开发(二)
日期:2002年5月12日 作者:刘彦青 译 人气: 查看:[大字体 中字体 小字体]
复合控件的创建

创建定制控件的第三种方法是组合二个或二个以上的现有的控件。在下面的例子中,读者将以合同编程人员的身份出现,而我则是客户,我希望读者能够开发一个稍微复杂一些的控件,使我能够用来记录收到的对我的书的询价。

作为客户,我将要求读者开发一个控件,使我能够输入一本或多本书籍,每当点击一本书时,控件就会记录下对该书的点击次数,如下图所示:



这一程序的.aspx文件如下所示,除@ Page命令外,该程序的C#和VB程序是相同的:

利便控件的.aspx文件

<%@  Page  language="c#" 
Codebehind="WebForm1.aspx.cs" 
AutoEventWireup="false" 
Inherits="CustomControlWebPage.WebForm1"  %>
 
  <%@  Register  TagPrefix="OReilly"  Namespace="CustomControls"  Assembly="CustomControls"  %>
 
  <!DOCTYPE  HTML  PUBLIC  "-//W3C//DTD  HTML  4.0  Transitional//EN"  >
  <HTML>
      <HEAD>
  <meta  content="Microsoft  Visual  Studio  7.0"  name=GENERATOR>
  <meta  content=C#  name=CODE_LANGUAGE>
  <meta  content="JavaScript  (ECMAScript)"  name=vs_defaultClientScript>
  <meta  content=http://schemas.microsoft.com/intellisense/ie5  name=vs_targetSchema>
      </HEAD>
  <body  MS_POSITIONING="GridLayout">
  <form  id=Form1  method=post  runat="server">
 
              <OReilly:BookInquiryList
              Runat="Server"
              id="bookInquiry1">
 
                    <OReilly:BookCounter 
                  Runat="server" 
                  BookName="Programming  ASP.NET" 
                  ID="Bookcounter1"/>

                  <OReilly:BookCounter 
                  Runat="server" 
                  BookName="Programming  C#" 
                  ID="Bookcounter2"  />
 
                    <OReilly:BookCounter 
                  Runat="server" 
                  BookName="Teach  Yourself  C++  21  Days" 
                  ID="BookCounter3"  />
 
                    <OReilly:BookCounter 
                  Runat="server" 
                  BookName="Teach  Yourself  C++  24  Hours" 
                  ID="Bookcounter4"  />
 
                    <OReilly:BookCounter 
                  Runat="server" 
                  BookName="Clouds  To  Code" 
                  ID="Bookcounter5"  />
 
                    <OReilly:BookCounter 
                  Runat="server" 
                  BookName="C++  From  Scratch" 
                  ID="Bookcounter6"  />
 
                    <OReilly:BookCounter 
                  Runat="server" 
                  BookName="Web  Classes  From  Scratch" 
                  ID="Bookcounter7"  />
 
                    <OReilly:BookCounter 
                  Runat="server" 
                  BookName="XML  Web  Documents  From  Srcatch" 
                  ID="Bookcounter8"  />
 
              </OReilly:BookInquiryList> 

</FORM> 
    </body>
</HTML>


在上面的代码中需要注意的是,BookInquiryList组件中包含许多BookCounter元素,其中有一个BookCounter元素是对应着我希望记录的书籍。这个控件非常灵活,我可以对任意数量的书进行记录。每个BookCounter元素有一个用来显示被记录书籍名字的BookName属性。

从图9中我们可以看到,每本书都由一个CountedButton定制控件进行记录,但.aspx文件中没有CountedButton控件的定义,它被完整地封装在了BookCounter定制控件中。

整个体系结构如下所示:

  1. BookInquiry利便控件是由WebControl派生的,实现了INamingContainer,在下面我们会提到。
  2. BookInquiry控件有一个由Control类派生的Controls特性。
  3. 在控件集合中有数量不等的BookCounter控件。
  4. BookCounter本身也是一个由WebControl派生得来的复合控件,WebControl也实现了INamingContainer。
    1. BookContainer的每个实例有二个特性:BookName和Count。
    2. Name特性是由Viewstate支持的,而且通过.aspx文件中的BookName BookName初始化。
    3. Count特性授权给private性质的CountedButton对象


使用BookInquiry对象的目的有二个:它是BookCounter对象的容器;它负责绘制它本身并确保它包含的BookCounter对象能够按需求绘制自己。

CountedButton控件的修改

我们需要对CountedButton控件进行一些很小的修改,下面分别是C#和VB.NET版的CountedButton控件。

修改后的CountedButton.cs文件


using  System;
  using  System.Web.UI;
  using  System.Web.UI.WebControls;
  using  System.ComponentModel;
 
  namespace  CustomControls
  {
        //  由System.Web.UI.WebControls.Button派生出的定制控件
        public  class  CountedButton  :  System.Web.UI.WebControls.Button
        {
 
              private  string  displayString;
 
              //  缺省的构造器
              public  CountedButton(    )
              {
                    displayString  =  "clicks";
                    InitValues(    );
              }
 
              //  重载,显示字符串
              public  CountedButton(string  displayString)
              {
                    this.displayString  =  displayString;
                    InitValues(    );
              }
 
              //  由构造器调用的函数
              private  void  InitValues(    )
              {
                    if  (ViewState["Count"]  ==  null)
                          ViewState["Count"]  =  0;
                    this.Text  =  "Click  me";
              }
 
              //  Count是ViewState中的一个特性
              public  int  Count 
            {
                  get
                  {
                        //  在构造器中初始化,不能是NULL
                          return  (int)  ViewState["Count"];
                    }
 
                    set
                    {
                          ViewState["Count"]  =  value;
                    }
              }
 
              //  覆盖OnClick事件处理程序,增大Count变量的值,并在更新按钮上的文本后调用基础类中的方法
              protected  override  void  OnClick(EventArgs  e)
              {
                    ViewState["Count"]  =    ((int)ViewState["Count"])  +  1;
                    this.Text  =  ViewState["Count"]  +  "  "  +  displayString;
                    base.OnClick(e);
              }
        }
  }


修改后的CountedButton.vb文件


Imports  System.ComponentModel
  Imports  System.Web.UI
  Imports  System.Web.UI.WebControls
 
  '  从System.Web.UI.WebControls.Button中派生的定制控件
  Public  Class  CountedButton
        Inherits  System.Web.UI.WebControls.Button
 
        Private  displayString  As  String
 
        '  构造器对ViewState进行初始化
        Public  Sub  New(    )
              displayString  =  "clicks"
              Init(    )
        End  Sub
 
      '  重载,显示字符串
        Public  Sub  New(ByVal  displayString  As  String)
              Me.displayString  =  displayString
              Init(    )
        End  Sub
 
        '  由构造器调用的方法
        Private  Shadows  Sub  Init(    )
              If  ViewState("Count")  =  Is  Nothing  Then
                    ViewState("Count")  =  0
                    Me.Text  =  "Click  me"
              End  If
        End  Sub
 
        '  Count是ViewState中的一个特性
        Public  Property  Count(    )  As  Integer
              Get
                    Return  CInt(ViewState("Count"))
              End  Get
              Set(ByVal  Value  As  Integer)
                    ViewState("Count")  =  Value
              End  Set
        End  Property
 
        '    覆盖OnClick事件处理程序,增大Count变量的值,并在更新按钮上的文本后调用基础类中的方法
        Protected  Overrides  Sub  OnClick(ByVal  e  As  EventArgs)
              ViewState("Count")  =  CInt(ViewState("Count"))  +  1
              Me.Text  =  CStr(ViewState("Count")  &  "  "  &  displayString
              MyBase.OnClick(e)
        End  Sub
  End  Class?)


由于我们希望按钮上显示“5 Inquiries”而不是“5 clicks”字符串,因此必须修改OnClick方法中修改按钮字符串文本的代码:

this.Text = ViewState["Count"] + " " + displayString;


相应的VB.NET代码是:

Me.Text = ViewState("Count") & " " & displayString


我们还使用了一个private性质的成员变量displayString来存储传递给构造器中的数值:

private string displayString;


在VB.NET中的代码为:

Private displayString As String


我们必须在构造器中设置这一字符串。为了保护已经使用了缺省的构造器的代码,我们必须重载构造器,新增加一个带有字符串参数的构造器:

public  CountedButton(string  displayString)
    {
          this.displayString  =  displayString;
          Init(    );
    }
 


在VB.NET中的代码是:

Public  Sub  New(ByVal  displayString  As  String)
        Me.displayString  =  displayString
        Initialize(    )
  End  Sub
 


我们可以对缺省的构造器进行修改,将displayString成员变量有值设置为一个合理的缺省值。其C#代码如下:

public  CountedButton(    )
  {
        displayString  =  "clicks";
        InitValues(    );
  }


相应的VB.NET代码是:


Public  Sub  New(    )
        displayString  =  "clicks"
        Init(    )
  End  Sub

 


二个构造器的代码都没有考虑private性质的辅助方法Init,它能够保证Count特性被初始化为0,并设置最初时按钮显示的字符串:

private  void  Init(    )
  {
        if  (ViewState["Count"]  ==  null)
              ViewState["Count"]  =  0;
        this.Text  =  "Click  me";
  }


在VB.NET中,相应的代码为:


Private  Shadows  Sub  Init(    )
        If  ViewState("Count")  =  Nothing  Then
              ViewState("Count")  =  0
              Me.Text  =  "Click  me"
        End  If
  End  Sub


作了上述的修改后,我们就可以在第一个复合控件━━BookCounter中使用CountedButton了。

BookCounter复合控件的创建

BookCounter复合控件用于记录和显示对某一本书查询的次数,下面分别是C#和VB.NET版的BookCounter复合控件的源代码:

C#版本的BookCounter控件源文件:BookCounter.cs


using  System;
  using  System.Web.UI;
  using  System.Web.UI.WebControls;
  using  System.ComponentModel;
 
  namespace  CustomControls
  {
        public  class  BookCounter  : 
            System.Web.UI.WebControls.WebControl, 
            INamingContainer
      {

            //  初始化按钮成员
              CountedButton  btn  =  new  CountedButton("inquiries");
 
              public  string  BookName 
            {
                  get
                  {
                        return  (string)  ViewState["BookName"];
                    }
 
                    set
                    {
                          ViewState["BookName"]  =  value;
                    }
              }
 
              public  int  Count
              {
                    get
                    {
                          return  btn.Count;
                    }
                    set
                    {
                          btn.Count  =  value;
                    }
              }
 
              public  void  Reset(    )
              {
                    btn.Count  =  0;
              }
 
              protected  override  void  CreateChildControls(    )
              {
                    Controls.Add(btn);
              }
        }
  }


VB.NET版的BookCounter控件的源代码: BookCounter.vb


Imports  System
  Imports  System.Web.UI
  Imports  System.Web.UI.WebControls
  Imports  System.ComponentModel
 
  Public  Class  BookCounter
        Inherits  System.Web.UI.WebControls.WebControl
        Implements  INamingContainer
 
        '  初始化按钮成员
        Public  btn  As  CountedButton  =  New  CountedButton("inquiries")
 
        Public  Property  BookName(    )  As  String
              Get
                    Return  CStr(ViewState("BookName"))
              End  Get
              Set(ByVal  Value  As  String)
                    ViewState("BookName")  =  Value
              End  Set
        End  Property
 
        Public  Property  Count(    )  As  Integer
              Get
                    Return  btn.Count
              End  Get
              Set(ByVal  Value  As  Integer)
                    btn.Count  =  Value
              End  Set
        End  Property
 
        Public  Sub  Reset(    )
              btn.Count  =  0
        End  Sub
 
        Protected  Overrides  Sub  CreateChildControls(    )
              Controls.Add(btn)
        End  Sub
 
  End  Class
 


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>");


至此,作为合同编程人员的你,就完成了任务。

结束语

在上面的文章中,我们讨论了开发定制控件的三种途径,尤其是通过一个例子,详细论述了建立复合控件的过程。在具体的工作中,灵活地运用这三种方式创建定制控件,使我们的开发工作事半功倍。

(出处:赛迪网)

相关文章:
 
·在ASP.NET中使用.NET组件
·ASP.NET学习手记:验证用户表单输入
·asp+ 现在已经被官方正式更名为 asp.net
·asp.net 的菜单制作(asp.net 的菜单application)
·asp.net中的vb7中如何调用dll中的函数
·asp.net中的vb7中如何使用socket作一个传送时间的server
·随机函数生成密码的asp.net版本
·一个功能完善的专栏管理的程序->这是asp.net的第二个应用(一)
·一个功能完善的专栏管理的程序->这是asp.net的第二个应用(二)
·一个功能完善的专栏管理的程序->这是asp.net的第二个应用(三)
相关软件:
 
·ASP.NET 实用全书 (PDG)
·ASP.NET 领先研究 (PDG)
·ASP.NET 实用案例教程 (PDG)
·asp.net Forums 0831版
·ASP.NET v2.0 Beta 预览 (PDF)
·ASP.NET 技术参考 (PDG)
·利用 Dreamweaver MX 2004 进行 asp.net 网站的开发 (CHM)
·建立 ASP.NET 服务器控制台 (CHM)
·铁力tieli asp.net多用户留言本 v1.1 仲秋版
·Asp.Net Forums v2.0 中文官方国庆版
  特别推荐
  热点TOP10