平时使用GridView的时候会有固定表头、指定行或指定列的需求,就像Excel冻结行、列那样。其实我们可以用CSS来搞定。扩展一下GridView,通过设置几个属性来达到这样的功能。

控件开发
1、新建一个继承自GridView的类,另外为了保持滚动条状态,还要继承IPostBackDataHandler接口


    /**//// <summary>

    /// 继承自GridView

    /// </summary>

    [ToolboxData(@"<{0}:SmartGridView runat='server'></{0}:SmartGridView>")]

    public class SmartGridView : GridView, IPostBackDataHandler

    {



    }





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


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


        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以设置每个单元格的样式,从而实现固定表头、指定行或指定列的功能。


        /**//// <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、增加两个私有变量


        /**//// <summary>

        /// 如果固定行、列的话 滚动条的x位置

        /// </summary>

        private int _yy_SmartGridView_x;

        /**//// <summary>

        /// 如果固定行、列的话 滚动条的y位置

        /// </summary>

        private int _yy_SmartGridView_y;





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


        /**//// <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包裹起来。


        /**//// <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("onscroll=\"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的值


        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


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


<%@ 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>