Custom Paging for GridView

I don't care for the built-in Paging technique for GridView, and neither do many other developers who don't want to recreate the GridView Control or buy a third party control for the same.

This idea is taken from Mendhak's sample code. The answer lies in building a Web User Control that acts like a Custom Paging Control for your GridView.

Using DropDownLists to Change Page Number and Page Size

The user can change the number of records that GridView displays and select the Page instead of clicking Next, Next, Next...

Not all people have the same requirements, and there are many other ways to achieve this effect, but the ones that I demonstrate will definitely add to the flexibility of your work.


Figure 1

Creating the UserControl

Create a new Website, add a Web User Control to it and name it Pager.ascx.

Add this code to the source of the page.

<div style="font-size:8pt; font-family:Verdana;">
   <div id="left" style="float:left;">
      <span>Show Page </span>
      <asp:DropDownList ID="ddlPageNumber" runat="server"
         AutoPostBack="true"
         OnSelectedIndexChanged="ddlPageNumber_
         SelectedIndexChanged"></asp:DropDownList>
      <span> of</span>
      <asp:Label ID="lblShowRecords" runat="server"></asp:Label>
      <span>Pages </span>
   </div>
   <div id="right" style="float:right;">
      <span>Display </span>
      <asp:DropDownList ID="ddlPageSize"
            runat="server"
            AutoPostBack="true"
            OnSelectedIndexChanged="ddlPageSize_SelectedIndexChanged">
         <asp:ListItem Text="1" Value="1"></asp:ListItem>
         <asp:ListItem Text="5" Value="5"></asp:ListItem>
         <asp:ListItem Text="10" Value="10"
                       Selected="true"></asp:ListItem>
         <asp:ListItem Text="20" Value="20"></asp:ListItem>
         <asp:ListItem Text="25" Value="25"></asp:ListItem>
         <asp:ListItem Text="50" Value="50"></asp:ListItem>
      </asp:DropDownList>
      <span> Records per Page</span>
   </div>
</div>

Exposing an event

You need to raise an Event from this control so that it can be handled from within the page consuming it.

public event CustomDelegateClass.PageChangedEventHandler PageChanged;

Notice the CustomDelegateClass.PageChangedEventHandler.

This is a custom delegate, so add a new Class file to your project (VS IDE will automatically add it to the App_Code folder) and add the following code:

public partial class CustomDelegateClass
{
   public delegate void PageChangedEventHandler(object sender,
      CustomPageChangeArgs e);
}

You might have notice by now that the function needs an argument of type CustomDelegateClass. This object contains useful information that is required for the navigation in GridView.

Add a class named CustomDelegateClass, which inherits from EventsArgs.

public partial class CustomPageChangeArgs : EventArgs
{
   private int _currentPageNumber;
   public int CurrentPageNumber
   {
      get { return _currentPageNumber; }
      set { _currentPageNumber = value; }
   }

   private int _totalPages;
   public int TotalPages
   {
      get { return _totalPages; }
      set { _totalPages = value; }
   }

   private int _currentPageSize;
   public int CurrentPageSize
   {
      get { return _currentPageSize; }
      set { _currentPageSize = value; }
   }
}

Now, the first DropDownList will fire the event to navigate to the different pageset, and the second DropDownlist will fire the event to change the PageSize of the GridView Control.

//First DropDownList, for selecting different page number

protected void ddlPageNumber_SelectedIndexChanged(object sender,
   EventArgs e)
   {
      CustomPageChangeArgs args = new CustomPageChangeArgs();
      args.CurrentPageSize =
         Convert.ToInt32(this.ddlPageSize.SelectedItem.Value);
      args.CurrentPageNumber =
         Convert.ToInt32(this.ddlPageNumber.SelectedItem.Text);
      args.TotalPages = Convert.ToInt32(this.lblShowRecords.Text);
      Pager_PageChanged(this, args);

      lblShowRecords.Text = string.Format(" {0} ",
         args.TotalPages.ToString());
   }
//second DropDonwList, to change the pagesize of gridView

protected void ddlPageSize_SelectedIndexChanged(object sender,
   EventArgs e)
   {
      CustomPageChangeArgs args = new CustomPageChangeArgs();
      args.CurrentPageSize =
         Convert.ToInt32(this.ddlPageSize.SelectedItem.Value);
      args.CurrentPageNumber = 1;
      args.TotalPages = Convert.ToInt32(this.lblShowRecords.Text);
      Pager_PageChanged(this, args);

      ddlPageNumber.Items.Clear();
      for (int count = 1; count <= this.TotalPages; ++count)
         ddlPageNumber.Items.Add(count.ToString());
      ddlPageNumber.Items[0].Selected = true;
      lblShowRecords.Text = string.Format(" {0} ",
         this.TotalPages.ToString());
   }

Your control is now ready to be consumed by a web page.

Custom Paging for GridView

Using the UserControl

Add a GridView and the created UserControl on a web page.

<!-- Register the UserControl -->
<%@ Register Src="~/Pager.ascx" TagPrefix="custom" TagName="Pager" %>

   <asp:GridView ID="grdPagingDemo" runat="server"
    AutoGenerateColumns="false"
    AllowPaging="true" PagerSettings-Visible="false"
    HeaderStyle-Font-Bold="True"
    HeaderStyle-BackColor="Maroon"
    HeaderStyle-ForeColor="Tan"
    AlternatingRowStyle-BackColor="BlanchedAlmond"
    Font-Names="Verdana"
    Font-Size="9pt"
    BorderColor="Tan"
    OnRowDataBound="grdPagingDemo_RowDataBound">

   <Columns>
      <asp:BoundField DataField="ProductID" HeaderText="ID"
                      ItemStyle-HorizontalAlign="Center" />
      <asp:BoundField DataField="ProductName"
                      HeaderText="Product Name" />
      <asp:BoundField DataField="UnitPrice"
                      HeaderText="Price of Unit"
                      ItemStyle-HorizontalAlign="Center" />
      <asp:BoundField DataField="UnitsInStock"
                      HeaderText="Units in Stocks"
                      ItemStyle-HorizontalAlign="Center" />
      <asp:TemplateField HeaderText="Discontinued?"
                         ItemStyle-HorizontalAlign="center">
         <ItemTemplate>
            <asp:CheckBox ID="chkDisc" runat="server" />
         </ItemTemplate>
      </asp:TemplateField>
   </Columns>

   </asp:GridView>
   <br />

<!-- An instance of usercontrol -->
   <custom:Pager ID="custPager" runat="server"
                 OnPageChanged="custPager_PageChanged" />

Bind your GridView the same way you normally do. In the CodeBehind of your web page, you need to handle the event exposed by the UserControl.

protected void Page_Load(object sender, EventArgs e)
{
   if (!this.Page.IsPostBack)
   {
      grdPagingDemo.PageIndex = 1;
      BindGridView();
   }
}

protected void custPager_PageChanged(object sender,
   CustomPageChangeArgs e)
{
   grdPagingDemo.PageSize = e.CurrentPageSize;
   grdPagingDemo.PageIndex = e.CurrentPageNumber;
   BindGridView();
}

Custom Paging S Procedure

I used MS SQL Server 2000, Northwind Database, and the Products Table.

Consider querying your DB for 10k records, getting them to your business logic and using only 10 records out of them. Good? Is it a system performance technique? I would say no! Why not just retrieve only the required data from the DB.

So, I created this stored procedure that requires only three parameters:

  • @startRowIndex int: Current page number for the page whose data you need. Example: 1, 2, 3, and so forth.
  • @maxRows int: Page size of GridView. Example: number of records to be displayed at a time.
  • @totalRows int/out: Output parameter to return total number of records returned for your query.
CREATE PROCEDURE [PagingDemo]
(
   @startRowIndex int,
   @maxRows int,
   @totalRows int out
)
AS

DECLARE @firstID int, @startRow int

SET @startRow = ((@startRowIndex - 1) * @maxRows) + 1
SET ROWCOUNT @startRow

SELECT @firstID = ProductID FROM Products ORDER BY ProductID

SET ROWCOUNT @maxRows

SELECT ProductID, ProductName, UnitPrice, UnitsInStock,
   Discontinued FROM Products
WHERE ProductID >= @firstID

SET ROWCOUNT 0

SELECT @totalRows = COUNT(ProductID) FROM Products
GO

@totalPages helps in calculating how many pages will be created based on the PageSize of GridView. In the BindGridView() function, you use the output parameter and assign this value to Gridview's pagesize property.

SqlCommand sqlCmd = new SqlCommand();
   sqlCmd.Connection = sqlConn;
   sqlCmd.CommandType = CommandType.StoredProcedure;
   sqlCmd.CommandText = "PagingDemo";
   sqlCmd.Parameters.Add("@startRowIndex",
      SqlDbType.Int).Value = grdPagingDemo.PageIndex;
   sqlCmd.Parameters.Add("@maxRows",
      SqlDbType.Int).Value = grdPagingDemo.PageSize;
   sqlCmd.Parameters.Add("@totalRows",
      SqlDbType.Int).Direction = ParameterDirection.Output;

   SqlDataAdapter sqlDad = new SqlDataAdapter();
   sqlDad.SelectCommand = sqlCmd;

   DataTable sqlDt = new DataTable();

   sqlDad.Fill(sqlDt);

   int totalPages = (int)sqlCmd.Parameters["@totalRows"].Value;

   grdPagingDemo.DataSource = sqlDt;
   grdPagingDemo.DataBind();

   custPager.TotalPages =
      totalPages % grdPagingDemo.PageSize == 0 ? totalPages /
         grdPagingDemo.PageSize : totalPages /
         grdPagingDemo.PageSize + 1;

Custom Paging for GridView

Another way is to display page numbers as links to respective page sets, just like default paging control.

[Pager2_1.jpg]
Figure 2

The underlying technique will remain the same as the previous one, except you will be using a repeater and some linkbuttons.

Add another usercontrol and name it LinkPager.ascx. Add this to the source:

<div style="font-size:8pt; font-family:Verdana;">
   <div id="left" style="float:left;"><span>Page</span>
      <asp:Label ID="lblCurrentPage" runat="server"></asp:Label>
      <span>of</span>
      <asp:Label ID="lblTotalRecords" runat="server"></asp:Label>
   </div>
   <div id="right" style="float:right;">
      <asp:Repeater ID="rptPages" runat="server"
                    OnItemDataBound="rptPages_ItemDataBound">
      <ItemTemplate>
         [<asp:LinkButton ID="lnkPageNumbers"
            runat="server"
            OnClick="lnkPageNumbers_Click"></asp:LinkButton>
         <asp:Label ID="lblPageNumbers" runat="server"></asp:Label>]
       </ItemTemplate>
      </asp:Repeater>
   </div>
   <br />
   <div id="right2" style="float:right;">
      <asp:LinkButton ID="lnkFirstPage"
                      runat="server"
                      Text="First"
                      OnClick="lnkGOFPage_Click"></asp:LinkButton>
      <asp:Label ID="lblFirstPage"
                 runat="server"
                 Text="First"></asp:Label>
      <asp:LinkButton ID="lnkPreviousPage"
                      runat="server"
                      Text="Prev"
                      OnClick="lnkGOFPage_Click"></asp:LinkButton>
      <asp:Label ID="lblPreviousPage"
                 runat="server"
                 Text="Prev"></asp:Label>
      <asp:LinkButton ID="lnkNextPage"
                      runat="server"
                      Text="Next"
                      OnClick="lnkGOFPage_Click"></asp:LinkButton>
      <asp:Label ID="lblNextPage"
                 runat="server"
                 Text="Next"></asp:Label>
      <asp:LinkButton ID="lnkLastPage"
                      runat="server"
                      Text="Last"
                      OnClick="lnkGOFPage_Click"></asp:LinkButton>
      <asp:Label ID="lblLastPage"
                 runat="server" Text="Last"></asp:Label>
   </div>
</div>

These linkbuttons will fire the event for paging. In code behind, use:

protected void lnkPageNumbers_Click(object sender, EventArgs e)
   {
      args = new CustomPageChangeArgs();
      args.CurrentPageNumber =
         Convert.ToInt32(((LinkButton)sender).Text);
      this.CurrentPageNumber = args.CurrentPageNumber;
      Pager_PageChanged(this, args);

      BindRepeater();
      lblCurrentPage.Text = this.CurrentPageNumber.ToString();
      SetUnsetLinkButtons();
   }

   protected void lnkGOFPage_Click(object sender, EventArgs e)
   {
      args = new CustomPageChangeArgs();
      switch (((LinkButton)sender).ID)
      {
         case "lnkFirstPage": args.CurrentPageNumber = 1;
            HttpContext.Current.Items.Add("currentPage", 1);
            break;
         case "lnkPreviousPage": args.CurrentPageNumber =
            Convert.ToInt32(lblCurrentPage.Text) - 1;
            break;
         case "lnkNextPage": args.CurrentPageNumber =
            Convert.ToInt32(lblCurrentPage.Text) + 1;
            break;
         case "lnkLastPage": args.CurrentPageNumber =
            Convert.ToInt32(lblTotalRecords.Text);
            break;
      }

      this.CurrentPageNumber = args.CurrentPageNumber;
      Pager_PageChanged(this, args);

      BindRepeater();
      lblCurrentPage.Text = this.CurrentPageNumber.ToString();
      SetUnsetLinkButtons();
   }

In the page that will consume this usercontrol, add a function that will handle this exposing event.

protected void custLinkPager_PageChanged(object sender,
   CustomPageChangeArgs e)
   {
      grdPagingDemo.PageIndex = e.CurrentPageNumber;
      BindGridView();
   }

The SetUnsetLinkButtons() function helps in displaying the links, or if a page is selected, it just shows the number.

Note: This effect can be achieved using some simple CSS. I used it just for demonstration.
private void SetUnsetLinkButtons()
   {
      if (this.CurrentPageNumber <= 1)
      {
         lnkFirstPage.Style[HtmlTextWriterStyle.Display] = "none";
         lnkPreviousPage.Style[HtmlTextWriterStyle.Display] = "none";
         lblFirstPage.Style[HtmlTextWriterStyle.Display] = "inline";
         lblPreviousPage.Style[HtmlTextWriterStyle.Display] = "inline";
         lblLastPage.Style[HtmlTextWriterStyle.Display] = "none";
         lblNextPage.Style[HtmlTextWriterStyle.Display] = "none";
         lnkLastPage.Style[HtmlTextWriterStyle.Display] = "inline";
         lnkNextPage.Style[HtmlTextWriterStyle.Display] = "inline";
      }
      else if (this.CurrentPageNumber >= this.TotalPages)
      {
         lnkFirstPage.Style[HtmlTextWriterStyle.Display] = "inline";
         lnkPreviousPage.Style[HtmlTextWriterStyle.Display] = "inline";
         lblFirstPage.Style[HtmlTextWriterStyle.Display] = "none";
         lblPreviousPage.Style[HtmlTextWriterStyle.Display] = "none";
         lblLastPage.Style[HtmlTextWriterStyle.Display] = "inline";
         lblNextPage.Style[HtmlTextWriterStyle.Display] = "inline";
         lnkLastPage.Style[HtmlTextWriterStyle.Display] = "none";
         lnkNextPage.Style[HtmlTextWriterStyle.Display] = "none";
      }
      else
      {
         lnkFirstPage.Style[HtmlTextWriterStyle.Display] = "inline";
         lnkPreviousPage.Style[HtmlTextWriterStyle.Display] = "inline";
         lblFirstPage.Style[HtmlTextWriterStyle.Display] = "none";
         lblPreviousPage.Style[HtmlTextWriterStyle.Display] = "none";
         lblLastPage.Style[HtmlTextWriterStyle.Display] = "none";
         lblNextPage.Style[HtmlTextWriterStyle.Display] = "none";
         lnkLastPage.Style[HtmlTextWriterStyle.Display] = "inline";
         lnkNextPage.Style[HtmlTextWriterStyle.Display] = "inline";
      }
   }

Please consult the attached project for a working example.



Related Articles

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • 10 Rules that Make or Break Enterprise App Development Projects In today's app-driven world, application development is a top priority. Even so, 68% of enterprise application delivery projects fail. Designing and building applications that pay for themselves and adapt to future needs is incredibly difficult. Executing one successful project is lucky, but making it a repeatable process and strategic advantage? That's where the money is. With help from our most experienced project leads and software engineers, …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds