Introduction
It's not hard to find code examples on how to do custom paging in ASP.NET, but I haven't found any good examples on using a user control to do so. This example shows you how to construct your own user control to perform custom paging; thus, promote code reuse by dropping the control on every page that needs the same functionality.
The user control shown in this example uses several events to control the page navigation, if you're not so familiar with how events/delegates work you'll probably have to get some reference on the topic first.
This solution also requires writing stored procedures that return pieces of data to be display on each page. I borrowed the idea from Zek3vil in his article - Custom Data Paging in ASP.NET (http://www.codeproject.com/aspnet/custompaging.asp?target=custom%7Cpaging). Basically, he used a temp
table with an ID column that has the IDENTITY
and PRIMARY KEY
attribute to hold the returned data. Although the example was written in T-SQL, the same concept could be implemented in PL/SQL as well.
To make it simple, I decided to use the employee table in pubs database on MS SQL Server. Let's start by looking at the stored procedure:
Creating the Stored Procedure
CREATE PROCEDURE Get_Employees( @CurrentPage int,
@PageSize int,
@TotalRecords int OUTPUT)
AS
Set NoCount On
Declare @FirstRec int
Declare @LastRec int
Set @FirstRec = (@CurrentPage - 1) * @PageSize
Set @LastRec = (@CurrentPage * @PageSize + 1)
Create Table #TempTable
(
EmpId int IDENTITY PRIMARY KEY,
fname varchar(20),
lname varchar(30),
pub_id char(4),
hire_date datetime
)
Insert Into #TempTable
(
fname,
lname,
pub_id,
hire_date
)
Select fname,
lname,
pub_id,
hire_date
From employee
Order By lname
Select fname,
lname,
pub_id,
hire_date
From #TempTable
Where EmpId > @FirstRec
And EmpId < @LastRec
Select @TotalRecords = Count(*)
From #TempTable
GO
The stored procedure has 2 input and 1 output parameter - @CurrentPage
is the current page number and should be greater than 0; @PageSize
determines how many records to display on each page; @TotalRecords
returns the number of records in total, which is also being used to calculate the total number of pages.
Next, let's talk about the meat - the user control:
Paging User Control
<table width="100%">
<tr>
<td>(Page
<asp:label id="lblCurrentPage" Runat="server"></asp:label>of
<asp:label id="lblTotalPages" Runat="server"></asp:label>)
</td>
<td valign="top" align="right">
Page
<asp:DropDownList id="ddPage" runat="server"
AutoPostBack="true"></asp:DropDownList>
</td>
<td align="right">
<asp:imagebutton id="btnFirst" Runat="server" Enabled="false"
ImageUrl="Images/NavFirstPageDisabled.gif" />
<asp::imagebutton id="btnPrevious" Runat="server" Enabled="false"
ImageUrl="Images/NavPreviousPageDisabled.gif" />
<asp:imagebutton id="btnNext" Runat="server" Enabled="false"
ImageUrl="Images/NavNextPageDisabled.gif" />
<asp:imagebutton id="btnLast" Runat="server" Enabled="false"
ImageUrl="Images/NavLastPageDisabled.gif" />
</td>
</tr>
</table>
The user control consists of 3 parts - labels that show something like "(Page 1 of 10)," a dropdown listbox that allows you to jump from page to page and 4 VCR-type buttons to navigate between First, Previous, Next and Last page.
There are some limitations on ASP.NET image buttons that hopefully will be improved in the next release. First, the image doesn't grey out when the button is disabled. Hence, we'll have to explicitly change the ImageUrl attribute when we enable/disable the button. Second, the mouse cursor remains the same (index finger) regardless of the button state. I didn't do anything to correct this problem in my code but it's a good exercise for you to figure out.
Now let's look at the code behind:
public delegate void FirstPageEventHandler(object sender,
DataNavigatorEventArgs e);
public delegate void LastPageEventHandler(object sender,
DataNavigatorEventArgs e);
public delegate void PreviousPageEventHandler(object sender,
DataNavigatorEventArgs e);
public delegate void NextPageEventHandler(object sender,
DataNavigatorEventArgs e);
public delegate void PageChangedEventHandler(object sender,
DataNavigatorEventArgs e);
Since each VCR-type button and the dropdown listbox has its own event to be linked to, we have to first declare all the public delegates. Notice that we have our own custom event argument type (DataNavigatorEventArgs
) because we need to keep track of the current page and total page number each time we navigate between pages.
Here's the DataNavigatorEventArgs
class:
public class DataNavigatorEventArgs : EventArgs
{
private int m_iCurrentPage;
private int m_iTotalPages;
public DataNavigatorEventArgs()
{
}
public int CurrentPage
{
get { return m_iCurrentPage; }
set { m_iCurrentPage = value; }
}
public int TotalPages
{
get { return m_iTotalPages; }
set { m_iTotalPages = value; }
}
}
Then, we declare all public events that are hooked up with those delegates:
public event FirstPageEventHandler FirstPage;
public event LastPageEventHandler LastPage;
public event PreviousPageEventHandler PreviousPage;
public event NextPageEventHandler NextPage;
public event PageChangedEventHandler PageChanged;
For those image buttons, we have to forward the Click
event to our declared delegates. We do this by the following code inside the InitializeComponent()
function:
private void InitializeComponent()
{
this.btnPrevious.Click += new System.Web.UI.ImageClickEventHandler
(this.OnPreviousPageButton);
this.btnNext.Click += new System.Web.UI.ImageClickEventHandler
(this.OnNextPageButton);
this.btnFirst.Click += new System.Web.UI.ImageClickEventHandler
(this.OnFirstPageButton);
this.btnLast.Click += new System.Web.UI.ImageClickEventHandler
(this.OnLastPageButton);
this.ddPage.SelectedIndexChanged += new EventHandler
(this.OnPageChangedButton);
this.Load += new System.EventHandler(this.Page_Load);
}
Now let's take the Next
button for example, we forward the Click
event to this.OnNextPageButton()
function. Here's how that function looks like:
protected void OnNextPageButton(object sender, ImageClickEventArgs e)
{
DataNavigatorEventArgs args = new DataNavigatorEventArgs();
args.CurrentPage = int.Parse(lblCurrentPage.Text);
args.TotalPages = int.Parse(lblTotalPages.Text);
SetDropDownPageNumber(args.CurrentPage + 1);
OnNextPage(args);
}
We instantiate the DataNavigatorEventArgs
object, assign its values from the labels on the user control, call the SetDropDownPageNumber()
to set the correct page number displayed in the dropdown listbox, and most importantly, forward the call to OnNextPage()
to raise the event by invoking delegates.
protected virtual void OnNextPage(DataNavigatorEventArgs args)
{
if (NextPage != null)
{
NextPage(this, args);
}
}
Notice the difference in declaration and signature between OnNextPageButton()
and OnNextPage()
. (Note: the name of your function that raises the event has to be OnEventName such as OnNextPage
).
Here's the SetDropDownPageNumber()
function that sets the page number for the dropdown listbox:
private void SetDropDownPageNumber(int iCurrentPage)
{
if (ddPage.Items.Count > 0)
ddPage.SelectedIndex = iCurrentPage - 1;
}
The user control also has public get/set properties that keep track of the current page number, total page number, each image button's state and its image URL. These properties will be used on the ASP.NET page where the control resides.
Using the Control
To use the control, just drag it from the Solution Explorer and drop it on your ASP.NET page. Here's the HTML of the page:
<form id="Form1" method="post" runat="server">
<table width="100%">
<tr>
<td>
<asp:datagrid id="dgEmp" runat="server"
EnableViewState="false"
AlternatingItemStyle-BackColor="Silver"
AllowCustomPaging="true"
Width="100%" HeaderStyle-BackColor="#6633ff"
HeaderStyle-ForeColor="#ffffff"
HeaderStyle-Font-Bold="true"></asp:datagrid>
</td>
</tr>
<tr>
<td><uc1:datanavigator id="ucDataNavigator"
runat="server"></uc1:datanavigator>
</td>
</tr>
</table>
</form>
Now let's take a look at the code behind. First, don't forget to hook up all the event handlers for our control:
private void InitializeComponent()
{
this.ucDataNavigator.FirstPage += new FirstPageEventHandler
(this.FirstPage);
this.ucDataNavigator.PreviousPage += new PreviousPageEventHandler
(this.PreviousPage);
this.ucDataNavigator.NextPage += new NextPageEventHandler
(this.NextPage);
this.ucDataNavigator.LastPage += new LastPageEventHandler
(this.LastPage);
this.ucDataNavigator.PageChanged += new PageChangedEventHandler
(this.PageChanged);
this.Load += new System.EventHandler(this.Page_Load);
this.Init += new EventHandler(this.Page_Init);
}
Remember previously we made the Click
event of the Next
image button raise the NextPage
event? Here we get to define our NextPage
event handler:
protected void NextPage(object sender, DataNavigatorEventArgs e)
{
if (e.CurrentPage <= e.TotalPages)
{
ucDataNavigator.CurrentPage++;
BindGrid();
EnableDisableButtons(e.TotalPages);
}
}
Here we first increment the current page index then retrieve the data for the DataGrid, finally, we call EnableDisableButtons()
to control the state and image of each VCR-type image button.
Note that I use Page_Init()
method above to intialize the CurrentPage
property on the user control to 1 since this only has to be done once.
private void Page_Init(object sender, System.EventArgs e)
{
ucDataNavigator.CurrentPage = 1;
}
Inside the BindGrid()
method I used the following logic to calculate the number of pages:
if ((totalCount % PAGE_SIZE) == 0)
ucDataNavigator.TotalPages = totalCount/PAGE_SIZE;
else
ucDataNavigator.TotalPages = (totalCount/PAGE_SIZE) + 1;
totalCount
's value is from the output parameter of the stored procedure and PAGE_SIZE
is a constant defined as 10 in my code. So another good exercise for you to do is to externalize the PAGE_SIZE
constant; i.e., instead of hard-coding it, make it table-driven or store the value in a configuration file.
Conclusion
The biggest advantage of using user controls on your ASP.NET pages is code-reuse. This article shows you how you can utilize this concept to avoid writing repeated code. To make it one step further, instead of a user control, you could write a custom control to do this so that it'd be even easier to reuse your code.
Next time I'll show you how to add sorting functionality in our paging user control. Have fun!