65.9K
CodeProject 正在变化。 阅读更多。
Home

GridView 自定义分页的简洁解决方案

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.57/5 (35投票s)

2007 年 6 月 5 日

CPOL

6分钟阅读

viewsIcon

272862

downloadIcon

2249

本文展示了如何轻松扩展 GridView 以支持自定义分页,并消除了使用 ObjectDataSource 作为数据源的限制。

Screenshot - customerPagingSmall.png

引言

ASP.NET 2.0 中的 GridView 控件在绑定到包含比页面大小更多项的数据源时,提供了对标准分页的强大支持。在具有小型数据集的简单应用程序中,这通常是一个足够的解决方案。然而,在您需要显示大约 10000 条记录的较大项目中,使用标准的内置分页功能拉取所有数据以绑定到 GridView 效率低下。我们需要自定义分页并动态处理数据块。查看 CodeProject、MSDN 和 Google 上可用的解决方案,我发现了两种建议。第一种建议是提供一个自定义寻呼器,您将在其中创建自己的页面导航控件,处理所有事件并处理显示。第二种建议强调在 ObjectDataSource 中使用 SelectCountMethod 返回虚拟项计数,GridView 将使用它来设置其分页。这种建议似乎足够和优雅,但如果您的数据源不是 ObjectDataSource,它就无法工作。在我们当前的项目中,我们的数据源主要是通过业务服务层返回的 DataTable 或某些业务对象的 GenericContainer(例如 List<T>),因此 ObjectDataSource 解决方案不适用。许多人也遇到了同样的问题,别无选择,他们只好创建自己的寻呼器。这非常令人沮丧,如果您有过类似的经历,您将理解我的意思并真正欣赏本文。

我认为自定义分页的一个巧妙解决方案是允许像在 DataGrid 中那样设置 VirtualItemCount 属性,并且不限制用户只能使用 ObjectDataSource,同时充分利用 GridView 中已内置的分页显示和交互。这激发了我编写 PagingGridView 控件和本文。

本文有意侧重于 GridView 的分页显示和交互方面,而不是数据块的检索。但是,为了完整性,我包含了使用 SQL Server 2005 的 ROW_NUMBER() 功能从 SQL Server 2005 检索页面行块的示例。

Using the Code

如果您将 PagingGridView 包含在 ASP.NET Web 应用程序项目或被 ASP.NET 网站项目引用的类库中,PagingGridView 组件将出现在您的工具箱中。要将其添加到您的页面,您可以像使用任何其他 Web 控件一样拖放它。

或者,如果您希望手动将代码添加到 ASCX/ASPX 文件中,首先您必须在文件开头注册标签,并包含由 TagPrefix 限定的 PagingGridView 控件元素。请参阅下面的示例

<%@ Register Assembly="PagingGridView" 
             Namespace="Fadrian.Web.Control" TagPrefix="cc1" %> 

... 

<cc1:PagingGridView ID="PagingGridView2" runat="server"/>

PagingGridView 代码解释

实现上述分页功能实际上非常简单。实现的关键在于 InitializePager 方法。PagingGridView 覆盖此方法并检查 CustomPaging 是否已打开。如果自定义分页已打开,那么我们需要调整一些设置,以便寻呼器将正确呈现数据源中的虚拟项以及当前页索引。

protected override void InitializePager(GridViewRow row, 
          int columnSpan, PagedDataSource pagedDataSource)
{
    // This method is called to initialise the pager
    // on the grid. We intercepted this and override
    // the values of pagedDataSource to achieve
    // the custom paging using the default pager supplied
    if (CustomPaging)
    {
        pagedDataSource.AllowCustomPaging = true;
        pagedDataSource.VirtualCount = VirtualItemCount;
        pagedDataSource.CurrentPageIndex = CurrentPageIndex;
    }
    base.InitializePager(row, columnSpan, pagedDataSource);
}

PagingGridView 公开了一个公共属性 VirtualItemCount。此属性的默认值为 -1,用户可以将其设置为任何整数值。如果此值设置为除 -1 以外的任何值,则 CustomPaging 属性将返回 true 以指示此控件已启用 CustomPaging

public int VirtualItemCount
{
    get 
    {
        if (ViewState["pgv_vitemcount"] == null)
            ViewState["pgv_vitemcount"] = -1;
        return Convert.ToInt32(ViewState["pgv_vitemcount"]);
    }
    set { ViewState["pgv_vitemcount"] = value; }
}

private bool CustomPaging
{
    get { return (VirtualItemCount != -1); }
}

此控件中有一个内部属性 CurrentPageIndex 用于存储当前页索引。这里提出的问题是为什么我们不直接使用 PageIndexPageIndex 存储 GridView 的当前 PageIndex,但在自定义分页场景中,每次我们绑定新的数据源(调用 DataBind)时,如果数据源中的项数小于或等于 PageSizePageIndex 将重置为 0。在我们的情况下,我们只拉取页面块数据,数据源中的项数总是与 PageSize 相同,因此 PageIndex 将始终重置。我们通过引入 CurrentPageIndex 解决了这个问题,并且每次设置 DataSource 时,我们都会捕获该值并将其存储到 ViewState

private int CurrentPageIndex
{
    get
    {
        if (ViewState["pgv_pageindex"] == null)
            ViewState["pgv_pageindex"] = 0;
        return Convert.ToInt32(ViewState["pgv_pageindex"]);
    }
    set { ViewState["pgv_pageindex"] = value; }
} 

public override object DataSource 
{
   get { return base.DataSource; }
   set
   {
      base.DataSource = value;
      // we store the page index here so we dont lost it in databind
      CurrentPageIndex = PageIndex;
   }
}

数据源和分页数据

本文不打算深入探讨如何从数据库检索分页数据的详细信息;相反,它在此处提供信息是为了完整演示如何使用 PagingGridView 控件处理来自 SQL Server 2005 数据库的数据。为了使示例代码保持简单,所有查询都以代码形式编写,避免了 SQL 注入的风险或与使用存储过程相比的效率开销。

为了支持自定义分页,我们至少需要两件事:我们想要显示的总记录数(我们将其设置为 VirtualItemCount)和用于显示特定页面项的数据。下面的代码展示了 GetRowCount 方法,它只是一个简单的 SELECT COUNT (*) SQL 语句来检索行数。

GetDataPage 方法的实现是使用 SQL Server 2005 的 ROW_NUMBER() 功能,为要在网格上显示的特定页面检索特定的记录块。此方法中的 SQL 语句检索内部 Select 语句中按 ROW_NUM 排序的**顶部** x 条感兴趣的记录,而外部 Select 语句使用 WHERE 子句进一步过滤行。例如,如果我们要检索 PageIndex = 3 的记录块,并且 PageSize 设置为 20,则我们想要显示的记录块是第 61-80 行。使用相同的示例,当执行下面的代码中的 SQL 时,内部 Select 将检索“TOP 80”行,然后外部 Select 通过“ROW_NUM > 60”表达式过滤掉所有 <= 60 的行,以返回 20 条记录(第 61-80 行)。

有关使用 ROW_NUMBER() 的更多信息,请参阅 MSDN 或其他在线文章。

private const string demoConnString = 
     @"Integrated Security=SSPI;Persist Security Info=False;" + 
     @"Initial Catalog=NorthwindSQL;Data Source=localhost\SQLEXPRESS";
private const string demoTableName = "Customers";
private const string demoTableDefaultOrderBy = "CustomerID";
private int GetRowCount()
{
    using (SqlConnection conn = new SqlConnection(demoConnString))
    {
        conn.Open();
        SqlCommand comm = new SqlCommand(@"SELECT COUNT(*) FROM " + demoTableName, conn);
        int count = Convert.ToInt32(comm.ExecuteScalar());
        conn.Close();
        return count;
    }
} 

private DataTable GetDataPage(int pageIndex, int pageSize, string sortExpression)
{
    using (SqlConnection conn = new SqlConnection(demoConnString))
    {
        // We always need a default sort field for ROW_NUMBER() to work correctly
        if (sortExpression.Trim().Length == 0)
        sortExpression = demoTableDefaultOrderBy;
        conn.Open();

        string commandText = string.Format(
            "SELECT * FROM (select TOP {0} ROW_NUMBER() OVER (ORDER BY {1}) as ROW_NUM, * " 
            +"FROM {2} ORDER BY ROW_NUM) innerSelect WHERE ROW_NUM > {3}",
        ((pageIndex + 1) * pageSize), 
        sortExpression, 
        demoTableName,
        (pageIndex * pageSize)); 

        SqlDataAdapter adapter = new SqlDataAdapter(commandText, conn);
        DataTable dt = new DataTable();
        adapter.Fill(dt);
        conn.Close();
        dt.Columns.Remove("ROW_NUM");
        return dt;
    }
}

如果您想知道本文的示例数据从何而来,我通过打开 Northwind Access 数据库并使用 Upsizing 向导在我的 SQL Server 2005 Express 中创建了一个新的数据库(包含完整架构和数据),从而创建了 NorthwindSQL 数据库。您可以重复此过程以重新创建数据来测试代码,或者简单地修改 demoConnStringdemoTableNamedemoTableDefaultOrderBy 以反映您的数据存储。

实际应用

为了在您的目标页面中启用自定义分页,您必须在代码中设置 VirtualItemCount 或在 PagingGridView 控件的属性窗口中设置它。在下面的示例中,我们将 VirtualItemCount 设置为一个方法返回的值,该方法返回我们要检索的总记录的行数。

语法上,我们对 PagingGridViewDataSourceDataBind 进行编码,与 GridView 完全相同。您只需要清楚地记住,当我们分配 DataSource 时,无论是 DataTable 还是 GenericContainer,数据集都应只包含该页面的数据项;否则,我们只是在浪费我们为启用 CustomPaging 所做的所有努力 :)

protected void Page_Load(object sender, EventArgs e)
{
    if (!this.IsPostBack)
    {
        PagingGridView1.VirtualItemCount = GetRowCount();
        BindPagingGrid();
    }
} 

protected void PagingGridView1_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    PagingGridView1.PageIndex = e.NewPageIndex;
    BindPagingGrid();
} 

private void BindPagingGrid()
{
    PagingGridView1.DataSource = GetDataPage(PagingGridView1.PageIndex,
    PagingGridView1.PageSize, PagingGridView1.OrderBy);
    PagingGridView1.DataBind();
}
© . All rights reserved.