Introduction
This article provides a simple reusable cross browser scrollable GridView in a web user control that can be used with any data source.
Background
One of the most common pieces of information displayed on web pages is information from a database. And, the most common web controls used to display database information in a browser are DataGrid
s and GridView
s. However, these controls don't provide any features for scrolling. So, if there are more rows of information than will fit neatly on one page of the browser, the user must scroll down the page to see all of the data or use pagination. Pagination is not a bad idea unless the visitor wishes to download all of the content.
One method commonly used to add scrolling capability is to wrap the GridView
declaration inside HTML DIV
tags. When you do this, the header of the GridView
scrolls along with all of the information being displayed. To resolve this issue, many developers hide the header values in the GridView
and then add a separate table above the GridView
to show the column headings. This solution works fine in IE based browsers, but Mozilla based browsers render the tables differently horizontally. As a consequence, the developer must make cumbersome adjustments to the header table so that the table and the GridView
align correctly in all browsers. Additionally, if the data changes as a result of new application specifications, the developer must manually change the header table separately. This article shows how a simple web user control can be created to display rows of information with scrollbars.
Using the code
Requirements
- Visual Studio 2005
- A database like SQL Server 2005 Express
- Internet Information Services 6.0
Example
An application developer is asked to display a customer contact list in a web page. The list has both domestic and international customers.
The database schema looks like this:
The fields desired on the web page are:
- Name
- Postal Code
- Street Address
- Email Address
- City
- Phone Number and Extension (if one is provided)
- State or Country
- First Name
- Last Name
The database never has more than 2000 customers at one time. Use a common table expression and a Union
statement to combine the domestic addresses and the international addresses into one recordset. Then, use a Stored Procedure with no input parameters to return the entire customer list.
An application developer sometimes writes their Select
statement like this:
'Using a common table expression to combine the domestic and international addresses
With Addresses as (
select DomesticAddressID as D_ID, 0 as I_ID, StAddress,
City, State, PostalCode from DomesticAddresses
Union
Select 0 as D_ID, InternationaAddressID as I_ID, StAddress,
City, Country as [State], PostalCode from InternationalAddresses
)
'retrieving the columns from the three tables - customers, Contact Information and Addresses
Select * From Customers c Inner Join ContactInformation ci on
c.ContactInformationID = ci.ContactInformation
Inner Join addresses on
c.DomesticAddress = addresses.D_ID and
c.internationalAddress = addresses.I_ID
Note: The default value for InternationalAddressID
is 0 when the customer is domestic, and vise versa when the customer is international.
Then, in the VB code, the appropriate column headings are added to a detached table, and non-essential columns are removed.
A better method is to include all of the appropriate column names in the Stored Procedure. Like this:
With Addresses as (
select DomesticAddressID as D_ID, 0 as I_ID, StAddress as [Street Address],
City, State as [Country or State], PostalCode as [Postal Code]
from DomesticAddresses
Union
Select 0 as D_ID, InternationaAddressID as I_ID, StAddress as [Street Address],
City, Country as [Country or State],
PostalCode as [Postal Code] from InternationalAddresses
)
Select Name, a.[Street Address], a.City, a.[Country or State], a.[Postal Code],
ci.email as [Email Address], ci.phone as [Phone Number],
ISNULL(ci.extension '') AS Extension,
ci.fname as [First Name], ci.lname as [Last Name]
From
Customers c Inner Join ContactInformation
ci on c.ContactInformationID = ci.ContactInformationID
Inner Join addresses a on c.DomesticAddressID = a.D_ID and
c.internationalAddressID = a.I_ID
When everything is spelled out in T-SQL, fields can easily be added and / or removed as requirements change without changing any of the Visual Basic code.
With this Stored Procedure in place, the only requirement in your VB code is to execute the Stored Procedure in a command object and bind the returned recordset to a GridView
.
Web User Control
Provided below is a picture of the user control. It has two GridView
s on it, a header GridView
and a body GridView
. The header GridView
doesn't have any rows of data. Its sole purpose is to display the column header text. The body GridView
displays the data rows; the value for the ShowHeaderRow
property is False
, and the RowStyle
TextWrap
property is True
. The body GridView
is enclosed within a div
container. The div
tag style is described in the stylesheet section.
The code-behind class provides subroutines to set up the header GridView
and set the column widths for the header GridView
and the body GridView
.
Member Name
| Member Type
| Data Type
Or Return Type
| Parameters
(Name:Type)
| Access Level
| Other Modifiers
|
_Recordset
| variable
| DataSet
| | Private
| |
_ColWidths
| variable
| ArrayList
| | Private
| |
Recordset
| property
| DataSet
| | Public
| |
ColWidths
| property
| ArrayList
| | Public
| |
Page_load
| subroutine
| NA
| NA
| Protected
| |
HRowDataBound
| subroutine
| NA
| NA
| Public
| Handles GVHeader.RowDataBound
|
BRowDataBound
| subroutine
| NA
| NA
| Public
| Handles GVBody.RowDataBound
|
RemoveRows
| subroutine
| NA
| ds: dataset reference
| Private
| |
AddHeaderRow
| subroutine
| NA
| ds: dataset reference
| Private
| |
Web User Control File ASCX
<%@ Control Language="vb" AutoEventWireup="false"
CodeBehind="ScrollingGridView.ascx.vb" Inherits="YourNamespace.ScrollingGridView" %>
<asp:GridView ID="GVHeader" runat="server" ForeColor="Black">
</asp:GridView>
<div id="AdjResultsDiv" style="WIDTH: 610px; HEIGHT: 200px">
<asp:GridView ID="GVBody" runat="server" ShowHeader="False" ForeColor="Black">
</asp:GridView>
</div>
Code-Behind File
Public Partial Class ScrollingGridView _
Inherits System.Web.UI.UserControl
#Region "Private members"
Private _Recordset As DataSet
Private _ColWidths As New ArrayList
#End Region
#Region "Properties"
Public Property Recordset() As DataSet
Get
Return _Recordset
End Get
Set(ByVal value As DataSet)
_Recordset = value
End Set
End Property
Public Property ColWidths() As ArrayList
Get
Return _ColWidths
End Get
Set(ByVal value As ArrayList)
_ColWidths = value
End Set
End Property
#End Region
#Region "Page Events"
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
If Not Me.IsPostback
Me.GVData.DataSource = Me._Recordset
Me.GVData.DataBind()
Me.RemoveRows(_Recordset)
Me.AddHeaderRow(_Recordset)
End if
End Sub
Public Sub HRowDataBound(ByVal sender As Object, _
ByVal e As GridViewRowEventArgs) Handles GVHeader.RowDataBound
If e.Row.RowType = DataControlRowType.Header Then
Dim i As Integer
For i = 0 To _ColWidths.Count - 1
e.Row.Cells(i).Width = System.Web.UI.WebControls.Unit.Pixel(_ColWidths(i))
Next
End If
End Sub
Public Sub DRowDataBound(ByVal sender As Object, _
ByVal e As GridViewRowEventArgs) Handles GVBody.RowDataBound
If e.Row.RowType <> DataControlRowType.Header Then
Dim i As Integer
For i = 0 To _ColWidths.Count - 1
e.Row.Cells(i).Width = System.Web.UI.WebControls.Unit.Pixel(_ColWidths(i))
Next
End If
End Sub
#End Region
#Region "Subroutines"
Private Sub RemoveRows(ByRef ds As DataSet)
ds.Tables(0).Rows.Clear()
End Sub
Private Sub AddHeaderRow(ByRef ds As DataSet)
Dim rcount As Integer = ds.Tables(0).Rows.Count
If (rcount = 0) Then
ds.Tables(0).Rows.Add(ds.Tables(0).NewRow())
Me.GVHeader.DataSource = ds.Tables(0)
Me.GVHeader.DataBind()
Dim colCount As Integer = Me.GVHeader.Rows(0).Cells.Count
Me.GVHeader.Rows(0).Cells.Clear()
Me.GVHeader.Rows(0).Cells.Add(New TableCell())
Me.GVHeader.Rows(0).Cells(0).ColumnSpan = colCount
Me.GVHeader.Rows(0).Cells(0).Text = String.Empty
End If
End Sub
#End Region
Here's the stylesheet syntax:
div#AdjResultsDiv {
width: 1080px;
height: 500px;
overflow: scroll;
position: relative;
}
div#AdjResultsDiv th
{
background-color:Navy;
cursor: default;
top: expression(document.getElementById("AdjResultsDiv").scrollTop-2);
left: expression(parentNode.parentNode.parentNode.parentNode.scrollLeft);
position: relative;
z-index: 20;
color:white;
}
div#AdjResultsDiv tbody
{
margin:auto;
}
Here's how it's implemented in an ASPX file:
To use this control, create a new web form (ASPX file) in Visual Studio. Add the web user control to the page. Then, add the style elements to a new or existing stylesheet.
Web Form
The presentation file contains the web user control, and the code-behind file contains the subroutines to retrieve a dataset and populate the web user control public properties: RecordSet
and ColWidths
.
Member Name
| Member Type
| Data Type
Or Return Type
| Parameters
(Name:Type)
| Access Level
| Other Modifiers
|
GetData
| function
| DataSet
| NA
| Private
| |
Page_Load
| subroutine
| NA
| NA
| Protected
| |
Web Form ASPX File
<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="TestScrollGrid.aspx.vb"
Inherits="YourNamespace.TestScrollGrid" %>
<%@ Register Src="UserControls/ScrollingGridView.ascx"
TagName="ScrollingGridView" TagPrefix="uc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="TestScrollGrid" runat="server">
<div>
<uc1:ScrollingGridView ID="ScrollingGridViewSample" runat="server" />
</div>
</form>
</body>
</html>
Code-Behind File
Partial Public Class TestScrollGrid _
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
If Not Me.IsPostBack Then
Dim ds As DataSet
Dim colwidths As New ArrayList
Try
ds = getdata()
Me.ScrollingGridViewSample.Recordset = ds
colwidths.Add(100)
colwidths.Add(500)
Me.ScrollingGridViewSample.ColWidths = colwidths
Catch ex as Exception
Finally
ds = Nothing
colwidths = Nothing
End Try
End If
End Sub
Private Function GetData() As DataSet
Dim connString as string = _
"data source=SQLServerName; initial catalog=DatabaseName;" & _
" persist security info=False; user id=UserID; password= password;"
Dim con As SqlClient.SqlConnection = New SqlConnection(ConnString)
con.Open()
Dim cmd As New SqlCommand("dbo.StoredProcName", con)
cmd.CommandType = CommandType.StoredProcedure
Dim ds as new dataset
Dim da As New SqlClient.SqlDataAdapter
Try
da.SelectCommand = cmd
da.Fill(ds)
GetData =ds
Finally
cmd = Nothing
da = Nothing
con.Close()
con = Nothing
End Try
End Function
End Class
That's all!