介绍
平时使用GridView的时候会有固定表头、指定行或指定列的需求,就像Excel冻结行、列那样。其实我们可以用CSS来搞定。扩展一下GridView,通过设置几个属性来达到这样的功能。
控件开发
1、新建一个继承自GridView的类,另外为了保持滚动条状态,还要继承IPostBackDataHandler接口

Code
    /**//// <summary>
/// 继承自GridView
/// </summary>
    [ToolboxData(@"<{0}:SmartGridView runat='server'></{0}:SmartGridView>")]
public class SmartGridView : GridView, IPostBackDataHandler
    {

    }

2、新建一个FixRowCol类,有七个属性

Code
using System;
using System.Collections.Generic;
using System.Text;

using System.ComponentModel;

namespace YYControls.SmartGridView
{
/**//// <summary>
/// 固定表头、指定行或指定列的实体类
/// </summary>
    [TypeConverter(typeof(ExpandableObjectConverter))]
public class FixRowCol
    {
private bool _isFixHeader;
/**//// <summary>
/// 固定表头否?
/// </summary>
        [Description("固定表头否?"), Category("扩展"), DefaultValue(false), NotifyParentProperty(true)]
public virtual bool IsFixHeader
        {
get { return _isFixHeader; }
set { _isFixHeader = value; }
        }

private bool _isFixPager;
/**//// <summary>
/// 固定分页行否?
/// </summary>
        [Description("固定分页行否?"), Category("扩展"), DefaultValue(false), NotifyParentProperty(true)]
public virtual bool IsFixPager
        {
get { return _isFixPager; }
set { _isFixPager = value; }
        }

private string _fixRowIndices;
/**//// <summary>
/// 需要固定的行的索引(用逗号“,”分隔)
/// </summary>
        [Description("需要固定的行的索引(用逗号“,”分隔)"), Category("扩展"), NotifyParentProperty(true)]
public virtual string FixRowIndices
        {
get { return _fixRowIndices; }
set { _fixRowIndices = value; }
        }

private string _fixColumnIndices;
/**//// <summary>
/// 需要固定的列的索引(用逗号“,”分隔)
/// </summary>
        [Description("需要固定的列的索引(用逗号“,”分隔)"), Category("扩展"), NotifyParentProperty(true)]
public virtual string FixColumnIndices
        {
get { return _fixColumnIndices; }
set { _fixColumnIndices = value; }
        }

private System.Web.UI.WebControls.Unit _tableWidth;
/**//// <summary>
/// 表格的宽度
/// </summary>
        [Description("表格的宽度"), Category("扩展"), NotifyParentProperty(true)]
public System.Web.UI.WebControls.Unit TableWidth
        {
get { return _tableWidth; }
set { _tableWidth = value; }
        }

private System.Web.UI.WebControls.Unit _tableHeight;
/**//// <summary>
/// 表格的高度
/// </summary>
        [Description("表格的高度"), Category("扩展"), NotifyParentProperty(true)]
public System.Web.UI.WebControls.Unit TableHeight
        {
get { return _tableHeight; }
set { _tableHeight = value; }
        }

private bool _enableScrollState;
/**//// <summary>
/// 是否保持滚动条的状态
/// </summary>
        [Description("是否保持滚动条的状态"), Category("扩展"), DefaultValue(false), NotifyParentProperty(true)]
public bool EnableScrollState
        {
get { return _enableScrollState; }
set { _enableScrollState = value; }
        }

/**//// <summary>
/// ToString();
/// </summary>
/// <returns></returns>
        public override string ToString()
        {
return "FixRowCol";
        }
    }
}

3、在继承自GridView的类中加一个复杂对象属性,该复杂对象就是第2步创建的那个FixRowCol

Code
  private FixRowCol _fixRowCol;
/**//// <summary>
/// 固定表头、指定行或指定列
/// </summary>
        [
        Description("固定表头、指定行或指定列"),
        Category("扩展"),
        DefaultValue(""),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
public virtual FixRowCol FixRowCol
        {
get
            {
if (_fixRowCol == null)
                {
                    _fixRowCol = new FixRowCol();
                }
return _fixRowCol;
            }
        }

4、重写OnRowDataBound以设置每个单元格的样式,从而实现固定表头、指定行或指定列的功能。

Code
        /**//// <summary>
/// OnRowDataBound
/// </summary>
/// <param name="e"></param>
        protected override void OnRowDataBound(GridViewRowEventArgs e)
        {
if (e.Row.RowType == DataControlRowType.Pager)
            {
if (FixRowCol.IsFixPager)
                {
if (this.PagerSettings.Position == PagerPosition.Top || (this.PagerSettings.Position == PagerPosition.TopAndBottom && _isTopPager))
                    {
// TopPager固定行和列
                        e.Row.Cells[0].Attributes.Add("style", "z-index:999; position: relative; top: expression(this.offsetParent.scrollTop); left: expression(this.offsetParent.scrollLeft);");
// 现在是TopPager,之后就是BottomPager了,所以设置_isTopPager为false
                        _isTopPager = false;
                    }
else if (this.PagerSettings.Position == PagerPosition.TopAndBottom && !_isTopPager)
                    {
// BottomPager只固定列
                        e.Row.Cells[0].Attributes.Add("style", "z-index:999; position: relative; left: expression(this.offsetParent.scrollLeft);");
// 现在是BottomPager,之后就是TopPager了,所以设置_isTopPager为true
                        _isTopPager = true;
                    }
                }
            }

if (e.Row.RowType == DataControlRowType.DataRow || e.Row.RowType == DataControlRowType.Header)
            {
// 给每一个指定固定的列的单元格加上css属性
                if (!String.IsNullOrEmpty(FixRowCol.FixColumnIndices))
                {
// 列索引
                    foreach (string s in FixRowCol.FixColumnIndices.Split(','))
                    {
int i;
if (!Int32.TryParse(s, out i))
throw new ArgumentException("FixColumnIndices", "含有非整形的字符");
if (i > e.Row.Cells.Count)
throw new ArgumentOutOfRangeException("FixColumnIndices", "溢出");

                        e.Row.Cells[i].Attributes.Add("style", "position: relative; left: expression(this.offsetParent.scrollLeft);");
                    }
                }

bool isFixRow = false; // 当前行是否固定
                if (FixRowCol.IsFixHeader && e.Row.RowType == DataControlRowType.Header)
                {
                    isFixRow = true;
                }

if (!String.IsNullOrEmpty(FixRowCol.FixRowIndices) && e.Row.RowType == DataControlRowType.DataRow)
                {
// 行索引
                    foreach (string s in FixRowCol.FixRowIndices.Split(','))
                    {
int i;
if (!Int32.TryParse(s, out i))
throw new ArgumentException("FixRowIndices", "含有非整形的字符");
if (i > e.Row.Cells.Count)
throw new ArgumentOutOfRangeException("FixRowIndices", "溢出");

if (i == e.Row.RowIndex)
                        {
                            isFixRow = true;
break;
                        }
                    }
                }

// 固定该行
                if (isFixRow)
                {
// 该行的每一个单元格
                    for (int j = 0; j < e.Row.Cells.Count; j++)
                    {
// 该单元格不属于固定列
                        if (String.IsNullOrEmpty(e.Row.Cells[j].Attributes["style"]) || e.Row.Cells[j].Attributes["style"].IndexOf("position: relative;") == -1)
                        {
                            e.Row.Cells[j].Attributes.Add("style", " position: relative; top: expression(this.offsetParent.scrollTop);");
                        }
// 该单元格属于固定列
                        else
                        {
                            e.Row.Cells[j].Attributes.Add("style", e.Row.Cells[j].Attributes["style"] + "top: expression(this.offsetParent.scrollTop); z-index: 666;");
                        }
                    }
                }
            }

base.OnRowDataBound(e);
        }

5、增加两个私有变量

 

Code
        /**//// <summary>
/// 如果固定行、列的话 滚动条的x位置
/// </summary>
        private int _yy_SmartGridView_x;
/**//// <summary>
/// 如果固定行、列的话 滚动条的y位置
/// </summary>
        private int _yy_SmartGridView_y;

6、重写GridView的OnPreRender方法,用于注册两个HiddenField,以及注册设置GridView的滚动条的位置的javascript代码

 

Code
       /**//// <summary>
/// OnPreRender
/// </summary>
/// <param name="e"></param>
        protected override void OnPreRender(EventArgs e)
        {
if (FixRowCol.EnableScrollState)
            {
// 滚动条x位置
                Page.ClientScript.RegisterHiddenField("yy_SmartGridView_x", _yy_SmartGridView_x.ToString());
// 滚动条y位置
                Page.ClientScript.RegisterHiddenField("yy_SmartGridView_y", _yy_SmartGridView_y.ToString());

// 设置GridView的滚动条的位置
                Page.ClientScript.RegisterStartupScript(
this.GetType(),
"jsSetScroll", "<script type=\"text/javascript\">document.getElementById('yy_ScrollDiv').scrollLeft=" + _yy_SmartGridView_x + ";document.getElementById('yy_ScrollDiv').scrollTop=" + _yy_SmartGridView_y + ";</script>"
                    );

// 将控件注册为要求在页回发至服务器时进行回发处理的控件
                if (Page != null) Page.RegisterRequiresPostBack(this);
            }

base.OnPreRender(e);
        }

7、重写GridView的Render方法,将GridView用一个div包裹起来

 

Code
    /**//// <summary>
/// Render
/// </summary>
/// <param name="writer"></param>
        protected override void Render(HtmlTextWriter writer)
        {
// 给GridView一个容器 <div>
            if (!FixRowCol.TableWidth.IsEmpty || !FixRowCol.TableHeight.IsEmpty)
            {
if (FixRowCol.TableWidth.IsEmpty) FixRowCol.TableWidth = new Unit(100, UnitType.Percentage);
if (FixRowCol.TableHeight.IsEmpty) FixRowCol.TableHeight = new Unit(100, UnitType.Percentage);

                writer.Write("<div id='yy_ScrollDiv' style=\"overflow: auto; width: "
                    + FixRowCol.TableWidth.ToString() + "; height: "
+ FixRowCol.TableHeight.ToString() + "; position: relative;\" ");

// 如果保持滚动条的状态的话,用隐藏字段记录滚动条的位置
                if (FixRowCol.EnableScrollState)
                {
                    writer.Write("notallow=\"document.getElementById('yy_SmartGridView_x').value = this.scrollLeft; document.getElementById('yy_SmartGridView_y').value = this.scrollTop;\">");
                }
else
                {
                    writer.Write(">");
                }
            }

base.Render(writer);

// </div> 结束
            if (!FixRowCol.TableWidth.IsEmpty || !FixRowCol.TableHeight.IsEmpty)
            {
                writer.Write("</div>");
            }
        }

8、获取存储了滚条位置信息的HiddenField的值

 

Code
        void IPostBackDataHandler.RaisePostDataChangedEvent()
        {

        }

bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection)
        {
// 获取两个保存了 固定行、列后 的GridView滚动条的位置信息
            _yy_SmartGridView_x = String.IsNullOrEmpty(postCollection["yy_SmartGridView_x"]) ? 0 : Convert.ToInt32(postCollection["yy_SmartGridView_x"]);
            _yy_SmartGridView_y = String.IsNullOrEmpty(postCollection["yy_SmartGridView_y"]) ? 0 : Convert.ToInt32(postCollection["yy_SmartGridView_y"]);

return false;
        }

控件使用
添加这个控件到工具箱里,然后拖拽到webform上,设置其FixRowCol下的7个属性即可。IsFixHeader是固定表头否?;IsFixPager是固定分页行否?;FixRowIndices是需要固定的行的索引(用逗号“,”分隔);FixColumnIndices是需要固定的列的索引(用逗号“,”分隔);TableWidth是表格的宽度;TableHeight是表格的高度;EnableScrollState为是否保持滚动条的状态
ObjData.cs

 

Code
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

using System.ComponentModel;

/**//// <summary>
/// OjbData 的摘要说明
/// </summary>
public class OjbData
{
public OjbData()
    {
//
// TODO: 在此处添加构造函数逻辑
//
    }

    [DataObjectMethod(DataObjectMethodType.Select, true)]
public DataTable Select()
    {
        DataTable dt = new DataTable();
        dt.Columns.Add("no", typeof(string));
        dt.Columns.Add("name", typeof(string));

for (int i = 0; i < 30; i++)
        {
            DataRow dr = dt.NewRow();
            dr[0] = "no" + i.ToString().PadLeft(2, '0');
            dr[1] = "name" + i.ToString().PadLeft(2, '0');

            dt.Rows.Add(dr);
        }

return dt;
    }
}

Default.aspx

Code
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!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 id="Head1" runat="server">
<title>SmartGridView测试</title>
</head>
<body>
<form id="form1" runat="server">
<yyc:SmartGridView ID="SmartGridView1" runat="server" AutoGenerateColumns="False"
            DataSourceID="ObjectDataSource1" Width="1000px">
<Columns>
<asp:BoundField DataField="no" HeaderText="序号" SortExpression="no" />
<asp:BoundField DataField="name" HeaderText="名称" SortExpression="name" />
<asp:BoundField DataField="no" HeaderText="序号" SortExpression="no" />
<asp:BoundField DataField="name" HeaderText="名称" SortExpression="name" />
<asp:BoundField DataField="no" HeaderText="序号" SortExpression="no" />
<asp:BoundField DataField="name" HeaderText="名称" SortExpression="name" />
<asp:BoundField DataField="no" HeaderText="序号" SortExpression="no" />
<asp:BoundField DataField="name" HeaderText="名称" SortExpression="name" />
<asp:BoundField DataField="no" HeaderText="序号" SortExpression="no" />
<asp:BoundField DataField="name" HeaderText="名称" SortExpression="name" />
</Columns>
<FixRowCol FixColumnIndices="0,1" FixRowIndices="0" IsFixHeader="True" TableHeight="300px"
                TableWidth="300px" EnableScrollState="true" />
</yyc:SmartGridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="Select"
            TypeName="OjbData"></asp:ObjectDataSource>
</form>
</body>
</html>