We have all experienced the need to show an “insert row” in Microsoft’s GridView and the only way to do that “out of the box”is to use the footer template in each column. The biggest drawback with that approach is the footer row will only be displayed if there is data bound to the grid which in turn prevents the case where you want to show an insert row even when the grid is empty.
Fear not for I have found a solution! Actually I found it somewhere else on the web (sorry I don’t remember where) but I have “tweaked” it to meet most of my insert row needs.
Essentially the solution involves extending Microsoft’s GridView control with code that creates an insert row at the bottom of the GridView even if there is no data bound to the grid. The following is a copy of the source code.
public class GridView : System.Web.UI.WebControls.GridView
{
// Fields
private IOrderedDictionary _InsertValues = null;
protected static readonly object EventItemInserted = new object();
protected static readonly object EventItemInserting = new object();
protected GridViewRow gvFooterRow = null;
// Events
[Category("Action")]
public event GridViewUpdatedEventHandler RowInserted
{
add
{
base.Events.AddHandler(EventItemInserted, value);
}
remove
{
base.Events.RemoveHandler(EventItemInserted, value);
}
}
[Category("Action")]
public event GridViewUpdateEventHandler RowInserting
{
add
{
base.Events.AddHandler(EventItemInserting, value);
}
remove
{
base.Events.RemoveHandler(EventItemInserting, value);
}
}
// Methods
protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
int ret = base.CreateChildControls(dataSource, dataBinding);
if (((this.Columns.Count != 0) && !base.DesignMode) && this.AllowInsert)
{
this.ShowFooter = true;
DataControlField[] flds = new DataControlField[this.Columns.Count];
this.Columns.CopyTo(flds, 0);
if (ret == 0)
{
this.Controls.Clear();
Table t = new Table();
this.Controls.Add(t);
if (this.ShowHeader)
{
GridViewRow r = this.CreateRow(-1, -1, DataControlRowType.Header, DataControlRowState.Normal);
this.InitializeRow(r, flds);
t.Rows.Add(r);
}
this.gvFooterRow = this.CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Insert);
this.InitializeRow(this.gvFooterRow, flds);
t.Rows.Add(this.gvFooterRow);
}
else
{
this.gvFooterRow = base.FooterRow;
}
this.FooterRow.RowState = DataControlRowState.Insert;
this.FooterRow.Visible = base.EditIndex < 0;
for (int i = 0; i < this.Columns.Count; i++)
{
DataControlFieldCell cell = (DataControlFieldCell)this.FooterRow.Cells[i];
DataControlField fld = this.Columns[i];
fld.InsertVisible = fld.Visible;
if (fld is CommandField)
{
CommandField cf = (CommandField)fld;
CommandField ins = new CommandField();
ins.ControlStyle.CopyFrom(cf.ControlStyle);
ins.ShowCancelButton = cf.ShowCancelButton;
ins.ButtonType = cf.ButtonType;
ins.InsertImageUrl = cf.InsertImageUrl;
ins.InsertText = cf.InsertText;
ins.CancelImageUrl = cf.CancelImageUrl;
ins.CancelText = cf.CancelText;
ins.InsertVisible = true;
ins.ShowInsertButton = true;
ins.Initialize(false, this);
ins.InitializeCell(cell, DataControlCellType.DataCell, DataControlRowState.Insert, -1);
}
else if (cell.Controls.Count == 0)
{
fld.Initialize(base.AllowSorting, this);
fld.InitializeCell(cell, DataControlCellType.DataCell, DataControlRowState.Insert | DataControlRowState.Edit, -1);
}
}
OrderedDictionary dict = new OrderedDictionary();
base.ExtractRowValues(dict, this.FooterRow, true, true);
DataTable tbl = new DataTable();
DataRow row = tbl.Rows.Add(new object[0]);
foreach (string k in dict.Keys)
{
tbl.Columns.Add(new DataColumn(k));
row[k] = dict[k];
}
this.FooterRow.DataItem = new DataView(tbl)[0];
GridViewRowEventArgs args1 = new GridViewRowEventArgs(this.FooterRow);
this.OnRowCreated(args1);
this.FooterRow.DataBind();
this.OnRowDataBound(args1);
this.FooterRow.DataItem = null;
foreach (DataControlField f in this.Columns)
{
f.Initialize(this.AllowSorting, this);
}
}
return ret;
}
protected override void OnRowCommand(GridViewCommandEventArgs e)
{
if ("-1".Equals(e.CommandArgument) && ("Cancel".CompareTo(e.CommandName) == 0))
{
this.Page.Trace.Warn("Canceling insert...");
}
else if ((!"-1".Equals(e.CommandArgument) || !"Insert".Equals(e.CommandName)) || (this.Page == null))
{
base.OnRowCommand(e);
}
else
{
GridViewUpdateEventArgs args = new GridViewUpdateEventArgs(-1);
base.ExtractRowValues(args.NewValues, this.FooterRow, true, true);
this.InsertValues = args.NewValues;
bool allEmpty = true;
foreach (string v in this.InsertValues.Values)
{
if (!(allEmpty = (v == null) || (v.Trim().Length == 0)))
{
break;
}
}
if (!allEmpty)
{
this.OnRowInserting(args);
if (!string.IsNullOrEmpty(this.DataSourceID) && !args.Cancel)
{
this.GetData().Insert(args.NewValues, delegate(int affectedRows, Exception ex)
{
GridViewUpdatedEventArgs evt = new GridViewUpdatedEventArgs(affectedRows, ex);
evt.NewValues.Clear();
foreach (string v in this.InsertValues.Keys)
{
evt.NewValues.Add(v, this.InsertValues[v]);
}
this.OnRowInserted(evt);
if (!((ex == null) || evt.ExceptionHandled))
{
return false;
}
base.RequiresDataBinding = true;
return true;
});
}
}
}
}
protected virtual void OnRowInserted(GridViewUpdatedEventArgs e)
{
GridViewUpdatedEventHandler h = (GridViewUpdatedEventHandler)base.Events[EventItemInserted];
if (h != null)
{
h(this, e);
}
}
protected virtual void OnRowInserting(GridViewUpdateEventArgs e)
{
GridViewUpdateEventHandler h = (GridViewUpdateEventHandler)base.Events[EventItemInserting];
if (h != null)
{
h(this, e);
}
}
// Properties
[DefaultValue("false"), Browsable(true), Category("Behavior")]
public bool AllowInsert
{
get
{
object o = this.ViewState["_InsOk"];
return ((o != null) && ((bool)o));
}
set
{
this.ViewState["_InsOk"] = value;
}
}
public override GridViewRow FooterRow
{
get
{
return ((this.gvFooterRow == null) ? base.FooterRow : this.gvFooterRow);
}
}
protected IOrderedDictionary InsertValues
{
get
{
return this._InsertValues;
}
set
{
this._InsertValues = value;
}
}
}
ooterRow = null;
// Events
[Category("Action")]
public event GridViewUpdatedEventHandler RowInserted
{
add
{
base.Events.AddHandler(EventItemInserted, value);
}
remove
{
base.Events.RemoveHandler(EventItemInserted, value);
}
}
[Category("Action")]
public event GridViewUpdateEventHandler RowInserting
{
add
{
base.Events.AddHandler(EventItemInserting, value);
}
remove
{
base.Events.RemoveHandler(EventItemInserting, value);
}
}
// Methods
protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
int ret = base.CreateChildControls(dataSource, dataBinding);
if (((this.Columns.Count != 0) && !base.DesignMode) && this.AllowInsert)
{
this.ShowFooter = true;
DataControlField[] flds = new DataControlField[this.Columns.Count];
this.Columns.CopyTo(flds, 0);
if (ret == 0)
{
this.Controls.Clear();
Table t = new Table();
this.Controls.Add(t);
if (this.ShowHeader)
{
GridViewRow r = this.CreateRow(-1, -1, DataControlRowType.Header, DataControlRowState.Normal);
this.InitializeRow(r, flds);
t.Rows.Add(r);
}
this.gvFooterRow = this.CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Insert);
this.InitializeRow(this.gvFooterRow, flds);
t.Rows.Add(this.gvFooterRow);
}
else
{
this.gvFooterRow = base.FooterRow;
}
this.FooterRow.RowState = DataControlRowState.Insert;
this.FooterRow.Visible = base.EditIndex < 0;
for (int i = 0; i < this.Columns.Count; i++)
{
DataControlFieldCell cell = (DataControlFieldCell)this.FooterRow.Cells[i];
DataControlField fld = this.Columns[i];
fld.InsertVisible = fld.Visible;
if (fld is CommandField)
{
CommandField cf = (CommandField)fld;
CommandField ins = new CommandField();
ins.ControlStyle.CopyFrom(cf.ControlStyle);
ins.ShowCancelButton = cf.ShowCancelButton;
ins.ButtonType = cf.ButtonType;
ins.InsertImageUrl = cf.InsertImageUrl;
ins.InsertText = cf.InsertText;
ins.CancelImageUrl = cf.CancelImageUrl;
ins.CancelText = cf.CancelText;
ins.InsertVisible = true;
ins.ShowInsertButton = true;
ins.Initialize(false, this);
ins.InitializeCell(cell, DataControlCellType.DataCell, DataControlRowState.Insert, -1);
}
else if (cell.Controls.Count == 0)
{
fld.Initialize(base.AllowSorting, this);
fld.InitializeCell(cell, DataControlCellType.DataCell, DataControlRowState.Insert | DataControlRowState.Edit, -1);
}
}
OrderedDictionary dict = new OrderedDictionary();
base.ExtractRowValues(dict, this.FooterRow, true, true);
DataTable tbl = new DataTable();
DataRow row = tbl.Rows.Add(new object[0]);
foreach (string k in dict.Keys)
{
tbl.Columns.Add(new DataColumn(k));
row[k] = dict[k];
}
this.FooterRow.DataItem = new DataView(tbl)[0];
GridViewRowEventArgs args1 = new GridViewRowEventArgs(this.FooterRow);
this.OnRowCreated(args1);
this.FooterRow.DataBind();
this.OnRowDataBound(args1);
this.FooterRow.DataItem = null;
foreach (DataControlField f in this.Columns)
{
f.Initialize(this.AllowSorting, this);
}
}
return ret;
}
protected override void OnRowCommand(GridViewCommandEventArgs e)
{
if ("-1".Equals(e.CommandArgument) && ("Cancel".CompareTo(e.CommandName) == 0))
{
this.Page.Trace.Warn("Canceling insert...");
}
else if ((!"-1".Equals(e.CommandArgument) || !"Insert".Equals(e.CommandName)) || (this.Page == null))
{
base.OnRowCommand(e);
}
else
{
GridViewUpdateEventArgs args = new GridViewUpdateEventArgs(-1);
base.ExtractRowValues(args.NewValues, this.FooterRow, true, true);
this.InsertValues = args.NewValues;
bool allEmpty = true;
foreach (string v in this.InsertValues.Values)
{
if (!(allEmpty = (v == null) || (v.Trim().Length == 0)))
{
break;
}
}
if (!allEmpty)
{
this.OnRowInserting(args);
if (!string.IsNullOrEmpty(this.DataSourceID) && !args.Cancel)
{
this.GetData().Insert(args.NewValues, delegate(int affectedRows, Exception ex)
{
GridViewUpdatedEventArgs evt = new GridViewUpdatedEventArgs(affectedRows, ex);
evt.NewValues.Clear();
foreach (string v in this.InsertValues.Keys)
{
evt.NewValues.Add(v, this.InsertValues[v]);
}
this.OnRowInserted(evt);
if (!((ex == null) || evt.ExceptionHandled))
{
return false;
}
base.RequiresDataBinding = true;
return true;
});
}
}
}
}
protected virtual void OnRowInserted(GridViewUpdatedEventArgs e)
{
GridViewUpdatedEventHandler h = (GridViewUpdatedEventHandler)base.Events[EventItemInserted];
if (h != null)
{
h(this, e);
}
}
protected virtual void OnRowInserting(GridViewUpdateEventArgs e)
{
GridViewUpdateEventHandler h = (GridViewUpdateEventHandler)base.Events[EventItemInserting];
if (h != null)
{
h(this, e);
}
}
// Properties
[DefaultValue("false"), Browsable(true), Category("Behavior")]
public bool AllowInsert
{
get
{
object o = this.ViewState["_InsOk"];
return ((o != null) && ((bool)o));
}
set
{
this.ViewState["_InsOk"] = value;
}
}
public override GridViewRow FooterRow
{
get
{
return ((this.gvFooterRow == null) ? base.FooterRow : this.gvFooterRow);
}
}
protected IOrderedDictionary InsertValues
{
get
{
return this._InsertValues;
}
set
{
this._InsertValues = value;
}
}
}
AllowInsert Property: this is a boolean value property that determines if the grid should show an insert row at the bottom of the grid. True will show an insert row and false will not. Bear in mind you do not have to bind the grid in order for it to show this row.
RowInserting Event: this is the event handler that will be called when the insert command is invoked. It works just like all of the other row commands in that this event will be called before any datasource controls perform their insert command. I modified this event to pass the insert values extracted from each of the columns to the event handler by way of the GridViewUpdateEventArgs.NewValues. This is helpful when you are not using a datasource control but rather performing your own insert logic. (of course assuming you are using two-way databinding).
RowInserted Event: this is the counterpart to the RowInserting event in that it is called after the insert command is performed on any datasource controls. Like all the other post row events, this is only called if the grid is using a datasource control.
How To Use: Using the source code above or the file I have attached below, extend Microsoft’s GridView with this functionality yourself. Once you have done that, to use it simply drop your new grid on the page or usercontrol where you wish to use it, set AllowInsert property on your grid to true, and then (if you are not using some datasource control that has insert logic associated with it) handle the RowInserting event with your own custom insert logic. Every time the grid rebinds, the insert values in the insert row are cleared and ready for another insert.
I hope everyone finds this code useful, I know I have. If you have any questions or problems with the code, please post a comment to this blog and I will address your problems or questions as soon as I can.