|
Apologies for digging up an old article, but I've only just recently found this, and I have one (hopefully) simple question:
Can I make the InsertableGridView perform an Insert from another button on my form? In other words, is there similar functionality to the "UpdateRow" method?
For background, I'm trying to make the GridView react correctly when the user presses ENTER. I'd like it to trigger the "Update" or "Insert" events, depending on the GridView's mode. I've created a Button near the GridView, and wrapped both controls in a Panel. Finally, I've set the "DefaultButton" property of the Panel to the ID of the Button. So far, I can make it Update, but not Insert.
The HTML is:
<asp:Panel runat="server" id="pnl" DefaultButton="btn">
<asp:Button runat="server" id="btn" />
<my:InsertableGridView runat="server" id="gv" ShowFooter="true" AllowInsert="true">
</my:InsertableGridView>
</asp:Panel>
...and the VB is:
Protected Sub btn_Click(sender as Object, e as EventArgs) Handles btn.Click
With gv
If .EditIndex = -1 Then
Else
.UpdateRow(.EditIndex, True)
End If
End With
End Sub
Notes:
(1) I use a Panel's "DefaultButton" (and not the Form's "DefaultButton") because there may be multiple GridViews on the form. I'd like it to work on the Panel that has the focus.
(2) I use ".UpdateRow" so the GridView's validation gets triggered. I'd like the Insert command to do the same.
Alternatively, is there an easier way to get the GridView to react to the ENTER key?
Aside from that, great article - thanks!
|
|
|
|
|
I kind of moved on too and published another article jQuery Based Ajax.Net library[^] Over there I switch from one row to another based on where user clicks (using jQuery). Please download that file and see jquery.tabs.js file $.fn.Grid plugin around line 105 - it might give you some idea.
|
|
|
|
|
Thanks for your response. I'll check it out.
Looks like I can make use of some of your other controls, too!
|
|
|
|
|
|
I did some updates and published this control along with other useful staff at jQuery Based Ajax.Net library[^] If you are planning to use it give this article a try.
|
|
|
|
|
I used this great control for years, but now with framework 4 i have some problems with insert event. The specific error is that after insert a record in gridview is launched event of container form. Tis behavior is only in framework 4
Any Ideas?
Thanks
|
|
|
|
|
Hi, just found this and it seems like a neat solution. I wanted to make the grid read-only by default and have an "add new" button that would perform a postback and set AllowInsert=True. I can't get it to work though. If I set AllowInsert=True at design-time it works as expected. If I set it in an event handler though (e.g. in grid_Init), the insertable row isn't rendered. I'm not very familiar with ASP so perhaps my understaanding of page lifecycles etc. is at the root of my problem. Is there a particular event handler that I should target?
|
|
|
|
|
I strongly encourage you to use later version of the library described in jQuery Based Ajax.Net library[^]. In general however all the controls are created in the CreateControls and doing it in event handler is too late. The easiest thing is to set grid.Footer.Visible = false.
|
|
|
|
|
Thanks, I'll take a look at the version you mention.
doing it in event handler is too late
If in Page_Load I just do:
<br />
If IsPostback Then<br />
grid.AllowInsert = True<br />
End If<br />
... and I provoke a Postback by clicking a Sort header, it works. It doesn't work if the Postback is caused by clicking a Button control though. I'm not yet sure why this is.
|
|
|
|
|
Hi all,
My code:
<edititemtemplate>
<asp:textbox id="DateTextBox" width="70px" runat="server" text="<%# Bind(" date")="" %>"="" onload="DateTextBox_Load" xmlns:asp="#unknown">
<asp:image id="Image1" runat="server" imageurl="Images/Calendar_scheduleHS.png" xmlns:asp="#unknown">
<cc1:calendarextender id="CalendarExtender1" format="dd.MM.yyyy" runat="server" targetcontrolid="DateTextBox" popupbuttonid="Image1" xmlns:cc1="#unknown">
<asp:requiredfieldvalidator id="DateRequiredFieldValidator" runat="server" controltovalidate="DateTextBox" errormessage="Date is required!" xmlns:asp="#unknown">
couse System.InvalidOperationException "Extender controls may not be registered before PreRender."
StackTrace:
at System.Web.UI.ScriptControlManager.RegisterExtenderControl[TExtenderControl](TExtenderControl extenderControl, Control targetControl)
at System.Web.UI.ScriptManager.RegisterExtenderControl[TExtenderControl](TExtenderControl extenderControl, Control targetControl)
at System.Web.UI.ExtenderControl.RegisterWithScriptManager()
at System.Web.UI.ExtenderControl.OnPreRender(EventArgs e)
at AjaxControlToolkit.ExtenderControlBase.OnPreRender(EventArgs e) in D:\tools\ASP_Ajax\AjaxControlToolkit\ExtenderBase\ExtenderControlBase.cs:line 367
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
Can someone help me?
|
|
|
|
|
I have converted the C# to VB for anyone interested... Enjoy.
Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Text
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Drawing.Design
Imports System.Collections.Specialized
Imports System.Data
Namespace InsGrid
<DefaultEvent("SelectedIndexChanged")> _
<SupportsEventValidation()> _
<Designer("System.Web.UI.Design.WebControls.GridViewDesigner, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")> _
<ControlValueProperty("SelectedValue")> _
<ToolboxData("<{0}:InsertableGrid runat=server></{0}:InsertableGrid>")> _
Public Class InsertableGrid
Inherits GridView
<Category("Action")> _
Public Event RowInserted(ByVal sender As Object, ByVal e As GridViewUpdatedEventArgs)
<Category("Action")> _
Public Event RowInserting(ByVal sender As Object, ByVal e As GridViewUpdateEventArgs)
<Browsable(True), DefaultValue("true"), Category("Behavior")> _
Public Property AllowInsert() As Boolean
Get
Return CBool(IIf(ViewState("_InsOk") Is Nothing, True, CBool(ViewState("_InsOk"))))
End Get
Set(ByVal Value As Boolean)
ViewState("_InsOk") = Value
End Set
End Property
<Browsable(True), DefaultValue("false"), Category("Behavior")> _
Public Property SuppressReadOnlyColumns() As Boolean
Get
Return CBool(IIf(ViewState("_SupRO") Is Nothing, False, CBool(ViewState("_SupRO"))))
End Get
Set(ByVal Value As Boolean)
ViewState("_SupRO") = Value
End Set
End Property
Private _InsertValues As IOrderedDictionary = Nothing
Protected Property InsertValues() As IOrderedDictionary
Get
Return _InsertValues
End Get
Set(ByVal Value As IOrderedDictionary)
_InsertValues = Value
End Set
End Property
Protected gvFooterRow As GridViewRow = Nothing
Public Overrides ReadOnly Property FooterRow() As GridViewRow
Get
Return CType(IIf(gvFooterRow Is Nothing, MyBase.FooterRow, gvFooterRow), GridViewRow)
End Get
End Property
<Browsable(True)> _
Public Overrides ReadOnly Property Columns() As DataControlFieldCollection
Get
Return MyBase.Columns
End Get
End Property
Protected Overrides Function CreateChildControls(ByVal dataSource As System.Collections.IEnumerable, ByVal dataBinding As Boolean) As Integer
Dim ret As Integer = MyBase.CreateChildControls(dataSource, dataBinding)
If Columns.Count = 0 Or DesignMode Or Not AllowInsert Then
Return ret
End If
ShowFooter = True
Dim flds() As DataControlField = New DataControlField(Columns.Count - 1) {}
Columns.CopyTo(flds, 0)
If ret = 0 Then
Controls.Clear()
Dim t As Table = New Table()
Controls.Add(t)
Dim r As GridViewRow = CreateRow(-1, -1, DataControlRowType.Header, DataControlRowState.Normal)
Me.InitializeRow(r, flds)
t.Rows.Add(r)
gvFooterRow = CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Insert)
Me.InitializeRow(gvFooterRow, flds)
t.Rows.Add(gvFooterRow)
Else
gvFooterRow = MyBase.FooterRow
End If
FooterRow.RowState = DataControlRowState.Insert
FooterRow.Visible = (MyBase.EditIndex < 0)
For i As Integer = 0 To Columns.Count - 1
Dim cell As DataControlFieldCell = CType(FooterRow.Cells(i), DataControlFieldCell)
Dim fld As DataControlField = Columns(i)
Dim NotReadOnly As Boolean = True
If (Me.SuppressReadOnlyColumns) Then
If (Columns(i).GetType().Name.EndsWith("BoundField")) Then
NotReadOnly = Not (CType(Columns(i), BoundField)).ReadOnly
ElseIf (Columns(i).GetType().Name.EndsWith("CheckBoxField")) Then
NotReadOnly = Not (CType(Columns(i), CheckBoxField)).ReadOnly
ElseIf (Columns(i).GetType().Name.EndsWith("ImageField")) Then
NotReadOnly = Not (CType(Columns(i), ImageField)).ReadOnly
End If
End If
fld.InsertVisible = (fld.Visible And NotReadOnly)
If TypeOf fld Is CommandField Then
Dim cf As CommandField = CType(fld, CommandField)
Dim ins As CommandField = New CommandField()
ins.ButtonType = cf.ButtonType
ins.InsertImageUrl = cf.InsertImageUrl
ins.InsertText = cf.InsertText
ins.CancelImageUrl = cf.CancelImageUrl
ins.CancelText = cf.CancelText
ins.ValidationGroup = cf.ValidationGroup
ins.CausesValidation = cf.CausesValidation
ins.InsertVisible = True
ins.ShowInsertButton = True
ins.Initialize(False, Me)
ins.InitializeCell(cell, DataControlCellType.DataCell, DataControlRowState.Insert, -1)
ElseIf cell.Controls.Count = 0 Then
fld.Initialize(MyBase.AllowSorting, Me)
If NotReadOnly Then
fld.InitializeCell( _
cell, _
DataControlCellType.DataCell, _
CType(DataControlRowState.Edit + DataControlRowState.Insert, DataControlRowState), _
-1)
End If
End If
Next
Dim dict As OrderedDictionary = New OrderedDictionary()
MyBase.ExtractRowValues(dict, FooterRow, True, True)
Dim tbl As DataTable = New DataTable()
Dim row As DataRow = tbl.Rows.Add()
For Each k As String In dict.Keys
tbl.Columns.Add(New DataColumn(k))
row(k) = dict(k)
Next
FooterRow.DataItem = New DataView(tbl).Item(0)
Dim args1 As GridViewRowEventArgs = New GridViewRowEventArgs(FooterRow)
Me.OnRowCreated(args1)
FooterRow.DataBind()
Me.OnRowDataBound(args1)
FooterRow.DataItem = Nothing
For Each f As DataControlField In Columns
f.Initialize(Me.AllowSorting, Me)
Next
Return ret
End Function
Protected Overridable Sub OnRowInserting(ByVal e As GridViewUpdateEventArgs)
RaiseEvent RowInserting(Me, e)
End Sub
Protected Overridable Sub OnRowInserted(ByVal e As GridViewUpdatedEventArgs)
RaiseEvent RowInserted(Me, e)
End Sub
Protected Overrides Sub OnRowCommand(ByVal e As GridViewCommandEventArgs)
If "-1".Equals(e.CommandArgument) And "Cancel".CompareTo(e.CommandName) = 0 Then
Page.Trace.Warn("Canceling insert...")
Return
End If
If "-1".Equals(e.CommandArgument) And "Insert".Equals(e.CommandName) Then
Dim args As GridViewUpdateEventArgs = New GridViewUpdateEventArgs(-1)
MyBase.ExtractRowValues(args.NewValues, FooterRow, True, True)
InsertValues = args.NewValues
Dim allEmpty As Boolean = True
For Each v As Object In InsertValues.Values
If v IsNot Nothing AndAlso System.Convert.ToString(v).Trim().Length > 0 Then
allEmpty = False
Exit For
End If
Next
If allEmpty Then
Return
End If
Me.OnRowInserting(args)
If args.Cancel Then
Return
End If
Dim view As DataSourceView = Me.GetData()
view.Insert(args.NewValues, New System.Web.UI.DataSourceViewOperationCallback(AddressOf CallBack))
Return
End If
MyBase.OnRowCommand(e)
End Sub
Private Function CallBack(ByVal affectedRows As Integer, ByVal ex As Exception) As Boolean
Dim evt As GridViewUpdatedEventArgs = New GridViewUpdatedEventArgs(affectedRows, ex)
evt.NewValues.Clear()
For Each v As String In InsertValues.Keys
evt.NewValues.Add(v, InsertValues(v))
Next
Me.OnRowInserted(evt)
If ex Is Nothing And Not evt.ExceptionHandled Then
Return False
End If
MyBase.RequiresDataBinding = True
Return True
End Function
End Class
End Namespace
modified on Tuesday, February 3, 2009 9:50 AM
|
|
|
|
|
Man, awesome job!
I found just one problem: the code does not take AutoGenerated buttons under consideration - they cause off-by-one error and the grid does not render the way it should. Here is my solution (tested). Modify InsertableGrid.cs file as follows:
lines 60-62
was:
DataControlField[] flds = new DataControlField[Columns.Count];
Columns.CopyTo(flds, 0);
if (ret == 0) {
correction:
if (ret == 0) {
bool hasAutoCommandField = base.AutoGenerateDeleteButton || base.AutoGenerateEditButton || base.AutoGenerateSelectButton;
int autoCommandFieldOffset = hasAutoCommandField ? 1 : 0;
DataControlField[] flds = new DataControlField[Columns.Count + autoCommandFieldOffset];
if (hasAutoCommandField) flds[0] = new CommandField();
Columns.CopyTo(flds, autoCommandFieldOffset);
lines 79-81:
was:
for (int i = 0; i < Columns.Count; i++) {
DataControlFieldCell cell = (DataControlFieldCell)FooterRow.Cells[i];
DataControlField fld = Columns[i];
correction:
for (int i = 0; i < FooterRow.Cells.Count; i++) {
DataControlFieldCell cell = (DataControlFieldCell)FooterRow.Cells[i];
DataControlField fld = cell.ContainingField;
Cheers,
Thomas Jastrzebski
|
|
|
|
|
Very nice solution!
Question:
If inserting to DB caused an exception, it can be handled in InsertableGrid1_RowInserted.
But even if (GridViewUpdatedEventArgs)eventArgs.KeepInEditMode is set to true, the old values that caused the error are cleared.
This is annoying if a lot of data has been entered before Insert and the action fails on only one of them.
Is it possible to have similar behavior as setting (GridViewUpdatedEventArgs)eventArgs.KeepInEditMode in normal edit mode?
Dani
|
|
|
|
|
|
Hi, thanks again for a great gridview! If anyone else is getting this error (using a databound DropDownList in your EditItemTemplate), try:
Add this to the InsertableGrid code:
private void PrepareDropDowns(bool addBlank)<br />
{<br />
foreach (TableCell t in FooterRow.Cells)<br />
{<br />
foreach (Control c in t.Controls)<br />
{<br />
DropDownList ddl = c as DropDownList;<br />
<br />
if (ddl != null && !ddl.DataSourceID.Equals(""))<br />
{<br />
if (addBlank)<br />
{<br />
ddl.AppendDataBoundItems = true;<br />
ddl.Items.Insert(0, "");<br />
}<br />
else<br />
{<br />
ddl.SelectedIndex = 1;<br />
ddl.Items.RemoveAt(0);<br />
}<br />
}<br />
}<br />
}<br />
}
Swap your CreateChildControls routine to this:
protected override int CreateChildControls(System.Collections.IEnumerable dataSource, bool dataBinding)<br />
{<br />
int ret = base.CreateChildControls(dataSource, dataBinding);<br />
if (Columns.Count == 0 || DesignMode || !AllowInsert)<br />
return ret;<br />
ShowFooter = true;<br />
DataControlField[] flds = new DataControlField[Columns.Count];<br />
Columns.CopyTo(flds, 0);<br />
if (ret == 0)<br />
{<br />
Controls.Clear();<br />
Table t = new Table();<br />
Controls.Add(t);<br />
<br />
GridViewRow r = CreateRow(-1, -1, DataControlRowType.Header, DataControlRowState.Normal);<br />
this.InitializeRow(r, flds);<br />
t.Rows.Add(r);<br />
<br />
gvFooterRow = CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Insert);<br />
this.InitializeRow(gvFooterRow, flds);<br />
t.Rows.Add(gvFooterRow);<br />
}<br />
else<br />
gvFooterRow = base.FooterRow;<br />
FooterRow.RowState = DataControlRowState.Insert;<br />
FooterRow.Visible = (base.EditIndex < 0);<br />
for (int i = 0; i < Columns.Count; i++)<br />
{<br />
DataControlFieldCell cell = (DataControlFieldCell)FooterRow.Cells[i];<br />
DataControlField fld = Columns[i];<br />
fld.InsertVisible = fld.Visible;<br />
if (fld is CommandField)<br />
{<br />
CommandField cf = (CommandField)fld;<br />
CommandField ins = new CommandField();<br />
ins.ButtonType = cf.ButtonType;<br />
ins.InsertImageUrl = cf.InsertImageUrl;<br />
ins.InsertText = cf.InsertText;<br />
ins.CancelImageUrl = cf.CancelImageUrl;<br />
ins.CancelText = cf.CancelText;<br />
ins.InsertVisible = true;<br />
ins.ShowInsertButton = true;<br />
ins.Initialize(false, this);<br />
ins.InitializeCell(cell, DataControlCellType.DataCell, DataControlRowState.Insert, -1);<br />
}<br />
else if (cell.Controls.Count == 0)<br />
{
fld.Initialize(base.AllowSorting, this);<br />
fld.InitializeCell(cell, DataControlCellType.DataCell, DataControlRowState.Edit | DataControlRowState.Insert, -1);<br />
PrepareDropDowns(true);<br />
}<br />
}<br />
OrderedDictionary dict = new OrderedDictionary();<br />
base.ExtractRowValues(dict, FooterRow, true, true);<br />
DataTable tbl = new DataTable();<br />
DataRow row = tbl.Rows.Add();<br />
foreach (string k in dict.Keys)<br />
{<br />
tbl.Columns.Add(new DataColumn(k));<br />
row[k] = dict[k];<br />
}<br />
FooterRow.DataItem = new DataView(tbl)[0];<br />
GridViewRowEventArgs args1 = new GridViewRowEventArgs(FooterRow);<br />
this.OnRowCreated(args1);<br />
FooterRow.DataBind();<br />
this.OnRowDataBound(args1);<br />
FooterRow.DataItem = null;<br />
foreach (DataControlField f in Columns)<br />
f.Initialize(this.AllowSorting, this);<br />
PrepareDropDowns(false);<br />
return ret;<br />
}
Cheers
|
|
|
|
|
Hello there!
For some reason this is not working for me.. any ideas? I've just copy/paste your code with no success...
Best Regards
|
|
|
|
|
|
I think the problem here is when I'm trying to create an insert line, I'm trying to bind it to the data. The reason it was done is so I can fill up the insert line with default values. During the binding, list control tries to select empty data in the drop down list box. There are couple of ways to solve it:
- Create OnRowCreated event handler and initialize insert data in there (see article for details on how to do it).
- Remove portion that tries to bind footer row to the data in the CreateChildControls
|
|
|
|
|
hi every one
we can a lot of way for showing data in gridview.
1) the first one is using datasource whitout writing any code.
2) another one is using dataset,datareader,... and writing code.
I whant to khow that :
what is different between using wizard and writing code ?
when we can use the first one ?
thanks alot
|
|
|
|
|
I tried using this as a drop-in replacement for asp:GridView -- just compile, add assembly, add assembly reference, and change tag type -- and it fails.
The CreateChildControls override fails to handle asp:HyperLink inside of ItemTemplate inside of asp:TemplateField.
I'm not sure whether this code breaks a naming container, or if its casts to DataControlField don't handle asp:HyperLinks, or what -- but, anyway, it appears that it is not ready to replace asp:GridView.
|
|
|
|
|
This is almost exactly what I need... except that when I tried it out it ignores the SkinID attribute. Did I do something wrong?
|
|
|
|
|
Hi,
cool control. I have it up and on my page but when I try to add to my DataSet's dataTable, it won't insert correctly. I'm using an ObjectDataSource to bind them and using the wizard to configure the SELECT and INSERT, but its still not working correctly, i just get an empty table back. When i check the RowInserting event, the data is there to send but nothign is happing. any help?
thanks!
|
|
|
|
|
So after much pulling out of hair and gnashing of teeth, I think I found a way to use the InsertVisible property to JUST control whether or not the field is displayed in insert mode. Well, except for in a TemplateField.
As a reference, italic means existing code, bold means new code, and strikethrough means that it was replaced by the bold code immediately proceeding it.
I forgot something important
Comment out this line:
fld.InsertVisible = fld.Visible;
First, make these changes:
else if (cell.Controls.Count == 0)
{
fld.Initialize(base.AllowSorting, this);
fld.InitializeCell(cell, DataControlCellType.DataCell, DataControlRowState.Edit | DataControlRowState.Insert, -1);
fld.InitializeCell(cell, DataControlCellType.DataCell, fld.InsertVisible ? DataControlRowState.Edit | DataControlRowState.Insert : DataControlRowState.Normal, -1);
}
What this does is initialize a cell to Insert mode if InsertVisible is true, or to Normal mode if it's false.
The next change to make:
base.ExtractRowValues(dict, FooterRow, true, true);
foreach (DataControlField f in Columns)
{
BoundField b = f as BoundField;
if (b != null && !dict.Contains(b.DataField))
dict.Insert(0, b.DataField, null);
}
Because of how ExtractRowValues works, if a field is marked InsertVisible="False", the field doesn't make it into that dictionary. However, the dictionary needs to contain the field so the row can databind. Important note: I only handled for BoundFields here, you'll need to do the same thing for any other type of fields you're using. TemplateFields don't have a DataField property, obviously, so I'm not sure how you'd handle those.
The last change you'll need to make:
base.ExtractRowValues(args.NewValues, FooterRow, true, true);
InsertValues = args.NewValues;
foreach (DataControlField f in Columns)
{
BoundField b = f as BoundField;
if (b != null && !b.InsertVisible && InsertValues.Contains((string)b.DataField))
InsertValues.Remove((string)b.DataField);
}
This is the opposite of the last problem. I'm having a brain fart at the moment so I can't remember why it happens, but when inserting, you don't want parameters created for fields marked InsertVisible="False". This takes them out.
Now, this whole thing could be done better if the ExtractRowValues (actually, the part that contains the code that breaks when InsertVisible is set is in the individual field type classes in a function called ExtractValuesFromCell I believe), but I didn't feel like doing that, and this was pretty easy.
Hope this helps.
Chris
|
|
|
|
|
This code is absolutely amazing! It saved me a lot of work, thanks for the posting.
I like the fact that the footer row gets created automatically by looking at the edit row. However, I need to be able to have different fields in the footer row with extra buttons. I tried creating the FooterTemplate and it renders correctly. When I click on the insert link however, it does not insert the row and it does not fire the RowInserting event.
Thanks
|
|
|
|
|
Hello
I have tried in vain to add your insertion footer code to my subclassed gridview. There are multiple errors eg. the footer is not show, columns go missing and cannot be databound...
In trying to find the issue I see that you have exactly 0 comments in InsertableGrid.cs.
Admittedly, c# is easy to understand but usually you add comments to give other programmers a hint on what you are trying to do -- the big picture.
Normally I evaluate code quality by the (precense) and quality of the comments, among other things. While you have no comments at all the code seems pretty well worked. However it still won't play nicely with my existing app...
Could you please add comments in the future?
Best regards
Andreas Warberg
|
|
|
|
|