Introduction
This article is about Custom Gridview
and it also covers some basic features of gridview
.
- Custom Paging:
Gridview
paging is available with this control but default paging retrieves all data from the database and shows it page-wise. Custom paging loads only that data which will be displayed on a page and hence improves performance.
- Custom Sorting: Normally sorting is applied only for that data which is shown in grid but here data is first sorted and then shown to user.
- Custom Export: Normally export grid features will export only that data which is shown in grid but here complete data is exported into Excel.
- Freeze Header
Gridview
: Header is frozen for both horizontal and vertical scrollbar.
- Dynamic Column Resizing: Column can be re-sized dynamically by Mouse Drag event.
- Row Hover and Selection:
Gridview
Row background color changes on mouse hover and mouse click event, it improves Gridview
GUI.
- Row Cell Tooltip: Tooltip is shown on Grid cell Mouse Hover. This feature is useful when cell text is larger than cell width so here only small text with "..." is shown and complete text is visible in tooltip.
Normal Gridview
Gridview
is used to show data in a tabular form with theme and CSS. It works fine for small amounts of data but when volume of data is high then performance gets reduced in loading and rendering the data. To overcome rendering issue, default paging is a good option.
Default Paging
Default paging is implemented by setting a property of Gridview
(AllowPaging = "true"
). Default page size is 10
and default page index is 0
. Also, one event is required to implement default paging, i.e. OnPageIndexChanging = "grdView_PageIndexChanging"
. So we have to bind whole data on page index changing event. Here we improve some performance by fixing rendering issue because only page size data renders on page but loading whole data is also a big issue for huge amount of data. To overcome this issue, custom paging comes into the picture.
protected void grdView_PageIndexChanging(object sender,
GridViewPageEventArgs e)
{
grdView.PageIndex = e.NewPageIndex;
pBindData(null);
}
Custom Paging
If the volume of data is high, then the thought should be to load only that data which is to be displayed on a page rather than loading all the data. Simple stored procedure will not be the solution in Oracle. For that, an Oracle package is implemented which will retrieve data from the database according to the first and the last index. To run custom Paging, you need Oracle database. Create Oracle package as shown below and Set connection string on CustomGridView
Page.
The Custom paging feature of this article is based on GridView Custom Paging.
But the challenge was to apply custom paging with Oracle database and this is the prime reason to publish this article.
Oracle Package to Retrieve Data from Database
CREATE OR REPLACE PACKAGE SUPERVISOR.PKG_GetArea as
TYPE MyRefCur is REF CURSOR;
procedure GetArea(inStartRowIndex in number, inEndRowIndex
in number, inSortExp in varchar2, outTotalRows out number,
outAreaCur OUT MyRefCur);
END;
/
CREATE OR REPLACE PACKAGE BODY SUPERVISOR.PKG_GetArea as
PROCEDURE GetArea(inStartRowIndex in number, inEndRowIndex
in number, inSortExp in varchar2, outTotalRows out number,
outAreaCur OUT MyRefCur)
IS
BEGIN
select count(*) into outTotalRows from tb_area_mst;
if(inEndRowIndex = -1) then
open outAreaCur for select AREA_ID, AREA_NAME, AREA_CODE,
LEVEL_ID, PARENT_AREA_ID, ADDRESS, PHONE_NO,CONTACT_PERSON,STATUS,
AREATREECODE from TB_AREA_MST order by Area_ID;
else
begin
open outAreaCur for select AREA_ID, AREA_NAME, AREA_CODE,
LEVEL_ID, PARENT_AREA_ID, ADDRESS, PHONE_NO,CONTACT_PERSON,
STATUS,AREATREECODE from (select AREA_ID, AREA_NAME, AREA_CODE,
LEVEL_ID, PARENT_AREA_ID, ADDRESS,
PHONE_NO,CONTACT_PERSON,STATUS,AREATREECODE, ROW_NUMBER()
OVER
(
ORDER BY
Decode(inSortExp,'AREA_ID ASC',AREA_ID) ASC,
Decode(inSortExp,'AREA_ID DESC',AREA_ID) DESC,
Decode(inSortExp,'AREA_NAME ASC',AREA_NAME) ASC,
Decode(inSortExp,'AREA_NAME DESC',AREA_NAME) DESC,
Decode(inSortExp,'AREA_CODE ASC',AREA_CODE) ASC,
Decode(inSortExp,'AREA_CODE DESC',AREA_CODE) DESC,
Decode(inSortExp,'LEVEL_ID ASC',LEVEL_ID) ASC,
Decode(inSortExp,'LEVEL_ID DESC',LEVEL_ID) DESC,
Decode(inSortExp,'PARENT_AREA_ID ASC',PARENT_AREA_ID) ASC,
Decode(inSortExp,'PARENT_AREA_ID DESC',PARENT_AREA_ID) DESC,
Decode(inSortExp,'ADDRESS ASC',ADDRESS) ASC,
Decode(inSortExp,'ADDRESS DESC',ADDRESS) DESC,
Decode(inSortExp,'PHONE_NO ASC',PHONE_NO) ASC,
Decode(inSortExp,'PHONE_NO DESC',PHONE_NO) DESC,
Decode(inSortExp,'CONTACT_PERSON ASC',CONTACT_PERSON) ASC,
Decode(inSortExp,'CONTACT_PERSON DESC',CONTACT_PERSON) DESC,
Decode(inSortExp,'STATUS ASC',STATUS) ASC,
Decode(inSortExp,'STATUS DESC',STATUS) DESC,
Decode(inSortExp,'AREATREECODE ASC',AREATREECODE) ASC,
Decode(inSortExp,'AREATREECODE DESC',AREATREECODE) DESC,
AREA_ID
)
R FROM TB_AREA_MST)
WHERE R BETWEEN inStartRowIndex AND inEndRowIndex;
end;
End if;
END;
END;
/
C# Code to Bind Data to Grid
private void pBindData(string aSortExp, bool aIsCompleteData)
{
OracleConnection objCon = null;
OracleCommand objCmd = null;
DataSet ds = null;
OracleDataAdapter objAdp = null;
try
{
objCon = new OracleConnection("Data Source=ABC;
User ID=SUPERVISOR;Password=XYZ");
objCon.Open();
objCmd = new OracleCommand("PKG_GetArea.GetArea",
objCon);
objCmd.CommandType = CommandType.StoredProcedure;
objCmd.Parameters.Add(new OracleParameter
("inStartRowIndex", OracleDbType.Int32)).Direction
= ParameterDirection.Input;
objCmd.Parameters["inStartRowIndex"].Value =
((currentPageNumber - 1) * PAGE_SIZE) + 1;
objCmd.Parameters.Add(new OracleParameter
("inEndRowIndex", OracleDbType.Int32)).Direction =
ParameterDirection.Input;
if (aIsCompleteData)
{
objCmd.Parameters["inEndRowIndex"].Value = -1;
}
else
{
objCmd.Parameters["inEndRowIndex"].Value =
(currentPageNumber * PAGE_SIZE);
}
objCmd.Parameters.Add(new OracleParameter
("inSortExp", OracleDbType.Varchar2)).Direction =
ParameterDirection.Input;
objCmd.Parameters["inSortExp"].Value = aSortExp;
objCmd.Parameters.Add(new OracleParameter
("outTotalRows", OracleDbType.Int32)).Direction =
ParameterDirection.Output;
objCmd.Parameters.Add(new OracleParameter
("AreaCur", OracleDbType.RefCursor)).Direction =
ParameterDirection.Output;
ds = new DataSet();
objAdp = new OracleDataAdapter(objCmd);
objAdp.Fill(ds);
grdView.DataSource = ds.Tables["Table"];
grdView.DataBind();
ViewState["ReportTime"] = DateTime.Now;
double totalRows = (int)objCmd.
Parameters["outTotalRows"].Value;
lblTotalPages.Text = GetTotalPages(totalRows)
.ToString();
ddlPage.Items.Clear();
for (int i = 1; i < Convert.ToInt32
(lblTotalPages.Text) + 1; i++)
{
ddlPage.Items.Add(new ListItem(i.ToString()));
}
ddlPage.SelectedValue = currentPageNumber.ToString();
if (currentPageNumber == 1)
{
lnkbtnPre.Enabled = false;
lnkbtnPre.CssClass = "GridPagePreviousInactive";
lnkbtnFirst.Enabled = false;
lnkbtnFirst.CssClass = "GridPageFirstInactive";
if (Int32.Parse(lblTotalPages.Text) > 0)
{
lnkbtnNext.Enabled = true;
lnkbtnNext.CssClass = "GridPageNextActive";
lnkbtnLast.Enabled = true;
lnkbtnLast.CssClass = "GridPageLastActive";
}
else
{
lnkbtnNext.Enabled = false;
lnkbtnNext.CssClass = "GridPageNextInactive";
lnkbtnLast.Enabled = false;
lnkbtnLast.CssClass = "GridPageLastInactive";
}
}
else
{
lnkbtnPre.Enabled = true;
lnkbtnPre.CssClass = "GridPagePreviousActive";
lnkbtnFirst.Enabled = true;
lnkbtnFirst.CssClass = "GridPageFirstActive";
if (currentPageNumber == Int32.Parse
(lblTotalPages.Text))
{
lnkbtnNext.Enabled = false;
lnkbtnNext.CssClass = "GridPageNextInactive";
lnkbtnLast.Enabled = false;
lnkbtnLast.CssClass = "GridPageLastInactive";
}
else
{
lnkbtnNext.Enabled = true;
lnkbtnNext.CssClass = "GridPageNextActive";
lnkbtnLast.Enabled = true;
lnkbtnLast.CssClass = "GridPageLastActive";
}
}
}
catch (Exception ex)
{
lblMessage.Text = ex.Message;
}
finally
{
if (objCmd != null)
{
objCmd.Dispose();
}
if (objAdp != null)
{
objAdp.Dispose();
}
if (ds != null)
{
ds.Dispose();
}
if ((objCon != null) && (objCon.State ==
ConnectionState.Open))
{
objCon.Close();
objCon.Dispose();
}
objCmd = null;
objAdp = null;
ds = null;
objCon = null;
}
}
Custom Sorting
Custom sorting is different than traditional sorting in the way that it sorts not only grid data but it sorts whole data and then displays the paged data into grid. To do this task, sort expression is passed into Oracle package and then data is sorted according to sort expression using decode method. I know that applying decode method for each column is not a good way, but I have no other option to do this task.
protected void grdView_Sorting(object sender, GridViewSortEventArgs e)
{
if (string.Compare(Convert.ToString(ViewState["SortOrder"]),
" ASC", true) == 0)
{
ViewState["SortOrder"] = " DESC";
}
else
{
ViewState["SortOrder"] = " ASC";
}
pBindData(e.SortExpression + ViewState["SortOrder"], false);
}
Custom Export
By default, the current page of the Gridview
is exported but here whole data is exported into Excel. To do this task, we simply retrieve whole data from database and export it without rendering the data into gridview
. In case of Default Paging, only current page data with paging is exported directly. To fix this issue, we first set gridview Allowpaging
property to false
and then export the data.
protected void lnkbtnExport_Click(object sender, EventArgs e)
{
if (grdView.Rows.Count > 0)
{
grdView.AllowPaging = false;
pBindData(null);
pExportGridToExcel(grdView, "CustomGridView_"
+ Convert.ToString(ViewState["ReportTime"]) + ".xls");
}
}
private void pExportGridToExcel(GridView grdGridView,
String fileName)
{
Response.Clear();
Response.AddHeader("content-disposition",
String.Format("attachment;filename={0}", fileName));
Response.Charset = "";
Response.ContentType = "application/vnd.xls";
StringWriter stringWrite = new StringWriter();
HtmlTextWriter htmlWrite =
new HtmlTextWriter(stringWrite);
ClearControls(grdCustom);
grdGridView.RenderControl(htmlWrite);
Response.Write(stringWrite.ToString().
Replace(HttpUtility.HtmlDecode(" "), " "));
Response.End();
}
Freeze Header Gridview
Freeze Header Gridview
is implemented using CSS.
.divGrid
{
border-style: solid;
border-width: 1px;
border-color: #4d4d4d;
overflow:auto;
}
.CustomGrid
{
table-layout: fixed;
width:902px;
cursor: pointer;
}
.GridRowSelect
{
background-color: #4B4B4B;
cursor: pointer;
}
.GridRowHover
{
background-color: #FFFFE1;
cursor: pointer;
}
.GridHeader
{
margin: 0px;
border: 1px solid #4D4D4D;
background-position: left top;
font-family: Tahoma;
letter-spacing:1pt;
font-size: 8pt;
text-decoration: none;
font-weight: normal;
font-style: normal;
font-variant: normal;
text-transform: none;
color: #E5E5E5;
text-align: center;
vertical-align: middle;
padding: 3px 6px 3px 6px;
background-image: url('Images/Header_Grid.png' );
background-repeat: repeat-x;
background-color:#4B4B4B;
word-wrap: break-word;
text-overflow:ellipsis;
overflow:hidden;
white-space: -moz-pre-wrap !important;
}
.GridHeader A
{
font-family: Tahoma;
letter-spacing:1pt;
font-size: 8pt;
text-decoration: none;
font-weight: normal;
font-style: normal;
font-variant: normal;
text-transform: none;
color: #E5E5E5;
text-align: center;
vertical-align: middle;
}
.GridLine
{
border: .25px solid #4D4D4D;
}
.GridItem1
{
border: 1px solid #4D4D4D;
text-align: left;
vertical-align: middle;
padding: 4px 6px 4px 6px;
font-family: Verdana;
font-size: 8pt;
color: #000000;
font-weight: normal;
font-style: normal;
font-variant: normal;
text-transform: none;
word-wrap: break-word;
text-overflow:ellipsis;
overflow:hidden;
white-space:nowrap;
}
div#customGridDiv
{
overflow: scroll;
position: relative;
}
div#customGridDiv th
{
top: expression(document.getElementById("customGridDiv")
.scrollTop-2);
left: expression(parentNode.parentNode.parentNode.
parentNode.scrollLeft);
position: relative;
z-index: 20;
}
Dynamic Column Resizing
Dynamic Column Resizing is implemented using JS. I have found one good JS from Matt Berseth Article.
var _isResizing;
var _element;
var _ths;
function pageLoad(args){
_ths = $get('grdCustom').getElementsByTagName('TH');
if(_ths.length > 1){
for(i = 0; i < _ths.length; i++){
_ths[i].style.width = Sys.UI.DomElement
.getBounds(_ths[i]).width + 'px';
if(i < _ths.length - 1){
$addHandler(_ths[i], 'mousemove', _onMouseMove);
$addHandler(_ths[i], 'mousedown', _onMouseDown);
}
}
$addHandler(document, 'mouseup', _onMouseUp);
$addHandler(document, 'selectstart', _onSelectStart);
}
}
function _onMouseMove(args){
if(_isResizing){
var bounds = Sys.UI.DomElement.getBounds(_element);
var width = args.clientX - bounds.x;
if(width > 1){
var nextColumn = _element.nextSibling;
var nextColumnWidth;
if(width < _toNumber(_element.style.width)){
nextColumnWidth = _toNumber(nextColumn.style.width) +
_toNumber(_element.style.width) - width;
}
else if(width > _toNumber(_element.style.width)){
nextColumnWidth = _toNumber(nextColumn.style.width) -
(width - _toNumber(_element.style.width));
}
if(nextColumnWidth > 1){
_element.style.width = width + 'px';
nextColumn.style.width = nextColumnWidth + 'px';
}
}
}
else{
var bounds = Sys.UI.DomElement.getBounds(args.target);
if(Math.abs((bounds.x + bounds.width) - (args.clientX))
<= 2) {
args.target.style.cursor = 'e-resize';
}
else{
args.target.style.cursor = '';
}
}
}
function _onMouseDown(args){
if(args.target.style.cursor == 'e-resize') {
_isResizing = true;
_element = args.target;
}
}
function _onMouseUp(args){
if(_isResizing){
_isResizing = false;
_element = null;
for(i = 0; i < _ths.length; i++){
_ths[i].style.cursor = '';
}
}
}
function _onSelectStart(args){
if(_isResizing){
args.preventDefault();
return false;
}
}
function _toNumber(m) {
return new Number(m.replace('px', ''));
}
Row Hover, Selection and Tooltip
Row Hover is implemented using CSS.
.GridRowHover
{
background-color: #FFFFE1;
cursor: pointer;
}
Row Selection is implemented using JS.
var SelectedRow = null;
var SelectedRowIndex = null;
var UpperBound = null;
var LowerBound = null;
window.onload = function()
{
UpperBound = parseInt('<%# PAGE_SIZE %>') - 1;
LowerBound = 0;
SelectedRowIndex = -1;
}
function SelectRow(CurrentRow, RowIndex)
{
if(SelectedRow == CurrentRow || RowIndex > UpperBound ||
RowIndex < LowerBound) return;
if(SelectedRow != null)
{
SelectedRow.style.backgroundColor =
SelectedRow.originalBackgroundColor;
SelectedRow.style.color = SelectedRow.originalForeColor;
}
if(CurrentRow != null)
{
CurrentRow.originalBackgroundColor =
CurrentRow.style.backgroundColor;
CurrentRow.originalForeColor = CurrentRow.style.color;
CurrentRow.style.backgroundColor = '#FFFF00';
CurrentRow.style.color = 'Black';
}
SelectedRow = CurrentRow;
SelectedRowIndex = RowIndex;
setTimeout("SelectedRow.focus();",0);
}
function SelectSibling(e)
{
var e = e ? e : window.event;
var KeyCode = e.which ? e.which : e.keyCode;
if(KeyCode == 40)
SelectRow(SelectedRow.nextSibling, SelectedRowIndex + 1);
else if(KeyCode == 38)
SelectRow(SelectedRow.previousSibling, SelectedRowIndex - 1);
return false;
}
Grid cell Tootip is implemented using "title
" property of Grid Cell.
Above JS, CSS and Title
Property is called on Gridview RowDataBound
Event.
protected void grdView_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
for (int i = 0; i < e.Row.Cells.Count; i++)
{
e.Row.Cells[i].CssClass = "GridItem1";
if (e.Row.Cells[i].Text.Trim() != " ")
{
e.Row.Cells[i].Attributes.Add("title", e.Row.Cells[i].Text);
}
}
e.Row.Attributes.Add
("onmouseover", "javascript:this.className = 'GridRowHover'");
e.Row.Attributes.Add("onmouseout", "javascript:this.className = ''");
e.Row.TabIndex = -1;
e.Row.Attributes["onclick"] = string.Format("javascript:SelectRow(this, {0});",
e.Row.RowIndex);
e.Row.Attributes["onkeydown"] = "javascript:return SelectSibling(event);";
e.Row.Attributes["onselectstart"] = "javascript:return false;";
}
}
Limitations
- Decode method is used for each column for both ASC and DESC condition to implement custom sorting.
- Freeze Header
Gridview
will work on Internet Explorer only.
References
History
- Version 1.0.0.0 is the initial version that includes ASP.NET
Gridview
with Default and Custom Paging, Sorting, Export Data, Freeze Header, Column Resizing, Row Hover, Row Selection, Grid Cell Tooltip features for better Performance, functionality and GUI.