In this Grid is used to stable in a Editable mode at page load. See the below Example.

First We Copy and Paste a BulkEditGridView.cs in App_Code Folder.

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Web;

namespace RealWorld.Grids
{
    /// <summary>
    /// BulkEditGridView allows users to edit multiple rows of a gridview at once, and have them
    /// all saved.
    /// </summary>
    [
    DefaultEvent("SelectedIndexChanged"),
    Designer("System.Web.UI.Design.WebControls.GridViewDesigner, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"),
    ControlValueProperty("SelectedValue"),
    ]
    public class BulkEditGridView : System.Web.UI.WebControls.GridView
    {
        //key for the RowInserting event handler list
        public static readonly object RowInsertingEvent = new object();

        private List<int> dirtyRows = new List<int>();
        private List<int> newRows = new List<int>();

        /// <summary>
        /// Default Constructor
        /// </summary>
        public BulkEditGridView()
        {
        }

        /// <summary>
        /// Modifies the creation of the row to set all rows as editable.
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="dataSourceIndex"></param>
        /// <param name="rowType"></param>
        /// <param name="rowState"></param>
        /// <returns></returns>
        protected override GridViewRow CreateRow(int rowIndex, int dataSourceIndex, DataControlRowType rowType, DataControlRowState rowState)
        {
            return base.CreateRow(rowIndex, dataSourceIndex, rowType, rowState | DataControlRowState.Edit);
        }

        public List<GridViewRow> DirtyRows
        {
            get
            {
                List<GridViewRow> drs = new List<GridViewRow>();
                foreach (int rowIndex in dirtyRows)
                {
                    drs.Add(this.Rows[rowIndex]);
                }

                return drs;
            }
        }

        /// <summary>
        /// Adds event handlers to controls in all the editable cells.
        /// </summary>
        /// <param name="row"></param>
        /// <param name="fields"></param>
        protected override void InitializeRow(GridViewRow row, DataControlField[] fields)
        {
            base.InitializeRow(row, fields);
            foreach (TableCell cell in row.Cells)
            {
                if (cell.Controls.Count > 0)
                {
                    AddChangedHandlers(cell.Controls);
                }
            }
        }

        /// <summary>
        /// Adds an event handler to editable controls.
        /// </summary>
        /// <param name="controls"></param>
        private void AddChangedHandlers(ControlCollection controls)
        {
            foreach (Control ctrl in controls)
            {
                if (ctrl is TextBox)
                {
                    ((TextBox)ctrl).TextChanged += new EventHandler(this.HandleRowChanged);
                }
                else if (ctrl is CheckBox)
                {
                    ((CheckBox)ctrl).CheckedChanged += new EventHandler(this.HandleRowChanged);
                }
                else if (ctrl is DropDownList)
                {
                    ((DropDownList)ctrl).SelectedIndexChanged += new EventHandler(this.HandleRowChanged);
                }
                ////could add recursion if we are missing some controls.
                //else if (ctrl.Controls.Count > 0 && !(ctrl is INamingContainer) )
                //{
                //    AddChangedHandlers(ctrl.Controls);
                //}
            }
        }


        /// <summary>
        /// This gets called when a row is changed.  Store the id of the row and wait to update
        /// until save is called.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void HandleRowChanged(object sender, EventArgs args)
        {
            GridViewRow row = ((Control)sender).NamingContainer as GridViewRow;
            if (null != row)
            {
                if (0 != (row.RowState & DataControlRowState.Insert))
                {
                    int altRowIndex = this.InnerTable.Rows.GetRowIndex(row);
                    if (false == newRows.Contains(altRowIndex))
                        newRows.Add(altRowIndex);
                }
                else
                {
                    if (false == dirtyRows.Contains(row.RowIndex))
                        dirtyRows.Add(row.RowIndex);
                }
            }

        }

        /// <summary>
        /// Setting this property will cause the grid to update all modified records when
        /// this button is clicked.  It currently supports Button, ImageButton, and LinkButton.
        /// If you set this property, you do not need to call save programatically.
        /// </summary>
        [IDReferenceProperty(typeof(Control))]
        public string SaveButtonID
        {
            get
            {
                return (string)(this.ViewState["SaveButtonID"] ?? String.Empty);
            }
            set
            {
                this.ViewState["SaveButtonID"] = value;
            }
        }

        /// <summary>
        /// Attaches an eventhandler to the onclick method of the save button.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            //Attach an event handler to the save button.
            if (false == string.IsNullOrEmpty(this.SaveButtonID))
            {
                Control btn = RecursiveFindControl(this.NamingContainer, this.SaveButtonID);
                if (null != btn)
                {
                    if (btn is Button)
                    {
                        ((Button)btn).Click += new EventHandler(SaveClicked);
                    }
                    else if (btn is LinkButton)
                    {
                        ((LinkButton)btn).Click += new EventHandler(SaveClicked);
                    }
                    else if (btn is ImageButton)
                    {
                        ((ImageButton)btn).Click += new ImageClickEventHandler(SaveClicked);
                    }
                }
            }
        }

        /// <summary>
        /// Looks for a control recursively up the control tree.  We need this because Page.FindControl
        /// does not find the control if we are inside a masterpage content section.
        /// </summary>
        /// <param name="namingcontainer"></param>
        /// <param name="controlName"></param>
        /// <returns></returns>
        private Control RecursiveFindControl(Control namingcontainer, string controlName)
        {
            Control c = namingcontainer.FindControl(controlName);

            if (c != null)
                return c;

            if (namingcontainer.NamingContainer != null)
                return RecursiveFindControl(namingcontainer.NamingContainer, controlName);

            return null;
        }

        /// <summary>
        /// Handles the save event, and calls the save method.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SaveClicked(object sender, EventArgs e)
        {
            this.Save();
            this.DataBind();
        }

        /// <summary>
        /// Saves any modified rows.  This is called automatically if the SaveButtonId is set.
        /// </summary>
        public void Save()
        {
            try
            {
                foreach (int row in dirtyRows)
                {
                    //TODO: need to check if we really want false here.  Probably want to pull this
                    //fron the save button.
                    this.UpdateRow(row, false);
                }

                foreach (int row in newRows)
                {
                    //Make the datasource save a new row.
                    this.InsertRow(row, false);
                }
            }
            finally
            {
                dirtyRows.Clear();
                newRows.Clear();
            }
        }

        /// <summary>
        /// Prepares the <see cref="RowInserting"/> event and calls insert on the DataSource.
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="causesValidation"></param>
        private void InsertRow(int rowIndex, bool causesValidation)
        {
            GridViewRow row = null;

            if ((!causesValidation || (this.Page == null)) || this.Page.IsValid)
            {
                DataSourceView dsv = null;
                bool useDataSource = base.IsBoundUsingDataSourceID;
                if (useDataSource)
                {
                    dsv = this.GetData();
                    if (dsv == null)
                    {
                        throw new HttpException("DataSource Returned Null View");
                    }
                }
                GridViewInsertEventArgs args = new GridViewInsertEventArgs(rowIndex);
                if (useDataSource)
                {
                    if ((row == null) && (this.InnerTable.Rows.Count > rowIndex))
                    {
                        row = this.InnerTable.Rows[rowIndex] as GridViewRow;
                    }
                    if (row != null)
                    {
                        this.ExtractRowValues(args.NewValues, row, true, false);
                    }
                }

                this.OnRowInserting(args);

                if (!args.Cancel && useDataSource)
                {
                    dsv.Insert(args.NewValues, new DataSourceViewOperationCallback(DataSourceViewInsertCallback));
                }
            }
        }

        /// <summary>
        /// Callback for the datasource's insert command.
        /// </summary>
        /// <param name="i"></param>
        /// <param name="ex"></param>
        /// <returns></returns>
        private bool DataSourceViewInsertCallback(int i, Exception ex)
        {
            if (null != ex)
            {
                throw ex;
            }

            return true;
        }


        /// <summary>
        /// Fires the <see cref="RowInserting"/> event.
        /// </summary>
        /// <param name="args"></param>
        protected virtual void OnRowInserting(GridViewInsertEventArgs args)
        {
            Delegate handler = this.Events[RowInsertingEvent];
            if (null != handler)
                handler.DynamicInvoke(this, args);
        }

        /// <summary>
        /// Event fires when new row has been edited, and save is clicked.
        /// </summary>
        public event GridViewInsertEventHandler RowInserting
        {
            add
            {
                this.Events.AddHandler(RowInsertingEvent, value);
            }
            remove
            {
                this.Events.RemoveHandler(RowInsertingEvent, value);
            }
        }

        /// <summary>
        /// Access to the GridView's inner table.
        /// </summary>
        protected Table InnerTable
        {
            get
            {
                if (false == this.HasControls())
                    return null;

                return (Table)this.Controls[0];
            }
        }

        /// <summary>
        /// Enables inline inserting.  Off by default.
        /// </summary>
        public bool EnableInsert
        {
            get
            {
                return (bool)(this.ViewState["EnableInsert"] ?? false);
            }
            set
            {
                this.ViewState["EnableInsert"] = value;
            }
        }

        /// <summary>
        /// We have to recreate our insert row so we can load the postback info from it.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected override void OnPagePreLoad(object sender, EventArgs e)
        {
            base.OnPagePreLoad(sender, e);

            if (this.EnableInsert && this.Page.IsPostBack)
            {
                this.CreateInsertRow();
            }
        }

        /// <summary>
        /// After the controls are databound, add a row to the end.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnDataBound(EventArgs e)
        {
            if (this.EnableInsert)
            {
                this.CreateInsertRow();
            }

            base.OnDataBound(e);
        }

        /// <summary>
        /// Creates the insert row and adds it to the inner table.
        /// </summary>
        protected virtual void CreateInsertRow()
        {
            GridViewRow row = this.CreateRow(this.Rows.Count, -1, DataControlRowType.DataRow, DataControlRowState.Insert);

            DataControlField[] fields = new DataControlField[this.Columns.Count];
            this.Columns.CopyTo(fields, 0);

            this.InitializeRow(row, fields);

            int index = this.InnerTable.Rows.Count - (this.ShowFooter ? 1 : 0);
            this.InnerTable.Rows.AddAt(index, row);
        }
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    public delegate void GridViewInsertEventHandler(object sender, GridViewInsertEventArgs args);

    /// <summary>
    ///
    /// </summary>
    public class GridViewInsertEventArgs : CancelEventArgs
    {
        private int _rowIndex;
        private IOrderedDictionary _values;

        public GridViewInsertEventArgs(int rowIndex)
            : base(false)
        {
            this._rowIndex = rowIndex;
        }

        /// <summary>
        /// Gets a dictionary containing the revised values of the non-key field name/value
        /// pairs in the row to update.
        /// </summary>
        public IOrderedDictionary NewValues
        {
            get
            {
                if (this._values == null)
                {
                    this._values = new OrderedDictionary();
                }
                return this._values;
            }
        }

        /// <summary>
        /// Gets the index of the row being updated.
        /// </summary>
        public int RowIndex { get { return this._rowIndex; } }
    }

}


We need to Register this Namespace in our Aspx page

see the Example of EditGrid.aspx Page,

- Css is used for Gridview Fixed columns
- AlignDiv Script is Used for Maintaining the ScrollPosition, it is called in code behind "EditGrid.aspx.cs" file
- Remaining Scripts are used to validate the Text box in a editable Grid.
- In this Editable Grid, We may give fixed columns and bind value. Here, i gave only
for serial no.
 
<%@ Page Language="C#" MasterPageFile="~/PFP.master" AutoEventWireup="true" CodeFile="EditGrid.aspx.cs" Inherits="PayRoll_EditGrid" Title="Untitled Page" %>
<%@ Register Namespace="RealWorld.Grids" TagPrefix="ac" %>
<asp:Content ID="cntEdit" ContentPlaceHolderID="cphPFPMaster" Runat="Server">
<style type="text/css">
       <%--Fixed Columns in Grid-- %>
        .GridViewStyle td.locked
        {
            background-color: #2D96CE;
            font-family: Tahoma;
            font-size: 8pt;
            vertical-align: middle;
            padding-left: 2pt;
            padding-right: 2pt;
            padding-top: 0pt;
            padding-bottom: 0pt;
            text-align: left;
            height: 17px;
            position: relative;
            border-collapse: collapse;
            border-left-color: Silver;
            border-top-style: none;
            border-top-color: Silver;
            cursor: default;
            left: expression(this.offsetParent.scrollLeft-1);
            z-index: 100;
        }
       
        .GridViewStyle th
        {
            font-family: Tahoma;
            font-size: 8pt;
            vertical-align: middle;
            text-align: left;
            padding-left: 2pt;
            padding-right: 2pt;
            padding-top: 0pt;
            padding-bottom: 0pt;
            empty-cells: show;
            position: relative;
            height: 27px;
            border-collapse: collapse;
            border-left-color: Silver;
            border-top-style: none;
            top: expression(this.offsetParent.scrollTop-1);
            z-index: 10;
        }
       
        .GridViewStyle th.locked
        {
            font-family: Tahoma;
            font-size: 8pt;
            vertical-align: middle;
            text-align: left;
            padding-left: 2pt;
            padding-right: 2pt;
            padding-top: 0pt;
            padding-bottom: 0pt;
            empty-cells: show;
            position: relative;
            height: 17px;
            border-collapse: collapse;
            border-top-style: none;
            left: expression(this.offsetParent.scrollLeft-1);
            z-index: 150;
        }
       
       
       
        .GVHeaderStyle
        {
            font-family: Tahoma;
            font-size: 8pt;
            vertical-align: middle;
            background-color: #7193ae;
            color: #000000;
            font-weight: bold;
            padding: 0pt;
            empty-cells: show;
            position: relative;
            border-left-style: none;
            border-collapse: collapse;
            border-left-color: Silver;
            top: expression(this.offsetParent.scrollTop-1);
            left: expression(this.offsetParent.scrollLeft-1);
            height: 13px;
            z-index: 1000;
        }
    </style>
    <script language="javascript" type="text/javascript">
   
        //For Maintaining Intial Scroll Position
        function AlignDiv(obj, objColumn) {

            var objDiv = document.getElementById('<%= divdatagrid.ClientID%>');
            if (objColumn == obj - 1) {
                objDiv.scrollLeft = 0;
            }
        }
       
        //For Numeric Value Validation
        function extractNumber(obj, decimalPlaces, allowNegative) {
            var temp = obj.value;

            // avoid changing things if already formatted correctly
            var reg0Str = '[0-9]*';
            if (decimalPlaces > 0) {
                reg0Str += '\\.?[0-9]{0,' + decimalPlaces + '}';
            } else if (decimalPlaces < 0) {
                reg0Str += '\\.?[0-9]*';
            }
            reg0Str = allowNegative ? '^-?' + reg0Str : '^' + reg0Str;
            reg0Str = reg0Str + '$';
            var reg0 = new RegExp(reg0Str);
            if (reg0.test(temp)) return true;

            // first replace all non numbers
            var reg1Str = '[^0-9' + (decimalPlaces != 0 ? '.' : '') + (allowNegative ? '-' : '') + ']';
            var reg1 = new RegExp(reg1Str, 'g');
            temp = temp.replace(reg1, '');

            if (allowNegative) {
                // replace extra negative
                var hasNegative = temp.length > 0 && temp.charAt(0) == '-';
                var reg2 = /-/g;
                temp = temp.replace(reg2, '');
                if (hasNegative) temp = '-' + temp;
            }

            if (decimalPlaces != 0) {
                var reg3 = /\./g;
                var reg3Array = reg3.exec(temp);
                if (reg3Array != null) {
                    // keep only first occurrence of .
                    //  and the number of places specified by decimalPlaces or the entire string if decimalPlaces < 0
                    var reg3Right = temp.substring(reg3Array.index + reg3Array[0].length);
                    reg3Right = reg3Right.replace(reg3, '');
                    reg3Right = decimalPlaces > 0 ? reg3Right.substring(0, decimalPlaces) : reg3Right;
                    temp = temp.substring(0, reg3Array.index) + '.' + reg3Right;
                }
            }

            obj.value = temp;
        }
        //Allow Only Numbers
        function blockNonNumbers(obj, e, allowDecimal, allowNegative) {
            var key;
            var isCtrl = false;
            var keychar;
            var reg;

            if (window.event) {
                key = e.keyCode;
                isCtrl = window.event.ctrlKey
            } else if (e.which) {
                key = e.which;
                isCtrl = e.ctrlKey;
            }

            if (isNaN(key)) return true;

            keychar = String.fromCharCode(key);

            // check for backspace or delete, or if Ctrl was pressed
            if (key == 8 || isCtrl) {
                return true;
            }

            reg = /\d/;
            var isFirstN = allowNegative ? keychar == '-' && obj.value.indexOf('-') == -1 : false;
            var isFirstD = allowDecimal ? keychar == '.' && obj.value.indexOf('.') == -1 : false;

            return isFirstN || isFirstD || reg.test(keychar);
        }
    </script>
   
    <table id="tblgvWorkType" runat="server" align="center" style="width: 50%; height: 100%;">
            <tr>
                <td align="left">
                    <div id="divdatagrid" class="GridViewStyle" style="overflow: auto; position: relative;
                         clear: none; width: 370px;" runat="server" >
                        <ac:BulkEditGridView ID="gvWorkType" runat="server" CssClass="mGrid" DataKeyNames="Name,Email"
                            OnRowDataBound="gvWorkType_RowDataBound">
                            <HeaderStyle HorizontalAlign="Center" BackColor="#2D96CE" ForeColor="White" />
                            <RowStyle HorizontalAlign="Left" ForeColor="White" />
                            <AlternatingRowStyle HorizontalAlign="Left" ForeColor="White" />
                            <Columns>
                                <asp:TemplateField HeaderText="#" >
                                    <ItemTemplate>
                                        <asp:Label ID="lblgvSeqid" ForeColor="Navy" runat="server" Text='<% # Container.DataItemIndex + 1 %>'></asp:Label>
                                    </ItemTemplate>
                                </asp:TemplateField>
                            </Columns>
                        </ac:BulkEditGridView>
                        <br />
                    <asp:Button ID="btnSave" runat="server" Text="Save" CssClass="ThemeButtons" OnClick="btnSave_Click" />
                    </div>
                </td>
            </tr>
        </table>
</asp:Content>


In the Code Behind, I have created the Dynamic Table and bind into the Grid. See the below "EditGrid.aspx.cs" File,

using System;
using System.Data;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class PayRoll_EditGrid : System.Web.UI.Page
{
    // I have create the Dynamic Table and Bind It
    protected void Page_Load(object sender, EventArgs e)
    {

        DataTable objDt = new DataTable();
        DataColumn dcEdit = null;
        dcEdit = new DataColumn();
        dcEdit.DataType = System.Type.GetType("System.String");
        dcEdit.ColumnName = "Name";
       
        objDt.Columns.Add(dcEdit);

        DataColumn dcEdit2 = null;
        dcEdit2 = new DataColumn();
        dcEdit2.DataType = System.Type.GetType("System.String");
        dcEdit2.ColumnName = "Email";
        objDt.Columns.Add(dcEdit2);

        DataColumn dcEdit3 = null;
        dcEdit3 = new DataColumn();
        dcEdit3.DataType = System.Type.GetType("System.String");
        dcEdit3.ColumnName = "Age";
        objDt.Columns.Add(dcEdit3);

        DataColumn dcEdit4 = null;
        dcEdit4 = new DataColumn();
        dcEdit4.DataType = System.Type.GetType("System.String");
        dcEdit4.ColumnName = "Qualification";
        objDt.Columns.Add(dcEdit4);

        DataRow dr = objDt.NewRow();
       
        dr["Name"] = "Suresh";
        dr["Email"] = "nsuresh@hovservices.in";
        dr["Age"] = "";

        objDt.Rows.Add(dr);

        DataRow dr1 = objDt.NewRow();
        dr1["Name"] = "vijayan";
        dr1["Email"] = "vijayans@hovservices.in";
        dr1["Age"] = "";
        objDt.Rows.Add(dr1);

        gvWorkType.DataSource = objDt;
        gvWorkType.DataBind();
    }


 // At the Time of DB Load Get the Values from GridView


    protected void btnSave_Click(object sender, EventArgs e)
    {
        //Get Values
        foreach (GridViewRow gr in gvWorkType.Rows)
        {
            foreach (Control ctl in gr.Controls)
            {
                if (ctl is TextBox)
                {
                    string str = (ctl as TextBox).Text;
                }
            }
        }
    }

/*

   In RowDataBound, Lock the Columns Using The Css
   Adding the Script event for needed columns and ForeColor here.

*/

    protected void gvWorkType_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        //Make Un scrollable
        for (int ColumLock = 0; ColumLock < 3; ColumLock++)
        {
            e.Row.Cells[ColumLock].CssClass = "locked";
        }

        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            //Set the Cell color...
            e.Row.Cells[1].ForeColor = e.Row.Cells[2].ForeColor = System.Drawing.Color.Navy;

            //Get the each TextBox and add the validations
            for (int column = 3; column < e.Row.Cells.Count; column++)
                foreach (Control ctrl in e.Row.Cells[column].Controls)
                {
                    if (ctrl.GetType() == typeof(TextBox))
                    {
                        TextBox txt = ctrl as TextBox;
                        txt.Width = 55;
                        txt.MaxLength = 5;
                        //http://www.mredkj.com/tutorials/validate2.html  --- For Reference
                        txt.Attributes.Add("onblur", "extractNumber(this,0,false);");
                        txt.Attributes.Add("onkeyup", "extractNumber(this,0,false);");
                        txt.Attributes.Add("onkeypress", "return blockNonNumbers(this, event, false, false);");
                        txt.Attributes.Add("class", "Controls");
                        if (column == e.Row.Cells.Count - 1)
                            txt.Attributes.Add("onblur", "return AlignDiv('" + e.Row.Cells.Count + "','" + column + "')");
                    }
                }
        }
    }
}


Now we run the Code, we will get the Editable grid like below,


This is used only in the situation of bulk entry with dynamic columns

Comments (1)

On 9 May 2013 at 10:10 , Unknown said...

Found yours after hours of failed attempt to implement an editable grid with frozen headers/columns. The freezing was not working especially since the edit was using Jquery .
Got it working with your example.
Thanks !