ASP.NET 2.0里为我们提供了全新的GridView控件,它在DataGrid基础之上增加了许多新的特性,例如不编写一行代码就可以和数据源控件结合起来实现数据的展示并且分页,但是这种分页效率较低,这是从数据库一次读取所有的数据再进行分页,如果数据量较少则可以实现快速开发,但是假如数据库中存放大量数据,这种操作性能显得就比较低了,所以我们一般自己编写数据读取方法,在PageIndexChanging事件中绑定新页,这时会遇到一个问题,如果我们仅读取一页数据时GridView不能正确计算出共有多少页,也就无法正确呈现出分页按钮,因此需要考虑对其进行扩展。另外DataList控件提供了灵活的模版设置以显示记录内容,但是它有一个最大的弱点就是不支持分页,我们同样试图扩展DataList以增加分页的特性。

2. 分析

之所以要将GridView和DataList放在一起考虑,是因为不论哪一个数据绑定控件生成分页链接列表时需要执行相似的操作。GridView控件本身支持分页,所以在开发自定义表格控件时,只需要加入相应的按钮服器端控件,将CommandName属性设置为Page,并设置CommandArgument属性为特定值,即可由GridView捕捉到页面更改事件,为了避免在代码中出现“魔法字符”,定义了常量类保存使用的字符串常量。但是这种方法对于DataList却不适用,因为DataList不能接收到客户端的回发事件,这也是DataList类和GridView类的一个区别—DataList类没有实现IPostBackEventHandler接口。为了能够使DataList接收客户端回发并触发分页事件,需要使自定义DataList实现IPostBackEventHandler接口,并使用自定义事件参数类在触发事件时传递页码信息。

现在自定义GridView和DataList控件均可以实现分页了,为了使两者有一个统一的分页外观,定义分页基类实现分页功能,并且针对DataList分页,继承分页基类并设置分页按钮的回发脚本。最后为了能够把分页按钮作为一个整体添加到表格中,使之继承自TableCell(表格中的单元格)。

3. 实现

3.1 创建保存命令字符串的常量类Constant

public class Constant
{
     public const string FIRST_PAGE = "First";
     public const string PREV_PAGE = "Prev";
     public const string NEXT_PAGE = "Next";
     public const string LAST_PAGE = "Last";
     public const string PAGE_ARGUMENT = "Page";
     public const char ARGUMENT_SPLITTER = '$';
}

分页按钮中的CommandArgument属性设置如下:

属性 描述

First

移动到第一页
Last 移动到最后一页
Prev 移动到上一页
Next 移动到下一页
一个数字 移动到某个特定页

3.2 定义DataPager类以生成分页链接,该类继承自TableCell类,并且定义了私有变量以计算页码(PagerSettings类是.NET预定义类,存储了有关分页设置的信息)

public class DataPager : TableCell
{
     private int _pageIndex;
     private int _recordCount;
     private int _pageSize;
     private int _pageCount;
     private PagerSettings _settings;
}
3.3 定义构造函数,接收当前页索引、总记录数、页大小信息并根据这些数据计算出总页码,接着调用GeneratePage方法生成分页
public DataPager(PagerSettings setting, int pageIndex, int recordCount, int pageSize)
{
     _settings = setting;
     _pageIndex = pageIndex;
     _recordCount = recordCount;
     _pageSize = pageSize;
      _pageCount = _recordCount % _pageSize == 0 ? _recordCount / _pageSize : _recordCount / _pageSize + 1;
      GeneratePage();
}

3.3 编写GeneratePage方法,根据PagerSetting设置生成带有数字或不带有数字的分页链接:

private void GeneratePage()
{
     if (_settings.Mode == PagerButtons.NextPrevious || _settings.Mode == PagerButtons.NextPreviousFirstLast)
     {
         GeneratePrevNextPage();
     }
     else if (_settings.Mode == PagerButtons.Numeric || _settings.Mode == PagerButtons.NumericFirstLast)
     {
         GenerateNumericPage();
     }
}
3.4 编写生成两类分页链接的方法:
private void GeneratePrevNextPage()
{
     GeneratePage(false);
}
  private void GenerateNumericPage()
{
     GeneratePage(true);
}
3.5 生成分页时均调用了GeneratePage方法,该方法创建了一系列的LinkButton并设置了CommandArgument和CommandName属性,以下是该方法的实现:
private void GeneratePage(bool generateNumber)
{
     this.Controls.Add(new LiteralControl("  "));
      if (_recordCount > 0)
     {
         this.Controls.Add(new LiteralControl("?? "));
         this.Controls.Add(new LiteralControl(_recordCount.ToString()));
         this.Controls.Add(new LiteralControl(" ??????  ???? "));
         this.Controls.Add(new LiteralControl(_pageSize.ToString()));
         this.Controls.Add(new LiteralControl(" ??????  "));
     }
      this.Controls.Add(new LiteralControl((_pageIndex + 1).ToString()));
     this.Controls.Add(new LiteralControl("/"));
     this.Controls.Add(new LiteralControl(_pageCount.ToString()));
     this.Controls.Add(new LiteralControl("    "));
      LinkButton btnFrist = new LinkButton();
     LinkButton btnPrev = new LinkButton();
     LinkButton btnNext = new LinkButton();
     LinkButton btnLast = new LinkButton();
      if (!String.IsNullOrEmpty(_settings.FirstPageImageUrl))
     {
         btnFrist.Text = "<img src='" + ResolveUrl(_settings.FirstPageImageUrl) + "' border='0'/>";
     }
     else
     {
         btnFrist.Text = _settings.FirstPageText;
     }
     btnFrist.CommandName = Constant.PAGE_ARGUMENT;
     btnFrist.CommandArgument = Constant.FIRST_PAGE;
     btnFrist.Font.Underline = false;
      if (!String.IsNullOrEmpty(_settings.PreviousPageImageUrl))
     {
         btnPrev.Text = "<img src='" + ResolveUrl(_settings.PreviousPageImageUrl) + "' border='0'/>";
     }
     else
     {
         btnPrev.Text = _settings.PreviousPageText;
     }
     btnPrev.CommandName = Constant.PAGE_ARGUMENT;
     btnPrev.CommandArgument = Constant.PREV_PAGE;
     btnPrev.Font.Underline = false;
      if (!String.IsNullOrEmpty(_settings.NextPageImageUrl))
     {
         btnNext.Text = "<img src='" + ResolveUrl(_settings.NextPageImageUrl) + "' border='0'/>";
     }
     else
     {
         btnNext.Text = _settings.NextPageText;
     }
     btnNext.CommandName = Constant.PAGE_ARGUMENT;
     btnNext.CommandArgument = Constant.NEXT_PAGE;
     btnNext.Font.Underline = false;
      if (!String.IsNullOrEmpty(_settings.LastPageImageUrl))
     {
         btnLast.Text = "<img src='" + ResolveUrl(_settings.LastPageImageUrl) + "' border='0'/>";
     }
     else
     {
         btnLast.Text = _settings.LastPageText;
     }
     btnLast.CommandName = Constant.PAGE_ARGUMENT;
     btnLast.CommandArgument = Constant.LAST_PAGE;
     btnLast.Font.Underline = false;
      if (this._pageIndex <= 0)
     {
         btnFrist.Enabled = btnPrev.Enabled = false;
     }
     else
     {
         btnFrist.Enabled = btnPrev.Enabled = true;
     }
      this.Controls.Add(btnFrist);
     this.Controls.Add(new LiteralControl("&nbsp;&nbsp;"));
     this.Controls.Add(btnPrev);
     this.Controls.Add(new LiteralControl("&nbsp;&nbsp;"));
      if (generateNumber)
     {
         int rightCount = (int)(_settings.PageButtonCount / 2);
                  int leftCount = _settings.PageButtonCount % 2 == 0 ? rightCount - 1 : rightCount;
         for (int i = 0; i < _pageCount; i++)
         {
             if (_pageCount > _settings.PageButtonCount)
             {
                 if (i < _pageIndex - leftCount && _pageCount - 1 - i > _settings.PageButtonCount - 1)
                 {
                     continue;
                 }
                 else if (i > _pageIndex + rightCount && i > _settings.PageButtonCount - 1)
                 {
                     continue;
                 }
             }
              if (i == _pageIndex)
             {
                 this.Controls.Add(new LiteralControl("<span style='color:red;font-weight:bold'>" + (i + 1).ToString() + "</span>"));
             }
             else
             {
                 LinkButton lb = new LinkButton();
                 lb.Text = (i + 1).ToString();
                 lb.CommandName = Constant.PAGE_ARGUMENT;
                 lb.CommandArgument = (i + 1).ToString();
                  this.Controls.Add(lb);
             }
              this.Controls.Add(new LiteralControl("&nbsp;&nbsp;"));
         }
     }
      if (this._pageIndex >= _pageCount - 1)
     {
         btnNext.Enabled = btnLast.Enabled = false;
     }
     else
     {
         btnNext.Enabled = btnLast.Enabled = true;
     }
     this.Controls.Add(btnNext);
     this.Controls.Add(new LiteralControl("&nbsp;&nbsp;"));
     this.Controls.Add(btnLast);
     this.Controls.Add(new LiteralControl("&nbsp;&nbsp;"));
}

3.6 创建自定义GridView类,该类继承自ASP.NET中默认的GridView(System.Web.UI.WebControls.GridView):

[ToolboxData(@"<{0}:GridView runat='server'></{0}:GridView>")]
public class GridView : System.Web.UI.WebControls.GridView
{
}
3.7 定义GridView属性,新增了RecordCound存储总记录数,并且重写了PageIndex和PageCount属性以存储当前页索引和总页数,此外还定义了MouseOverBackgroundColor属性以实现当鼠标移到数据行上时背景变色。
public int RecordCount
{
     get
     {
         object o = ViewState["RecordCount"];
          return o == null ? 0 : Convert.ToInt32(o);
     }
     set
     {
         ViewState["RecordCount"] = value;
     }
}
  public override int PageIndex
{
     get
     {
         object o = ViewState["PageIndex"];
          return o == null ? 0 : Convert.ToInt32(o);
     }
     set
     {
         ViewState["PageIndex"] = value;
     }
}
  public override int PageCount
{
     get
     {
         int pageCount = RecordCount % PageSize == 0 ? RecordCount / PageSize : RecordCount / PageSize + 1;
          return pageCount;
     }
}
  public Color MouseOverBackgroundColor
{
     get
     {
         object o = ViewState["MouseOverBackgroundColor"];
         return o == null ? Color.Silver : (Color)o;
     }
     set
     {
         ViewState["MouseOverBackgroundColor"] = value;
     }
}
3.8 重写OnRowCreated方法,在创建数据行时首先增加背景变色JavaScript,接下来根据分页信息创建页码链接单元格添加到表格中。
protected override void OnRowCreated(GridViewRowEventArgs e)
{
     if (e.Row.RowType == DataControlRowType.DataRow)
     {
         e.Row.Attributes.Add("onmouseover", "bgcolor=this.style.backgroundColor;this.style.backgroundColor='" + ColorTranslator.ToHtml(MouseOverBackgroundColor) + "';");
         e.Row.Attributes.Add("onmouseout", "this.style.backgroundColor=bgcolor;");
     }
      if (e.Row.RowType == DataControlRowType.Pager)
     {
         e.Row.Controls.Clear();
         TableCell tc = new DataPager(PagerSettings, PageIndex, RecordCount, PageSize);
                  tc.ColumnSpan = this.Columns.Count;
         e.Row.Controls.Add(tc);
     }
      base.OnRowCreated(e);
}

3.9 为了实现在删除最后一行数据时能够滚动到上一页,重写了OnRowDeleted方法:

protected override void OnRowDeleting(GridViewDeleteEventArgs e)
{
     if (this.Rows.Count == 1)
         this.PageIndex = this.PageIndex - 1 >= 0 ? this.PageIndex - 1 : 0;
      base.OnRowDeleting(e);
}
3.10 在网站中声明并定义自定义GridView:
<cc:GridView ID="gdvData" runat="server" PageSize="4" AllowPaging="True" OnPageIndexChanging="gdvData_PageIndexChanging" RecordCount="0" DataKeyNames="ID" Font-Size="12px" OnRowDeleting="gdvData_RowDeleting" Width="500px" AutoGenerateColumns="False" BackColor="White" BorderColor="#FFE0C0" BorderStyle="None" BorderWidth="1px" CellPadding="4" MouseOverBackgroundColor="255, 224, 192" OnRowDeleted="gdvData_RowDeleted" >
     <Columns>
         <asp:BoundField DataField="ID" HeaderText="????" />
         <asp:BoundField DataField="Name" HeaderText="????" />
         <asp:BoundField DataField="Favor" HeaderText="????" />
         <asp:CommandField ShowDeleteButton="True" HeaderText="????" />
     </Columns>
     <PagerSettings PageButtonCount="5" />
     <RowStyle BackColor="White" ForeColor="#330099" />
     <FooterStyle BackColor="#FFFFCC" ForeColor="#330099" />
     <PagerStyle BackColor="#FFFFCC" ForeColor="#330099" HorizontalAlign="Center" />
     <SelectedRowStyle BackColor="#FFCC66" Font-Bold="True" ForeColor="#663399" />
     <HeaderStyle BackColor="#990000" Font-Bold="True" ForeColor="#FFFFCC" />
     <AlternatingRowStyle BackColor="Yellow" />
</cc:GridView>

3.11 编写后置代码以绑定到数据源:

protected void Page_Load(object sender, EventArgs e)
{
     if (!IsPostBack)
         this.BindData();
}
  private void BindData()
{
     UserQuery query=null;
      object o = ViewState["Query"];
      if (o == null)
         query = new UserQuery();
     else
         query = (UserQuery)o;
      int total=0;
     IList<User> users = Users.GetUsers(query, gdvData.PageIndex, gdvData.PageSize, out total);
              gdvData.RecordCount = total;
     gdvData.DataSource = users;
     gdvData.DataBind();
  }

3.12 编写PageIndexChanging和RowDeleting事件处理程序:

protected void gdvData_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
     gdvData.PageIndex = e.NewPageIndex;
     this.BindData(); 
}
  protected void gdvData_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
     int id=Convert.ToInt32(gdvData.DataKeys[e.RowIndex].Value);
                  Users.DeleteUser(id); 
      this.BindData();
}

3.13 在浏览器中预览页面(此代码中的Users和User类均未给出,读者可尝试自行编写,其中Users类相当于一个逻辑类实现了用户的查询和删除,User类代表用户实体)。

4. 总结

本次任务中创建了可以自动实现分页的GridView控件,把生成分页链接的操作单独提取出来创建了DataPager类,在此类中使用LinkButton并设置了CommandName和CommandArgument以实现换页。下一次任务我们将在此类的基础上为DataList添加分页特性。


ASP.NET自定义控件系列文章

前言

第一天 简单的星级控件 

第二天 带有自定义样式的星级控件

第三天 使用控件状态的星级控件

第四天 折叠面板自定义控件

第五天 可以评分的星级控件

第六天 可以绑定数据源的星级控件

第七天 开发具有丰富特性的列表控件

第八天 显示多个条目星级评分的数据绑定控件

第九天 自定义GridView

第十天 实现分页功能的DataList


全部源码下载

本系列文章PDF版本下载