65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET & Oracle 中自定义分页 GridView。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (10投票s)

2009年11月26日

CPOL

4分钟阅读

viewsIcon

89313

downloadIcon

2938

ASP.NET Gridview 具备默认和自定义分页、排序、导出数据、冻结标题行、列宽调整、行悬停、行选择、单元格工具提示等功能,以提高性能、功能性和 GUI 效果,并支持 Oracle 数据库。

引言

本文主要介绍自定义 Gridview ,同时也涵盖了 gridview 的一些基本功能。

  1. 自定义分页:Gridview 自带分页功能,但默认分页会从数据库检索所有数据并按页显示。自定义分页则只加载将要显示在页面上的数据,从而提高性能。
  2. 自定义排序:通常排序只应用于显示在网格中的数据,而这里的数据会先被排序,然后才呈现给用户。
  3. 自定义导出:通常导出网格的功能只导出显示在网格中的数据,而这里会将所有数据导出到 Excel。
  4. 冻结标题行 Gridview :标题行在水平和垂直滚动条下都会被冻结。
  5. 动态列宽调整:可以通过鼠标拖动事件动态调整列宽。
  6. 行悬停和选择:鼠标悬停和点击事件发生时,Gridview 行的背景色会发生变化,这改善了 Gridview 的 GUI 效果。
  7. 单元格工具提示:鼠标悬停在网格单元格时会显示工具提示。当单元格文本超出单元格宽度时,此功能非常有用,此时只显示带有“...”的简短文本,完整文本将在工具提示中显示。

普通 Gridview

Gridview 用于以表格形式显示数据,并带有主题和 CSS。对于少量数据,它工作得很好,但当数据量很大时,加载和渲染数据的性能会下降。为了克服渲染问题,默认分页是一个不错的选择。

默认分页

默认分页通过设置 Gridview 的属性 (AllowPaging = "true") 来实现。默认页大小为 10 ,默认页索引为 0。此外,实现默认分页还需要一个事件,即 OnPageIndexChanging = "grdView_PageIndexChanging"。因此,我们必须在页索引更改事件中绑定所有数据。此处通过修复渲染问题来提高一些性能,因为只有页面大小的数据会在页面上渲染,但加载所有数据对于大量数据来说也是一个大问题。为了解决这个问题,自定义分页就派上用场了。

protected void grdView_PageIndexChanging(object sender,
                                           GridViewPageEventArgs e)
{
     grdView.PageIndex = e.NewPageIndex;
     pBindData(null);
} 

自定义分页

如果数据量很大,那么应该考虑只加载要在页面上显示的数据,而不是加载所有数据。简单的存储过程在 Oracle 中不是解决方案。为此,需要实现一个 Oracle 包,该包将根据第一个和最后一个索引从数据库检索数据。要运行自定义分页,您需要 Oracle 数据库。按以下方式创建 Oracle 包,并在 CustomGridView 页面上设置连接字符串。

本文的自定义分页功能基于 GridView Custom Paging

但是挑战在于如何将自定义分页应用于 Oracle 数据库,这也是 **发布本文的主要原因**。

从数据库检索数据的 Oracle 包

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# 代码

 private void pBindData(string aSortExp, bool aIsCompleteData)
        {
            OracleConnection objCon = null;
            OracleCommand objCmd = null;
            DataSet ds = null;
            OracleDataAdapter objAdp = null;
            try
            {
                //Connect to Database
                objCon = new OracleConnection("Data Source=ABC;
                             User ID=SUPERVISOR;Password=XYZ");
                objCon.Open();

                //Command Object
                objCmd = new OracleCommand("PKG_GetArea.GetArea",
                                                         objCon);

                //Stored Procedure
                objCmd.CommandType = CommandType.StoredProcedure;

                //Create Parameter Object
                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;

                ///Instantiate Dataset
                ds = new DataSet();

                ///Instantiate Data Adopter
                objAdp = new OracleDataAdapter(objCmd);

                ///Fill Data Set
                objAdp.Fill(ds);

                ///Bind Data to Grids
                grdView.DataSource = ds.Tables["Table"];
                grdView.DataBind();

                ViewState["ReportTime"] = DateTime.Now;

                ///get the total rows
                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;
            }
        }

自定义排序

自定义排序与传统排序不同之处在于,它不仅对网格数据进行排序,还会对所有数据进行排序,然后将分页后的数据显示在网格中。为了完成此任务,排序表达式会传递到 Oracle 包中,然后使用 decode 方法根据排序表达式对数据进行排序。我知道对每列使用 decode 方法不是一个好方法,但我没有其他办法来完成这项任务。

 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);
  } 

自定义导出

默认情况下,会导出 Gridview 的当前页面,但此处会将所有数据导出到 Excel。为了完成此任务,我们只需从数据库检索所有数据,并在不将数据渲染到 gridview 的情况下导出它。对于 **默认分页**,只会直接导出带分页的当前页面数据。为了解决这个问题,我们首先将 gridview Allowpaging 属性设置为 false ,然后导出数据。

protected void lnkbtnExport_Click(object sender, EventArgs e)
{
      if (grdView.Rows.Count > 0)
      {
           grdView.AllowPaging = false;
           pBindData(null);
           ///export to excel
           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();
        }   

冻结标题行 Gridview

使用 CSS 实现冻结标题行 Gridview

.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;
} 

动态列宽调整

使用 JS 实现动态列宽调整。我从 Matt Berseth 的文章 中找到了一个不错的 JS。

//  true when a header is currently being resized
var _isResizing;
//  a reference to the header column that is being resized
var _element;
//  an array of all of the tables header cells
var _ths;

function pageLoad(args){
    //  get all of the th elements from the gridview
    _ths = $get('grdCustom').getElementsByTagName('TH');

    //  if the grid has at least one th element
    if(_ths.length > 1){

        for(i = 0; i < _ths.length; i++){
            //  determine the widths
            _ths[i].style.width = Sys.UI.DomElement
.getBounds(_ths[i]).width + 'px';

            //  attach the mousemove and mousedown events
            if(i < _ths.length - 1){
                $addHandler(_ths[i], 'mousemove', _onMouseMove);
                $addHandler(_ths[i], 'mousedown', _onMouseDown);
            }
        }

        //  add a global mouseup handler
        $addHandler(document, 'mouseup', _onMouseUp);
        //  add a global selectstart handler
        $addHandler(document, 'selectstart', _onSelectStart);
    }
}

function _onMouseMove(args){
    if(_isResizing){

        //  determine the new width of the header
        var bounds = Sys.UI.DomElement.getBounds(_element);
        var width = args.clientX - bounds.x;

        //  we set the minimum width to 1 px, so make
        //  sure it is at least this before bothering to
        //  calculate the new width
        if(width > 1){

            //  get the next th element so we can adjust
            //its size as well
            var nextColumn = _element.nextSibling;
            var nextColumnWidth;
            if(width < _toNumber(_element.style.width)){
                //  make the next column bigger
                nextColumnWidth = _toNumber(nextColumn.style.width) +
		_toNumber(_element.style.width) - width;
            }
            else if(width > _toNumber(_element.style.width)){
                //  make the next column smaller
                nextColumnWidth = _toNumber(nextColumn.style.width) -
		(width - _toNumber(_element.style.width));
            }

            //  we also don't want to shrink this width to
            // less than one pixel,
            //  so make sure of this before resizing ...
            if(nextColumnWidth > 1){
                _element.style.width = width + 'px';
                nextColumn.style.width = nextColumnWidth + 'px';
            }
        }
    }
    else{
        //  get the bounds of the element.  If the mouse cursor
        //is within 2px of the border, display the e-cursor
        //-> cursor:e-resize
        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 the user clicks the mouse button while
    //  the cursor is in the resize position, it means
    //  they want to start resizing.  Set _isResizing to true
    //  and grab the th element that is being resized
    if(args.target.style.cursor == 'e-resize') {
        _isResizing = true;
        _element = args.target;
    }
}

function _onMouseUp(args){
    //  the user let go of the mouse - so
    //  they are done resizing the header.  Reset
    //  everything back
    if(_isResizing){

        //  set back to default values
        _isResizing = false;
        _element = null;

        //  make sure the cursor is set back to default
        for(i = 0; i < _ths.length; i++){
            _ths[i].style.cursor = '';
        }
    }
}

function _onSelectStart(args){
    // Don't allow selection during drag
    if(_isResizing){
        args.preventDefault();
        return false;
    }
}

function _toNumber(m) {
    //  helper function to peel the px off of the widths
    return new Number(m.replace('px', ''));
}   

行悬停、选择和工具提示

使用 CSS 实现行悬停。

.GridRowHover
{
	background-color: #FFFFE1;
	cursor: pointer;
} 

使用 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 的 "title" 属性实现 Grid 单元格工具提示。

上述 JS、CSS 和 Title 属性在 Gridview RowDataBound 事件中被调用。

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() != "&nbsp;")
             {
                 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;";
     }
 }

限制

  1. 对 ASC 和 DESC 条件下的每一列都使用了 Decode 方法来实施自定义排序。
  2. 冻结标题行 Gridview 仅在 Internet Explorer 中有效。

参考文献

历史

  • 1.0.0.0 版本是初始版本,其中包括 ASP.NET Gridview ,具备默认和自定义分页、排序、导出数据、冻结标题行、列宽调整、行悬停、行选择、单元格工具提示等功能,以提高性能、功能性和 GUI 效果。
© . All rights reserved.