Paging in DataGridView






3.19/5 (14 votes)
Enable paging on a DataGridView control in VS 2005.
Introduction
This article provides a way to programmatically implement paging in a Windows DataGridView
control. To my knowledge, there is no such functionality currently existing in the standard DataGridView
control.
Background
A few months ago, I was put on a project to help out with the work load. Deadlines were looming and the extra help was needed. The form I had to code was a query form that listed various log tables in the database. The user could then select one of these log tables from a dropdown menu and the events captured by the selected log table was displayed in a DataGridView
control. This part of the coding was easy, but there seemed to be just one problem. Sometimes these log tables were really large, containing hundreds even thousands of lines of entries. The customer wanted only to display a set amount of entries at a time and be able to page to the next, previous, last, and first pages of the grid.
I was then faced with trying to make a DataGridView
control pageable. At first I thought that this task would be easy enough, but the more I researched it, the more I realised that there was no built-in functionality for making a DataGridView
control pageable. I had to therefore come up with my own solution to extend the DataGridView
control to include paging. The article below outlines a very basic Windows Form containing a Windows Toolstrip
control with four buttons labeled First, Previous, Next, and Last. It also contains a DataGridView
control and a BindingSource
control.
A great idea for this project is to create your own custom control inheriting the base class for a DataGridView
control and just extending its functionality to include paging, but for simplicity's sake, I have done the quick and dirty version by just adding the code in the form. Perhaps in a future article I will outline the steps to create your own custom control that inherits from the DataGridView
control.
Using the code
Before we look at the code, do the following:
- Create a new C# Windows Application with Visual Studio 2005
- Drag a
DataGridView
control on to the Windows Form and call itdgNames
- Drag a
BindingSource
control on to the form - Drag a
Toolstrip
control on to the form - On the
Toolstrip
control, create four buttons using the dropdown menu provided by the control - In the Properties of each button, set the
DisplayStyle
of each button toText
- Set the buttons' text to First, Previous, Next, and Last, respectively
- Name the buttons as follows:
- First =
tsbFirst
- Previous =
tsbPrevious
- Next =
tsbNext
- Last =
tsbLast
Now that we have done this, let's look at the code.
Our Data Class
Usually you would read the data from a database via some sort of class. For simplicity's sake, I created a class called clsData
that returns a dataset of hardcoded values. But you would need to use a class that reads the data you want to display in the DataGridView
from some sort of data source. The following code is the code for the class used to return our dummy data.
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
namespace GridviewPaging
{
class clsData
{
public static DataSet GetData()
{
DataSet ds = new DataSet();
try
{
ds.Tables.Add(GetTable());
return ds;
}
catch (Exception ex)
{
String msg = ex.Message;
return null;
}
finally
{
}
}
private static DataTable GetTable()
{
// Use the MakeTable function below to create a new table.
DataTable dt;
dt = MakeTable();
try
{
int i = 0;
String Name = "Name";
String LastName = "LastName";
DataRow dr;
for (i = 0; i <= 1000; i++)
{
// Once a table has been created, use the
// NewRow to create a DataRow.
dr = dt.NewRow();
// Then add the new row to the collection.
dr["Fname"] = i + " " + Name;
dr["LName"] = i + " " + LastName;
//// Then add the new row to the collection.
dt.Rows.Add(dr);
}
return dt;
}
catch (Exception ex)
{
String msg = ex.Message;
return null;
}
finally
{
}
}
private static DataTable MakeTable()
{
// Create a new DataTable titled 'Names.'
DataTable namesTable = new DataTable("Names");
// Add three column objects to the table.
DataColumn idColumn = new DataColumn();
idColumn.DataType = System.Type.GetType("System.Int32");
idColumn.ColumnName = "RecordID";
idColumn.AutoIncrement = true;
namesTable.Columns.Add(idColumn);
DataColumn fNameColumn = new DataColumn();
fNameColumn.DataType = System.Type.GetType("System.String");
fNameColumn.ColumnName = "Fname";
fNameColumn.DefaultValue = "Fname";
namesTable.Columns.Add(fNameColumn);
DataColumn lNameColumn = new DataColumn();
lNameColumn.DataType = System.Type.GetType("System.String");
lNameColumn.ColumnName = "LName";
namesTable.Columns.Add(lNameColumn);
// Create an array for DataColumn objects.
DataColumn[] keys = new DataColumn[1];
keys[0] = idColumn;
namesTable.PrimaryKey = keys;
// Return the new DataTable.
return namesTable;
}
}
}
Our Main Form
If the code segments below don't make much sense now, don't worry. I have included the full code for the Windows Form at the end of this article.
At the top of the code window of our Windows Form, make sure you have these using
statements:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
I am also using the following namespace: namespace GridviewPaging
.
I have also added a few variables global to this form to enable the paging of the DataGridView
control. Add the following variables:
private string NavClicked = "";
private string RecordID1 = "";
private string RecordID2 = "";
//We need this dataset to enable paging
private DataSet dsTemp = new DataSet();
int CurrentPage = 1;
Just below these variables, add the following enumerator. This determines which button was clicked.
private enum NavButton
{
First = 1,
Next = 2,
Previous = 3,
Last = 4,
}
In the Form1_Load
event, add the following code:
private void Form1_Load(object sender, EventArgs e)
{
//Get the data for the selected table and populate the Grid
DataSet ds = new DataSet();
ds = GridviewPaging.clsData.GetData();
//Persist the Table Data to enable paging.
dsTemp = ds;
//If the dataset contains rows
if (ds.Tables[0].Rows.Count > 0)
{
//Send to our Fill Grid method
fillDataGrid_dtgBrowse(ds);
}
else
{
bindingSource1.DataSource = null;
bindingSource1.Clear();
dgNames.DataSource = bindingSource1;
}
}
In the Form1_Load
event, we are doing the following:
- We are instantiating a new
DataSet
calledds
. - We are setting
ds
equal to theDataSet
returned by theGetData
method in ourclsData
class. - We are setting another
DataSet
calleddsTemp
equal tods
to enable the persistence of data. - If our DataSet
ds
contains any data, we fill ourDataGridView
with this data. - If no data is returned, we clear the
DataGridView
.
We will now use two methods to enable paging. These methods are fillDataGrid_dtgBrowse
and DeterminePageBoundaries
. These methods are very well commented, so I didn't go into much detail in explaining what they do. The comments in the code do this.
fillDataGrid_dtgBrowse
#region fillDataGrid_dtgBrowse
private void fillDataGrid_dtgBrowse(DataSet dtsTableData)
{
try
{
Cursor = Cursors.WaitCursor;
//Set the BindingSource's DataSource to the DataSet Parameter passed to it.
bindingSource1.DataSource = dtsTableData.Tables[0];
//Determine the page boundaries
DeterminePageBoundaries(dtsTableData);
//Now that we have the boundaries, filter the bindingsource.
//This has the following effect as the following Query:
//select * from [LogTable] where RecordID greater or = 10 and RecordID less or = 12
bindingSource1.Filter = "RecordID >= " + RecordID1 + " and RecordID <= " + RecordID2;
//Set the Grid's DataSource to the BindingSource. This populates the grid with data.
dgNames.DataSource = bindingSource1;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Cursor = Cursors.Default;
}
}
#endregion
The fillDataGrid_dtgBrowse
method simply does the binding of the data to the DataGridView
control. Next we have the method DeterminePageBoundaries
. We need to know the boundaries of the pages to enable the paging in the DataGridView
.
DeterminePageBoundaries
#region DeterminePageBoundaries
private void DeterminePageBoundaries(DataSet dsPages)
{
int TotalRowCount = dsPages.Tables[0].Rows.Count;
//This is the maximum rows to display on a page.
//So we want paging to be implemented at 100 rows a page
int pageRows = 100;
int pages = 0;
//If the rows per page are less than the total row count do the following:
if (pageRows < TotalRowCount)
{
//If the Modulus returns > 0 then there should be another page.
if ((TotalRowCount % pageRows) > 0)
{
pages = ((TotalRowCount / pageRows) + 1);
}
else
{
//There is nothing left after the Modulus, so the pageRows divide exactly...
//...into TotalRowCount leaving no rest, thus no extra page needs to be added.
pages = TotalRowCount / pageRows;
}
}
else
{
//If the rows per page are more than the total
//row count, we will obviously only ever have 1 page
pages = 1;
}
//We now need to determine the LowerBoundary and UpperBoundary in order to correctly...
//...determine the Correct RecordID's to use in the bindingSource1.Filter property.
int LowerBoundary = 0;
int UpperBoundary = 0;
//We now need to know what button was clicked, if any (First, Last, Next, Previous)
switch (NavClicked)
{
case "First":
//First clicked, the Current Page will always be 1
CurrentPage = 1;
//The LowerBoundary will thus be ((50 * 1) - (50 - 1)) = 1
LowerBoundary = ((pageRows * CurrentPage) - (pageRows - 1));
//If the rows per page are less than the total row count do the following:
if (pageRows < TotalRowCount)
{
UpperBoundary = (pageRows * CurrentPage);
}
else
{
//If the rows per page are more than the total row count do the following:
//There is only one page, so get the total row count as an UpperBoundary
UpperBoundary = TotalRowCount;
}
//Now using these boundaries, get the appropriate RecordID's (min & max)...
//...for this specific page.
//Remember, .NET is 0 based, so subtract 1 from both boundaries
RecordID1 = dsPages.Tables[0].Rows[LowerBoundary - 1]["RecordID"].ToString();
RecordID2 = dsPages.Tables[0].Rows[UpperBoundary - 1]["RecordID"].ToString();
break;
case "Last":
//Last clicked, the CurrentPage will always be = to the variable pages
CurrentPage = pages;
LowerBoundary = ((pageRows * CurrentPage) - (pageRows - 1));
//The UpperBoundary will always be the sum total of all the rows
UpperBoundary = TotalRowCount;
//Now using these boundaries, get the appropriate RecordID's (min & max)...
//...for this specific page.
//Remember, .NET is 0 based, so subtract 1 from both boundaries
RecordID1 = dsPages.Tables[0].Rows[LowerBoundary - 1]["RecordID"].ToString();
RecordID2 = dsPages.Tables[0].Rows[UpperBoundary - 1]["RecordID"].ToString();
break;
case "Next":
//Next clicked
if (CurrentPage != pages)
{
//If we arent on the last page already, add another page
CurrentPage += 1;
}
LowerBoundary = ((pageRows * CurrentPage) - (pageRows - 1));
if (CurrentPage == pages)
{
//If we are on the last page, the UpperBoundary
//will always be the sum total of all the rows
UpperBoundary = TotalRowCount;
}
else
{
//Else if we have a pageRow of 50 and
//we are on page 3, the UpperBoundary = 150
UpperBoundary = (pageRows * CurrentPage);
}
//Now using these boundaries, get the appropriate RecordID's (min & max)...
//...for this specific page.
//Remember, .NET is 0 based, so subtract 1 from both boundaries
RecordID1 = dsPages.Tables[0].Rows[LowerBoundary - 1]["RecordID"].ToString();
RecordID2 = dsPages.Tables[0].Rows[UpperBoundary - 1]["RecordID"].ToString();
break;
case "Previous":
//Previous clicked
if (CurrentPage != 1)
{
//If we aren't on the first page already, subtract 1 from the CurrentPage
CurrentPage -= 1;
}
//Get the LowerBoundary
LowerBoundary = ((pageRows * CurrentPage) - (pageRows - 1));
if (pageRows < TotalRowCount)
{
UpperBoundary = (pageRows * CurrentPage);
}
else
{
UpperBoundary = TotalRowCount;
}
//Now using these boundaries, get the appropriate RecordID's (min & max)...
//...for this specific page.
//Remember, .NET is 0 based, so subtract 1 from both boundaries
RecordID1 = dsPages.Tables[0].Rows[LowerBoundary - 1]["RecordID"].ToString();
RecordID2 = dsPages.Tables[0].Rows[UpperBoundary - 1]["RecordID"].ToString();
break;
default:
//No button was clicked.
LowerBoundary = ((pageRows * CurrentPage) - (pageRows - 1));
//If the rows per page are less than the total row count do the following:
if (pageRows < TotalRowCount)
{
UpperBoundary = (pageRows * CurrentPage);
}
else
{
//If the rows per page are more than the total row count do the following:
//Therefore there is only one page,
//so get the total row count as an UpperBoundary
UpperBoundary = TotalRowCount;
}
//Now using these boundaries, get the appropriate RecordID's (min & max)...
//...for this specific page.
//Remember, .NET is 0 based, so subtract 1 from both boundaries
RecordID1 = dsPages.Tables[0].Rows[LowerBoundary - 1]["RecordID"].ToString();
RecordID2 = dsPages.Tables[0].Rows[UpperBoundary - 1]["RecordID"].ToString();
break;
}
}
#endregion
Lastly, we need to add code to the click events for our Next, Previous, First, and Last buttons. After you've done this, the rest is a piece of old tackie.
Button click events
#region Page through DataGridView
private void tsbFirst_Click(object sender, EventArgs e)
{
try
{
NavClicked = NavButton.First.ToString();
fillDataGrid_dtgBrowse(dsTemp);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Cursor = Cursors.Default;
}
}
// Jump to the previous record in the DataGridView
private void tsbPrevious_Click(object sender, EventArgs e)
{
try
{
NavClicked = NavButton.Previous.ToString();
fillDataGrid_dtgBrowse(dsTemp);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Cursor = Cursors.Default;
}
}
// Jump to the next record in the DataGridView
private void tsbNext_Click(object sender, EventArgs e)
{
try
{
NavClicked = NavButton.Next.ToString();
fillDataGrid_dtgBrowse(dsTemp);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Cursor = Cursors.Default;
}
}
// Jump to the last record in the DataGridView
private void tsbLast_Click(object sender, EventArgs e)
{
try
{
NavClicked = NavButton.Last.ToString();
fillDataGrid_dtgBrowse(dsTemp);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Cursor = Cursors.Default;
}
}
#endregion
After you have added the click events for the buttons on your form, your code should look like this:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace GridviewPaging
{
public partial class frmMain : Form
{
private string NavClicked = "";
private string RecordID1 = "";
private string RecordID2 = "";
//We need this dataset to enable paging
private DataSet dsTemp = new DataSet();
int CurrentPage = 1;
private enum NavButton
{
First = 1,
Next = 2,
Previous = 3,
Last = 4,
}
public frmMain()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//Get the data for the selected table and populate the Grid
DataSet ds = new DataSet();
ds = GridviewPaging.clsData.GetData();
//Persist the Table Data to enable paging.
dsTemp = ds;
//If the dataset contains rows
if (ds.Tables[0].Rows.Count > 0)
{
//Send to our Fill Grid method
fillDataGrid_dtgBrowse(ds);
}
else
{
bindingSource1.DataSource = null;
bindingSource1.Clear();
dgNames.DataSource = bindingSource1;
}
}
#region fillDataGrid_dtgBrowse
// Fills the grid with the data from the DataSet
private void fillDataGrid_dtgBrowse(DataSet dtsTableData)
{
try
{
Cursor = Cursors.WaitCursor;
//Set the BindingSource's DataSource
//to the DataSet Parameter passed to it.
bindingSource1.DataSource = dtsTableData.Tables[0];
//Determine the page boundaries
DeterminePageBoundaries(dtsTableData);
//Now that we have the boundaries, filter the bindingsource.
//This has the following effect as the following Query:
//select * from [LogTable] where RecordID >= 10 and RecordID <= 12
bindingSource1.Filter = "RecordID >= " + RecordID1 +
" and RecordID <= " + RecordID2;
//Set the Grid's DataSource to the BindingSource.
//This populates the grid with data.
dgNames.DataSource = bindingSource1;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Cursor = Cursors.Default;
}
}
#endregion
#region DeterminePageBoundaries
// This method will determine the correct
// Page Boundaries for the RecordID's to filter by
private void DeterminePageBoundaries(DataSet dsPages)
{
int TotalRowCount = dsPages.Tables[0].Rows.Count;
//This is the maximum rows to display on a page.
//So we want paging to be implemented at 100 rows a page
int pageRows = 100;
int pages = 0;
//If the rows per page are less than
//the total row count do the following:
if (pageRows < TotalRowCount)
{
//If the Modulus returns > 0 then there should be another page.
if ((TotalRowCount % pageRows) > 0)
{
pages = ((TotalRowCount / pageRows) + 1);
}
else
{
//There is nothing left after the Modulus,
//so the pageRows divide exactly...
//...into TotalRowCount leaving no rest,
//thus no extra page needs to be added.
pages = TotalRowCount / pageRows;
}
}
else
{
//If the rows per page are more than the total
//row count, we will obviously only ever have 1 page
pages = 1;
}
//We now need to determine the LowerBoundary
//and UpperBoundary in order to correctly...
//...determine the Correct RecordID's
//to use in the bindingSource1.Filter property.
int LowerBoundary = 0;
int UpperBoundary = 0;
//We now need to know what button was clicked,
//if any (First, Last, Next, Previous)
switch (NavClicked)
{
case "First":
//First clicked, the Current Page will always be 1
CurrentPage = 1;
//The LowerBoundary will thus be ((50 * 1) - (50 - 1)) = 1
LowerBoundary = ((pageRows * CurrentPage) - (pageRows - 1));
//If the rows per page are less than
//the total row count do the following:
if (pageRows < TotalRowCount)
{
UpperBoundary = (pageRows * CurrentPage);
}
else
{
//If the rows per page are more than
//the total row count do the following:
//There is only one page, so get
//the total row count as an UpperBoundary
UpperBoundary = TotalRowCount;
}
//Now using these boundaries, get the
//appropriate RecordID's (min & max)...
//...for this specific page.
//Remember, .NET is 0 based, so subtract 1 from both boundaries
RecordID1 = dsPages.Tables[0].Rows[LowerBoundary - 1]
["RecordID"].ToString();
RecordID2 = dsPages.Tables[0].Rows[UpperBoundary - 1]
["RecordID"].ToString();
break;
case "Last":
//Last clicked, the CurrentPage will always be = to the variable pages
CurrentPage = pages;
LowerBoundary = ((pageRows * CurrentPage) - (pageRows - 1));
//The UpperBoundary will always be the sum total of all the rows
UpperBoundary = TotalRowCount;
//Now using these boundaries, get the appropriate
//RecordID's (min & max)...
//...for this specific page.
//Remember, .NET is 0 based, so subtract 1 from both boundaries
RecordID1 = dsPages.Tables[0].Rows[LowerBoundary - 1]
["RecordID"].ToString();
RecordID2 = dsPages.Tables[0].Rows[UpperBoundary - 1]
["RecordID"].ToString();
break;
case "Next":
//Next clicked
if (CurrentPage != pages)
{
//If we arent on the last page already, add another page
CurrentPage += 1;
}
LowerBoundary = ((pageRows * CurrentPage) - (pageRows - 1));
if (CurrentPage == pages)
{
//If we are on the last page, the UpperBoundary
//will always be the sum total of all the rows
UpperBoundary = TotalRowCount;
}
else
{
//Else if we have a pageRow of 50 and we are
//on page 3, the UpperBoundary = 150
UpperBoundary = (pageRows * CurrentPage);
}
//Now using these boundaries, get the appropriate
//RecordID's (min & max)...
//...for this specific page.
//Remember, .NET is 0 based, so subtract 1 from both boundaries
RecordID1 = dsPages.Tables[0].Rows[LowerBoundary - 1]
["RecordID"].ToString();
RecordID2 = dsPages.Tables[0].Rows[UpperBoundary - 1]
["RecordID"].ToString();
break;
case "Previous":
//Previous clicked
if (CurrentPage != 1)
{
//If we aren't on the first page already,
//subtract 1 from the CurrentPage
CurrentPage -= 1;
}
//Get the LowerBoundary
LowerBoundary = ((pageRows * CurrentPage) - (pageRows - 1));
if (pageRows < TotalRowCount)
{
UpperBoundary = (pageRows * CurrentPage);
}
else
{
UpperBoundary = TotalRowCount;
}
//Now using these boundaries,
//get the appropriate RecordID's (min & max)...
//...for this specific page.
//Remember, .NET is 0 based, so subtract 1 from both boundaries
RecordID1 = dsPages.Tables[0].Rows[LowerBoundary - 1]
["RecordID"].ToString();
RecordID2 = dsPages.Tables[0].Rows[UpperBoundary - 1]
["RecordID"].ToString();
break;
default:
//No button was clicked.
LowerBoundary = ((pageRows * CurrentPage) - (pageRows - 1));
//If the rows per page are less than
//the total row count do the following:
if (pageRows < TotalRowCount)
{
UpperBoundary = (pageRows * CurrentPage);
}
else
{
//If the rows per page are more than
//the total row count do the following:
//Therefore there is only one page,
//so get the total row count as an UpperBoundary
UpperBoundary = TotalRowCount;
}
//Now using these boundaries, get the
//appropriate RecordID's (min & max)...
//...for this specific page.
//Remember, .NET is 0 based, so subtract 1 from both boundaries
RecordID1 = dsPages.Tables[0].Rows[LowerBoundary - 1]
["RecordID"].ToString();
RecordID2 = dsPages.Tables[0].Rows[UpperBoundary - 1]
["RecordID"].ToString();
break;
}
}
#endregion
#region Page through DataGridView
// Jump to the first record in the DataGridView
private void tsbFirst_Click(object sender, EventArgs e)
{
try
{
NavClicked = NavButton.First.ToString();
fillDataGrid_dtgBrowse(dsTemp);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Cursor = Cursors.Default;
}
}
// Jump to the previous record in the DataGridView
private void tsbPrevious_Click(object sender, EventArgs e)
{
try
{
NavClicked = NavButton.Previous.ToString();
fillDataGrid_dtgBrowse(dsTemp);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Cursor = Cursors.Default;
}
}
// Jump to the next record in the DataGridView
private void tsbNext_Click(object sender, EventArgs e)
{
try
{
NavClicked = NavButton.Next.ToString();
fillDataGrid_dtgBrowse(dsTemp);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Cursor = Cursors.Default;
}
}
// Jump to the last record in the DataGridView
private void tsbLast_Click(object sender, EventArgs e)
{
try
{
NavClicked = NavButton.Last.ToString();
fillDataGrid_dtgBrowse(dsTemp);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Cursor = Cursors.Default;
}
}
#endregion
}
}
I hope that this will shed some light on how to make a DataGridView
pageable. It is probably not the easiest method to accomplish this, but it worked very well for me. Download the source code and demo for the sample application and play around with it a bit.
Points of interest
The class clsData
has some nice examples of how to create a datatable and add that datatable to a dataset. Somewhere sometime you might just need to do this, so have a look at the included class clsData
as well.
Updates
- 20 June 2007: I was going to post another article, extending this code into a user control that would include paging. However, after reading my article, topherino posted his own user control extending the
DataGridView
control to include paging. (See the Messages section below for the link to his article.) Check out his article for aDataGridView
control that you simply drag and drop on to your form. It's a nicely written control and well worth using in your applications. - 3 July 2007: I have found a control in Visual Studio 2005 that provides more or less the functionality we need for paging. After you add a
BindingSource
control to your form, add aBindingNavigator
control to the form as well. You set theBindingSource
to your dataset, like this:
bindingSource1.DataSource = ds.Tables[0];
Then you set your DataGridView
's datasource to the BindingSource
like this:
dgNames.DataSource = bindingSource1;
After you do this, you add the BindingNavigator
control to the form and set its BindingSource
property to your BindingSource
(bindingSource1
) that you added to your page. The BindingNavigator
only allows you to advance one row at a time when you click the Next button. I do feel however that there might be a way to override this functionality and make it advance multiple rows at a time. Anyway, it's worth looking into. I haven't had time to look at this myself, but I'm sure that it could help others out there that only want to move one row at a time.