Welcome to MSDN Blogs Sign in | Join | Help

Matt Dotson's .NET Tips & Tricks

The posts on this weblog are provided "AS IS" with no warranties, and confer no rights. The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
Real World GridView: Bulk Editing

GridView is a tremendously useful control in ASP.NET 2.0.  Despite being much improved from the DataGrid of the ASP.NET 1.x era, GridView still lacks some features which always seem to be requested.  This is a multipart post on how to customize GridView to add some of this missing functionality.  I'll post the full source code at the end of the series.  Hopefully this will be useful to everyone.

One of the most needed improvements is the ability to edit multiple rows at once.  By default, GridView allows users to modify only one row at a time, and requires the user to postback many times to switch between editable rows.  Here is a quick look at the "Out of the Box" GridView.

This is not the greatest user experience, so we'll start by allowing the user to modify all of the rows before saving.  To begin, we will create a custom control which inherits from GridView.  This will allow us to override and change some of the behavior we don't like.  One of the first things we want to change is how the rows get created.

   public class BulkEditGridView : System.Web.UI.WebControls.GridView
   {
      protected override GridViewRow CreateRow(int rowIndex, int dataSourceIndex, DataControlRowType rowType, DataControlRowState rowState)
      {
         return base.CreateRow(rowIndex, dataSourceIndex, rowType, rowState | DataControlRowState.Edit);
      }
   }

What we have done here is make sure that every row which gets created has a rowstate of Edit, meaning it's editable.  Since the row is now editable, the columns that are NOT read-only will show up as textboxes (for boundfields) or what ever their EditItemTemplate is.  Here is a look at our grid.

Wow! Looks great ... are we done yet ... um ... not quite.  That would be great if that was all we had to do, but unfortunately there is a bit more.  Notice that there is no way to save our changes, which might be a <sarcasm>minor</sarcasm> problem for our user.  In ASP.NET 1.X, we might have just added a Save() function and made developers add code to the code-behind file to call our function.  Well, this is now a 2.0 world, and we are going to create a great design time experience.  We'll allow developers to set the SaveButtonID on our grid, and we'll take care of setting up all the event handlers.

   [IDReferenceProperty(typeof(Control))]
   public string SaveButtonID
   {
      get
      {
         string val = (string)this.ViewState["SaveButtonID"];
         if (val == null)
         {
            return string.Empty;
         }
          return val;
      }
      set
      {
         this.ViewState["SaveButtonID"] = value;
      }
   }

The IDReferenceProperty tells Visual Studio that this property contains the ID of another property on the page.  This way you get a nice dropdown of control ids in the Properties Window.  It's just nice "fit and finish" that makes our control that much more developer friendly.

   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);
            }

            //add more button types here.
         }
      }
   }

   private void SaveClicked(object sender, EventArgs e)
   {
      this.Save();
      this.DataBind();
   }

The developer doesn't have to write any C# to get the save button to save data!!  Thats a great developer experience, and the kind of standard we should hold ourselves to.  Of course, we want to be flexible and we will expose the Save() method publically so developers CAN access this programatically if they want.  We have added support for Buttons, LinkButtons, and ImageButtons, but there could be more controls you want to include.  The click event of all these controls is routed to the same SaveClicked handler which calls Save().  Pretty simple. 

On a Tangent: You might wonder what the RecursiveFindControl function is.  Why can't we just use FindControl?  Well, FindControl() works great when we are not using MasterPages, but if our grid is in a different ContentSection than the Button, FindControl() won't find the control we are looking for.  FindControl() is scoped to the NamingContainer that our control exists in.  To work around this, we recusively climb the tree of NamingContainers to find our button.

And now on to actually saving.  One thing we want to do, is make sure that we don't save rows that have not changed.  Well, how do we know if a row changes?  We have to watch for it to change.

   protected override void InitializeRow(GridViewRow row, DataControlField[] fields)
   {
      base.InitializeRow(row, fields);
      foreach (DataControlFieldCell cell in row.Cells)
      {
         if (cell.Controls.Count > 0)
         {
            AddChangedHandlers(cell.Controls);
         }
      }
   }

   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);
         }
      }
   }

   void HandleRowChanged(object sender, EventArgs args)
   {
      GridViewRow row = ((Control) sender).NamingContainer as GridViewRow;
      if (null != row && !dirtyRows.Contains(row.RowIndex))
      {
         dirtyRows.Add(row.RowIndex);
      }
   }

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

Adding XxxxxChanged handlers to the controls allows us to know when a control in a row changed values.  If any value in the row changes, we mark the row as "dirty".  Like with the buttons, I have added some of the most common input controls.  You may wish to add additional ones.  Additionally, TemplateFields might have nested controls, so you might want to recursively look for controls. 

I'm sure that you have already figured out what our save method will look like.  Just loop through the dirty rows and save them.

   public void Save()
   {
      foreach (int row in dirtyRows)
      {
         this.UpdateRow(row, false);
      }

      dirtyRows.Clear();
   }

Using the GridView's UpdateRow() function allows our save to behave just like the "Out of the Box" save.  An associated DataSourceControl will be used to update the data, the RowUpdated event will fire, and everything else the basic save does.  Finally, here is the ASPX.

   <asp:Button runat="server" ID="SaveButton" Text="Save Data" />
   <blog:BulkEditGridView runat="server" id="EditableGrid" DataSourceID="AdventureWorks" AutoGenerateColumns="False" DataKeyNames="LocationID" SaveButtonID="SaveButton" >
      <Columns>
         <asp:BoundField DataField="LocationID" HeaderText="LocationID" InsertVisible="False" ReadOnly="True" SortExpression="LocationID" />
         <asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name" />
         <asp:BoundField DataField="Availability" HeaderText="Availability" SortExpression="Availability" />
         <asp:BoundField DataField="CostRate" HeaderText="CostRate" SortExpression="CostRate" />
      </Columns>
   </blog:BulkEditGridView>

I wrote this to give you a basic understanding of how to create a bulk edit GridView.  Of course there are many enhancements you could add to this.  Perhaps you only want some of the rows to be editable, not all of them. Or, you want the grid to not be editable until the user clicks an "edit everything" button.  Maybe you would like all of the changes to complete in a transaction.  And probably you want this to do something I haven't even thought of (I would like to know if you come up with something though).  I've added some of these enhancements for customers on projects, and most of them are not that difficult to implement.

 

NOTE: These comments are the authors own thoughts, and do not represent an official Microsoft recommendation.  This post is for educational purposes only.
   

UPDATE:  Thanks everyone for the feedback!!  Based on several of your comments, I have incorporated an inline insert into the grid (still very beta).  Also I have attached the source code as a zip for those people who can't see the Got Dot Net workspace.  I can't promise I'll keep the attachment up to date, but the workspace will always have the latest.

UPDATE (JAN 2007): Updated source code is now available on CodePlex, and it should be easier to download than it was on GotDotNet.
http://www.codeplex.com/ASPNetRealWorldContr

Posted: Wednesday, November 09, 2005 8:54 AM by mattdotson

Attachment(s): RealWorldGrids.zip

Comments

Sunish Abraham said:

Hi Matt,

I am new to ASP.NET 2.0 GridView and the topic of this blog entry is something that I wanted to implement. Can you provide an example project/solution (here is my email address sunisha[at]comcast.net)? Thank you so much!

Sunish
# November 10, 2005 10:44 AM

SBrickey said:

I'm curious how much effort it'd be to extend the gridview in such a way that multiple header/footers could be manipulated.

I've got a gridview where the header is used for column names, and footer for column totals... similar to the bulk edit, I want to enable editing in each row, but just as importantly i'd like to dedicate a row for inserting (otherwise how many to a page? somewhat of an arbitrary number it seems).

Right now my solution is to add a dummy record in the OnDataBind event... then in the OnDataBound, i handle index 0 as an exception.

in terms of display, index 0 is also marked as the editable field, and in the column templates I've added the controls I need.

This just seems like such a hack for my needs... and i've not even really pushed it (what if i wanted subtotal fields or something?)

Thanks,
-SBrickey
me[at]sbrickey[dot]net
# November 21, 2005 2:44 PM

mattdotson said:

I'm going to cover this in a future post, but I've found one of the easiest/cleanest ways to do that is to modify the Table directly. GridView should have a single child control ... the table, if you want to add one or more rows to that table, do it after databinding. I also use this approach for spanning cells.
# November 22, 2005 4:43 AM

Trav said:

Any chance of getting a vb version for lowly vb.net programmers?
# November 30, 2005 7:44 PM

Khuzema said:

any chance to get a source project. regards
# December 14, 2005 10:51 AM

Michael said:

Any easy way you've found/implemented to insert new rows inline on the GridView? I'd like to have a button next to "Edit" called "Insert" that would create a new row in the GridView, but wouldn't actually create the row in the DB until they put in values and selected Create/Update. Thanks!
# December 16, 2005 5:03 PM

Harry Verstandig said:

Matt, great blog - and I really need this capability for my first 2.0 web app. But, I'm having trouble implementing it and wonder if I can get some help. First of all, I'm not sure where to put the various code pieces you identified. What goes into the custom server control class and what in the bulk gridview code behind? Also, where does the Viewstate for the SaveButtonID get created initially? I get error messages on that, as well as on the "public string SaveButtonID" method. Any help sure would be wonderful. Thanks.
# February 26, 2006 1:11 PM

Matt's .NET Tips & Tricks said:

Let's take a look at some real world uses for GridView.
# March 1, 2006 10:38 PM

Matt's .NET Tips & Tricks said:

GridView is a great control, and out of the box it does a lot of wonderful stuff, but I haven't worked on a project yet where it did everything I needed.  &quot;Real World GridView&quot; is an attempt to share some of the common customizations I end up
# March 1, 2006 11:11 PM

mattdotson said:

I posted the source code for this article @ http://www.gotdotnet.com/Workspaces/Workspace.aspx?id=1ecb6d2e-a104-48f7-a8fb-1622eba20852. Sorry for the delay & thanks for being patient.  Also, there is a part 2 of this article now.  I am working on part 3 which will include frozen headers.  Should be posted "soon" :)  Hopefully soon will be sooner than 3 months this time.
# March 2, 2006 3:45 AM

mark r. reynolds said:

Thanks for sharing your effort!
I have been working with the BulkEditGridView today and have observed an issue regarding utlizing an ObjectDataSource associated with the BulkEditGridView control. The anomoly takes place when setting the BulkEditGridView ConflictDetection property to "CompareAllValues" instead of the default of "Overwritechanges". Upon executing an update the code dumps within the BulkEditGridView Save method during the first iteration of the updaterow loop with the following comment:

"You have specified that your update method compares all values on ObjectDataSource 'ObjectDataSource1', but the dictionary passed in for oldValues is empty. Pass in a valid dictionary for update or change your mode to OverwriteChanges."

The code works fine if you change the ConflictDetection property back. Additionally, I have only observed this anomoly in the case of ObjectDataSource association with the the BulkEditGridView. Have verified the anamoly by testing the code against a standard GridView. I will be happy to provide you with a project if you are interested in pursuing this issue.

Again, thanks for your work. I'm sure the Real World GridView will be quite useful in the .Net community.

mark r. reynolds...TBS  

# March 2, 2006 10:37 PM

P said:

Hi matt,

I am finding difficult to locate "GridView" code in this location  http://www.gotdotnet.com/Workspaces/Workspace.aspx?id=1ecb6d2e-a104-48f7-a8fb-1622eba20852

can you help me on this...

Regards,
P
# March 3, 2006 5:20 AM

mattdotson said:

P, it's under the source control section of the workspace. $/RealWorld.Grids/BulkEditGridView.cs
# March 3, 2006 2:06 PM

mattdotson said:

Thanks Mark.  I think I understand your issue, and will investigate it.  If I find a solution, I will post it here.
# March 3, 2006 2:13 PM

mattdotson said:

By the way, Part 3 is done.  Check it out here: http://blogs.msdn.com/mattdotson/archive/2006/03/02/542613.aspx
# March 3, 2006 2:28 PM

James said:

This looks like what I need to use. I found the BulkEditGridView.cs and added it to my app_code folder. I am having troubles referencing this from the aspx file. I see the example above, but being new to the .net world I'm still a bit confused.
I dropped a regular gridview on the page and connected to the datasource and that worked fine. Then I changed the <asp:GridView tag to <asp:BulkEditGridView
but this gives an error [not a known element].
Any help getting this working would be greatly appreciated.
Thanks!
# March 12, 2006 11:33 AM

James said:

I guess I see that the tag prefix is the problem. Instead of using <asp:
I need to use something else. Above example shows <blog:
but in another example I see <rwg:
This is the part I need to know how to tie together.
Is there something else that needs to be installed or can this be used by just including the BulkEditGridView.cs file in the the App_Code folder?

Thanks.
# March 12, 2006 4:28 PM

sirisha said:

Hi Matt,

  For the bulk edit data grid view, is there a way that the user selectes the rows that he wants to edit and when he clicks edit then all the common columns will become editable. Then when the user enters value in one column all the other columns will be updated with this value.

 ThankYou
 
# March 21, 2006 11:31 AM

cc said:

It seems that the FrozenGridView produces an error if the query which poplulates the grid returns no results.

Object reference not set to an instance of an object:
foreach (DataControlFieldHeaderCell th in this.HeaderRow.Cells)
# March 21, 2006 1:04 PM

Matt said:

James, I would keep any custom controls you write as a seperate dll.  I've used <blog: and <rwg: in different places, and these are defined at the top of the aspx pages.  

Sirisha, you could definately extend the grid to create the functionality you want.

CC, thanks for the info, I'll include this fix the next time I post some code.

--Matt
# March 26, 2006 10:08 PM

brian said:

Here is VB version I translated from Matt's C#.

mports Microsoft.VisualBasic
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.ComponentModel
Imports System.Security.Permissions

Namespace nwss.controls
   <AspNetHostingPermission(SecurityAction.Demand, Level:=AspNetHostingPermissionLevel.Minimal), _
            AspNetHostingPermission(SecurityAction.InheritanceDemand, level:=AspNetHostingPermissionLevel.Minimal), _
            ToolboxData("<{0}:BulkEditGridview runat=""server""> </{0}:BulkEditGridview>")> _
   Public Class bulk_edit_gv
       Inherits GridView
       Protected Overrides Function CreateRow(ByVal rowIndex As Integer, ByVal dataSourceIndex As Integer, ByVal rowType As System.Web.UI.WebControls.DataControlRowType, ByVal rowState As System.Web.UI.WebControls.DataControlRowState) As System.Web.UI.WebControls.GridViewRow
           Return MyBase.CreateRow(rowIndex, dataSourceIndex, rowType, DataControlRowState.Edit)
       End Function
       <IDReferenceProperty(GetType(Control))> _
       Public Property saveButtonID() As String
           Get
               Dim val As String = Me.ViewState("saveButtonID")
               If val = Nothing Then
                   Return ""
               Else
                   Return val
               End If
           End Get
           Set(ByVal value As String)
               Me.ViewState("saveButtonID") = value
           End Set
       End Property
       Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
           MyBase.OnLoad(e)
           If Me.saveButtonID.ToString > "" Then
               Dim btn As Control = RecursiveFindControl(Me.NamingContainer, Me.saveButtonID)
               If btn IsNot Nothing Then
                   If TypeOf (btn) Is Button Then
                       AddHandler CType(btn, Button).Click, AddressOf saveclicked
                   ElseIf TypeOf (btn) Is LinkButton Then
                       AddHandler CType(btn, LinkButton).Click, AddressOf saveclicked
                   ElseIf TypeOf (btn) Is ImageButton Then
                       'AddHandler CType(btn, ImageButton).Click, AddressOf saveclicked
                   End If
               End If

           End If
       End Sub


       Protected Overrides Sub InitializeRow(ByVal row As System.Web.UI.WebControls.GridViewRow, ByVal fields() As System.Web.UI.WebControls.DataControlField)
           MyBase.InitializeRow(row, fields)
           For Each cell As DataControlFieldCell In row.Cells
               If cell.Controls.Count > 0 Then
                   addChangeHandler(cell.Controls)
               End If
           Next
       End Sub
       Private Sub addChangeHandler(ByVal controls As ControlCollection)
           For Each ctrl As Control In controls
               If TypeOf (ctrl) Is TextBox Then
                   AddHandler CType(ctrl, TextBox).TextChanged, AddressOf handleRowChanged
               ElseIf TypeOf (ctrl) Is DropDownList Then
                   AddHandler CType(ctrl, DropDownList).SelectedIndexChanged, AddressOf handleRowChanged
               ElseIf TypeOf (ctrl) Is CheckBox Then
                   AddHandler CType(ctrl, CheckBox).CheckedChanged, AddressOf handleRowChanged
               End If
           Next
       End Sub
       Sub handleRowChanged(ByVal sender As Object, ByVal args As EventArgs)
           Dim row As GridViewRow = CType(sender, Control).NamingContainer
           If row IsNot Nothing And Not dirtyRows.Contains(row.RowIndex) Then
               dirtyRows.Add(row.RowIndex)
           End If
       End Sub
       Private dirtyRows As New System.Collections.Generic.List(Of Integer)
       Private Function RecursiveFindControl(ByVal root As Control, ByVal id As String) As Control
           If root.ID = id Then
               Return root
           End If
           For Each c As Control In root.Controls
               Dim t As Control = RecursiveFindControl(c, id)
               If t IsNot Nothing Then
                   Return t
               End If
           Next
           Return Nothing
       End Function
       Public Sub save()
           For Each row As Integer In dirtyRows
               Me.UpdateRow(row, False)
           Next
           dirtyRows.Clear()
       End Sub
       Private Sub saveclicked(ByVal sender As Object, ByVal e As EventArgs)
           Me.save()
           Me.DataBind()
       End Sub
   End Class
End Namespace
# March 29, 2006 5:36 PM

Cliff said:

Thanks for the VB version. I am struggling to get this working however. I am a relatively new convert to ASP.Net from Classic ASP and am still having problems with some of the basic concepts. Can someone give me an "idiots guide" to taking this code and using it in a simple aspx page?
Thanks,
Cliff
# March 30, 2006 6:45 AM

Brian said:

Cut and paste it out of here into a new custom
vb file in the appcode folder of your website.

Change the namespace to something meaningful for you.

For more information read the walkthrough "Developing and Using a custom server control" in the on-line help library installed with web dev. If you didn't install it, you should. This will tell you how to create,deploy, and use custom controls.
# March 30, 2006 12:00 PM

Cliff said:

Thanks Brian, that really helped and I have it working now. This is a great custom control.
Your help and rapid response are really appreciated.

Cliff
# March 30, 2006 3:37 PM

Cliff said:

I have another 'real world' variant that looks an ideal candidate for BulkEditGridView and would, I am sure, be useful to many developers. In content management systems I am frequently having to provide administrators with the ability to set the sort order for a group of records (departments, categories, latest products, etc) so they are displayed to customers in a specified order. The BulkEditGridView provides a great basis for this by setting the only editable column to be the sort order. However, I need to be able to validate that the sort order is an integer value between 1 and the total number of rows, with no repeats. I have some classic ASP I use to do this but it would save me hours of work each time if this could be integrated with BulkEditGridView.
So, my question is, how could BulkEditGridView be modified to achieve this validation? Alternatively, would the validation be better performed outside of the control and if so, how?
Many thanks,

Cliff
# March 31, 2006 3:41 AM

Eric D. said:

I have a unrelated question about Server Controls someone might know the answer to. I made a Server Control that has a SQL Connection string as a property. How do I make the property bindable like the SQLDataSource property so a user can just select a Connection String from the web.config? The SQLDataSource ConnectionString property is just a string- how does it tell the designer to bind it as a SQL Connection String?
# April 6, 2006 2:35 PM

Scott C said:

How hard would it be to implement templated Columns so that you could have validators and such?
# April 6, 2006 3:52 PM

John M said:

This would be really cool if someone published a downloadable Server Control out of this (don't think that can be done with my ASP.Net Express version - I may be mistaken).
# April 12, 2006 11:58 PM

Scott said:

I implemented much the same design to solve one of my UIs and it worked out well. I just built out a Custom Web Controls assembly for my custom grid view, registered it on the page and presto, fully editable gridview. The issue I ran into was with my gridview skins...if you're not familiar with them, they are basically css styles for your gridview that you drop in the App_Themes folder of your webapp. For some reason my custom grid view will not respect the skin I assign to it...I'm guessing its a pathing issue perhaps? Have you ever come accross this issue or have an idea of how to fix it?
# April 18, 2006 2:52 PM

Christian Lebon said:

This is very cool.  What would really take this to the next level is if you added the code to insert new rows
# April 24, 2006 1:34 AM

Bill said:

I was looking over this and would like to try and implement it but am having some difficulty.  I am new to .NET and like James question above, I am wondering how to get my program to recognize rwg tags and BulkEditGridView as an object.  I added the following to my web.config file:

<pages>
     <controls>
       <add assembly="RealWorld.Grids" namespace="RealWorld.Grids" tagPrefix="rwg"/>
     </controls>
</pages>  

But it cannot find the assembly RealWorld.Grids, of course because I don't have a file in my website called that.  But the file is not in the source code section of Matts gotdotnet blog, and I don't know how to create that file, if I have to use some of the other files given to crete a dll or what.  If someone wonders through here that could shed some light onthis for me I would definately appreciate it.    I apologize if I didn't provide enough info on my problem.

-Bill
# April 26, 2006 12:56 PM

CC said:

Bill,

You are close. You need to download the BulkEditGridView.cs file from Matt's workspace and place it in your App_Code folder.

The web.config file only needs to have:
<pages>
<controls>
<add namespace="RealWorld.Grids"  tagPrefix="rwg"/>
</controls>
</pages>

Then in your aspx page, you can drop in a regular gridview control and change the tag to <rwg:BulkEditGridView and things should be fine.

Hope this helps.
# April 27, 2006 4:54 PM

CliffMitchell said:

Matt,

I notice that when I set the EmptyDataText attribute on a BulkEditGridView control I get an error when the data set is empty:

Unable to cast object of type 'System.Web.UI.WebControls.TableCell' to type 'System.Web.UI.WebControls.DataControlFieldCell'.

Any suggestions as to how to get around this?
Thanks,
Cliff
# April 30, 2006 9:23 AM

Thiru said:

I am having difficulties in finding the source code for this gridview control. Please help.

Thanks,

Thiru
# May 4, 2006 2:18 PM

Dan said:

I am also trying to find the source code for this. Please excuse me as I am a newbie to the ASP.net world.
# May 4, 2006 5:26 PM

Matt Dotson said:

I have updated the BulkEditGridView to fix the EmptyDataText problem.  Thanks Cliff!!
# May 4, 2006 11:26 PM

Matt Dotson said:

I have checked in changes to allow inline inserts to the BulkEditGridView.  It's still beta and I haven't done a whole lot of testing on it.  Try it out and let me know what you think.  

Eventually I'll get around to writing an article on it.

PS. You have to set EnableInsert=true and there should be a blank row at the bottom.  You have to click the save button to perform the insert on the server.
# May 5, 2006 1:32 AM

Dan C said:

I have posted a VB version on the gotdotnet workspace. Can somone help with the testing
# May 8, 2006 2:10 PM

Diego said:

Great controls!!!!

Is there a release in C#?
# May 9, 2006 5:28 AM

Christian Lebon said:

I am testing this as we speak.  Two things if you guys done mind.

What is the syntax on using the DropDownField control?  I am hoping that is like a combobox that lets you either chose or type free text.

And what about throwing a date picker in there?  That would be sweet as well.  

So you know, I have throw this into an Atlas update panel and it works well.  Thanks so much!

Christian
# May 9, 2006 4:32 PM

Steve said:

Happy to test - tell me how to find it!
# May 9, 2006 11:13 PM

Christian Lebon said:

I notice when there are no records returned I am not seeing the insert row.  how can enable this regardless of whether there are records returned or not.
# May 10, 2006 3:20 AM

Chagel's Home said:

# May 17, 2006 2:52 AM

mattdotson said:

Steve ... there is a link to the zip file at the end of the article.

Diego ... all of my code is in C#, you can download the zip, or see the current code in the GotDotNet workspace.

Christian ... the DropDownField is for a dropdown column in the grid view.  It has a few issues though that i haven't had time to work out yet.

That's awesome to hear it works with atlas, I hadn't tried that yet.

I'm going to look into the no rows issue.
# May 17, 2006 11:34 PM

Diego said:

> Diego ... all of my code is in C#, you can download the zip, or see the current code in the GotDotNet workspace.

Uhm... I downloaded the files, one a time, from source control  (I can only use html interface :-| ), but I can't find the zip.
However there is no problem, all works fine ;-)

Thank you and bye.

Diego
# May 18, 2006 3:31 AM

Christian said:

Matt,
Great.  Please let me know if you get that combobox worked out and the syntax if you would.

One of the things that I am trying to figure out is how to put Atlas Autocomplete extender textboxes in a grid.  That way, you get the combo type setup, but it loads nothing until used.  I think that would be a simply awesome tool.
# May 18, 2006 3:11 PM

Joe said:

Set everything up using an Access DataSource and an Update command in the datasource, but when I click Save button, the changed data in the Gridivew goes back to the original data and I don't get any error. The code is making it to the "Me.UpdateRow(row, False)", but no updating is taking place. The Update SQL statement in my DB is

UPDATE T2_Wo SET [Project#] = @Project, [Task Description] = @Description where ID=@ID

<UpdateParameters>
           <asp:parameter Name="Project"/>
           <asp:parameter Name="Description"/>
           <asp:Parameter Name="ID"/>
           </UpdateParameters>

Any ideas?
Joe
# May 19, 2006 11:38 AM

Elizabeth said:

Life saving topic.  I worked with VS2003 DataGrids now I have to work in VS2005 GridView, bulk editing plus multi-view.  I will look at the VB code that is attached and try to decipher.  
# May 22, 2006 1:12 PM

mattdotson said:

Good news ... I've made some more updates.  And I've posted a zip on the Releases of the GDN Workspace for everyone who is having trouble with the source code control.
# May 25, 2006 2:39 AM

molhokwai said:

Hi Matt,

I've tested implementing the BulkEdit RealWordl GridView. Works fine with a SqlDataSource with UpdateCommand and all... But the only way I could get it to work (with a DataSet) is to do the following:
- Implement the RowUpdating method (in the code-behind page) and in the method get the bound row from the DataSource and update the values
- And afterwords call the DataSet's corresponding table adapter's Update method...

Is there a way to just bind the RealWorld Grid View an let it do all the work without having to code the update and all?

Thanks...
# May 30, 2006 4:24 AM

Lars said:

This is just what I need for my project! But I want only to enabled only some rows for editing, while the others are read only.

I have tried quite a few approaches, but no success. In some event handlers, row.DataItem == null, and in RowDataBound it seems to be too late to change rowState, but row.DataItem != null.

I use a ObjectDataSource, which seems to complicate matters even more...

What am I missing?
# May 31, 2006 10:24 AM

Amino said:

I must thank mattdotson for his great job. I have integrated it into my project. I am able to get both objectdatasource and sqlDataSource working without any problems.

So great, so simple.

weldone
# June 15, 2006 11:58 AM

ShaunR said:

Great blog, and controls..thanks!
# June 16, 2006 11:31 AM

なんとかblog said:

先日ObjectDataSource,ObjectDataSourceViewを...
# June 20, 2006 12:20 PM

Phil said:

I have the bulk Edit displaying data but when I call the save I get this error:

"You have specified that your update command compares all values on SqlDataSource 'AccessDataSource2', but the dictionary passed in for oldValues is empty.  Pass in a valid dictionary for update or change your mode to OverwriteChanges."

Any help would be great.
# June 21, 2006 3:40 PM

rosy said:

its very helpfull to us thankx
# July 1, 2006 2:20 AM

Srinivasa Raghavan said:

how to do bulk update of rows using  sqldatasource control.
# July 7, 2006 12:23 PM

WadeC said:

Got any tips on how you could best implement the EditRow property...

I have a grid with a template field containing a user control. When the user control is pressed I need to know the row it is on. I am having difficulty obtaining this information with your grid. Previously I used the EditRow.

I have been trying to add an OnRowClick event to store the row in a hidden field - but I am finding that the user control's event can fire before the rowclick and there is no row info available.

WadeC
# July 12, 2006 8:56 PM

ndramkumar said:

I had used the Gridview and it is very useful for my requirement. But I have some issues with it. Please anybody help me to solve it out.

My requirement is to dynamically generate columns (Bound and Template Columns as required) in GridView control. So I need to dynamically bind the controls at runtime. I need to do a bulk update to the database from the GridView.

I am facing problems in binding the control dynamically. When I dynamically create a text box and bind it to a GridView, during the update the value of the textbox is not properly passed to the ControlParamter of SqlDataSource control which will update the database. The values updated to the database are null.

Initially I used the Page_Load method to generate the controls in GridView and later changed it to Page_Init in order to handle the post back issues with the dynamically created textbox.

What is the problem that I am facing?

Please suggest a solution for this problem.

I am attaching the source code which dynamically binds the controls to the GridView at runtime.



void ITemplate.InstantiateIn(System.Web.UI.Control container)
   {
       switch (_templateType)
       {
           case ListItemType.Header:
               //Creates a new label control and add it to the container.
               Label lbl = new Label();            //Allocates the new label object.
               lbl.Text = _columnName;             //Assigns the name of the column in the lable.
               container.Controls.Add(lbl);        //Adds the newly created label control to the container.
               break;

           case ListItemType.Item:
               //Creates a new text box control and add it to the container.
               TextBox tb1 = new TextBox();                            //Allocates the new text box object.
               tb1.DataBinding +=  new EventHandler(tb1_DataBinding);   //Attaches the data binding event.
               tb1.ID = _columnName;
               tb1.Columns = 15;                                      //Creates a column with size 4.
               //tb1.Text = _dr[_columnName].ToString();
               container.Controls.Add(tb1);            //Adds the newly created textbox to the container.
               break;
           
           case ListItemType.AlternatingItem  :
               //Creates a new label box control and add it to the container.
               Label lb1 = new Label();                            //Allocates the new label object.
               container.Controls.Add(lb1);                            //Adds the newly created label to the container.
               break;

           case ListItemType.EditItem:
               TextBox tb2 = new TextBox();                            //Allocates the new text box object.
               tb2.DataBinding += new EventHandler(tb1_DataBinding);   //Attaches the data binding event.
               tb2.ID = _columnName;
               tb2.Columns = 15;                                      //Creates a column with size 4.
               tb2.ReadOnly = false;
               container.Controls.Add(tb2);                            //Adds the newly created textbox to the container.                
               break;

           case ListItemType.Footer:
               CheckBox chkColumn = new CheckBox();
               chkColumn.ID = "Chk" + _columnName;
               container.Controls.Add(chkColumn);
               break;
       }
   }

   /// <summary>
   /// This is the event, which will be raised when the binding happens.
   /// </summary>
   /// <param name="sender"></param>
   /// <param name="e"></param>
   void tb1_DataBinding(object sender, EventArgs e)
   {
       TextBox txtdata = (TextBox)sender;
       GridViewRow container = (GridViewRow)txtdata.NamingContainer;
       object dataValue = DataBinder.Eval(container.DataItem, _columnName);
      if (dataValue != DBNull.Value)
       {
           txtdata.Text = dataValue.ToString();
       }
 
   }



# July 26, 2006 1:45 AM

Jurjen de Groot said:

I've been using the control. GREAT, however I can't seem to get the Pager to be anywhere but to the Left of the control. Setting the PagerStyle HorizontalAlign to "Center" (or any other value) doesn't make a difference. It won't move !!

Anybody else experience the same and maybe got a solution to this ?

Regards,
Jurjen de Groot
Netherlands.
info AT gits DASH online DOT nl
# August 1, 2006 10:11 AM

Roy DA said:

In reference to the comment below...How is what molhokwai proposes actually accomplished? I can use the Real World Gridview great if it's a one-for-one update, meaning for every grid record, there exists one database record.
However, when I try to use the bulk edit on a summary-style page in which one grid record may have several associated database records, it just doesn't work... No error, it just refreshes with nothing occurring. HELP!


# re: Real World GridView: Bulk Editing
Tuesday, May 30, 2006 4:24 AM by molhokwai
Hi Matt,

I've tested implementing the BulkEdit RealWordl GridView. Works fine with a SqlDataSource with UpdateCommand and all... But the only way I could get it to work (with a DataSet) is to do the following:
- Implement the RowUpdating method (in the code-behind page) and in the method get the bound row from the DataSource and update the values
- And afterwords call the DataSet's corresponding table adapter's Update method...

Is there a way to just bind the RealWorld Grid View an let it do all the work without having to code the update and all?

Thanks...
# August 2, 2006 1:45 PM

k.ekezie said:

the BulkEditgridview works nicely. But I have one issue/question. Is it possible to make the grid generic for any table. By that I mean, do I have to write SQL code (i.e for inserts/updates/deletes for each table in the data source?
# August 3, 2006 11:20 AM

Jay said:

Hi Matt

Nice article. I have a question, how exactly we can handle if there are DBcombo's involved in this application in other words how do we validate the value of the 2nd combo based on the first combo selection before saving it the database.


Thanks
Jay
# August 9, 2006 12:31 PM

Adam said:

Hey, thanks for the BulkEditGridView! Now, is there an easy way to turn off validation for the insert row? i.e. so it can be left empty? Thanks!!!
# August 10, 2006 9:37 AM

Ram said:

Hi There

Can someone tell me where exactly is it storing the record values in the class. In other words i need to do some validation on particular colmns.

Thanks
Ram
# August 10, 2006 3:57 PM

Fearson said:

your article is very usefull to me,thanks a lot
# August 11, 2006 12:51 PM

Ulysses said:

is it possible that each row is put into the magicajax panel. my purpose of doing it is that, i wanted the row to be update but without reloading the whole gridview..
# August 15, 2006 10:44 PM

Miguel said:

Have you thought of implementing a "undo" command? I implemented your excellent solution adding support to RadioButtonList, but I wanted to make it possible to revert the changes in a single click. One Idea would be simply make DirtyRows.Clear() and Save(), but unfortunately, although it does not update anything, the selected radiobuttons are still the changed ones.
# August 20, 2006 10:53 AM

Dave said:

I copied the BulkEditGridView.vb to my appdata directory and added the line <%@ Register Namespace="RealWorld.Grids" tagPrefix="rwg" %> to the top of my aspx page.  This line is underlined in green and a message stating that "the namespace does not have any public methods" appears.  What am i missing?
# August 21, 2006 12:41 PM

Jeff said:

I had the same problem as Mark above when trying to use optimistic concurrency with this grid.  What I found out was that the base GridView class only keeps track of old values for the row with the EditIndex.  I attached some code below to store the old values in the viewstate, and then add them to the colleciton during an update.  It needs some work in order to accomodate different type of data keys, but this should help get someone started.

       // DataTable to store original values
       private DataTable originalDataTable;
       private bool tableCopied = false;

       protected override void OnRowUpdating(GridViewUpdateEventArgs e)
       {
           string select = "";

           // Retrieve original values data table from view state
           if (originalDataTable == null)
               originalDataTable = (DataTable)ViewState["originalValuesDataTable"];

           // Consturct a select string to retrieve original row based on key values
           foreach (string key in e.Keys.Keys)
           {
               if (select.Length > 0)
                   select += " and ";

               Type type = e.Keys[key].GetType();

               if (type == typeof(string))
                   select += String.Format("{0} = '{1}'", key, e.Keys[key]);
               else if (type == typeof(DateTime))
                   select += String.Format("{0} = '{1:g}'", key, e.Keys[key]);
               else
                   select += String.Format("{0} = {1}", key, e.Keys[key]);
           }

           // Find original row
           DataRow[] rows = originalDataTable.Select(select);
           if (rows.Length == 0)
               throw new InvalidOperationException("Could not locate original values for row being updatated");

           DataRow row = rows[0];

           // Add original values to OldValues collection
           foreach (string key in e.NewValues.Keys)
           {
               if (row[key] == System.DBNull.Value)
                   e.OldValues.Add(key, null);
               else
                   e.OldValues.Add(key, row[key]);
           }

           base.OnRowUpdating(e);
       }

       protected override void OnRowDataBound(GridViewRowEventArgs e)
       {
           // Save data table of original values to view state
           if (e.Row.RowType == DataControlRowType.DataRow)
               if (!tableCopied)
               {
                   originalDataTable = ((DataRowView)e.Row.DataItem).Row.Table.Copy();
                   ViewState["originalValuesDataTable"] = originalDataTable;
                   tableCopied = true;
               }

           base.OnRowDataBound(e);
       }
# August 21, 2006 2:19 PM

Dave said:

I have never used a custom control and am new to ASP development.  Do I need to compile the .vb file before I can use it in my website?  I have gridviews in my site, but would like to use the mutli row editing.  

I would be most thankful if anyone could provide steps start to finish on how to make the switch. I've tried several things but cannot get it right. I've searched endlessly on web and have not been able to figure it out.
# August 21, 2006 5:07 PM

Jeffry said:

May I have a sample project as well? (jevans161 @ yahoo.com).

Thanks,

Jeffry
# August 23, 2006 11:05 AM

Miguel said:

I have an EditTemplate in the BulkEditingGridView and a CheckBox. The event for CheckedChanged is changing the TextBox that is databound. Unfortunately, although the Checkbox has AutoPostBack=true and the TextBox changes to what I want, the DirtyRows is not updated, so if I don't make any other change Save() does not work...
All this is inside an UpdatePanel, do you have any idea why this behaviour happens?
# August 23, 2006 1:36 PM

Jeremy Fuller said:

Validation is actually pretty easy for this control, although it does need to be manually handled during the Save event. First, don't set the SaveButtonID property so you can handle the event yourself. I handle the Click event of my save button, and I've attached my code below. The BulkEditGridView control I'm working with has four columns, and only the fourth column is enabled for editing. The user enters decimal values, and anything else needs to be rejected. My code detects validation failures, sets an error Label control, and highlights the cell with the problem. My control is called Editor. Hopefully you can put this to work in your own project!

<pre>protected void Save_Click(object sender, EventArgs e)
{
 foreach (GridViewRow r in Editor.DirtyRows)
 {
  TextBox c = (TextBox)((DataControlFieldCell)r.Controls[3]).Controls[0];
  try
  {
   decimal d = decimal.Parse(c.Text);
  }
  catch
  {
   r.Cells[3].BackColor = System.Drawing.Color.Pink;
   c.BackColor = System.Drawing.Color.Pink;
   lblError.Text = "<br /><br />You entered an invalid number in the editor. Please correct and click Save again.";
   return;
  }
  r.Cells[3].BackColor = System.Drawing.Color.Empty;
  c.BackColor = System.Drawing.Color.Empty;
 }
 lblError.Text = "";
 Editor.Save();
 // ...
}</pre>
# August 24, 2006 11:26 PM

Seetapati said:

First of all, this is a great control. However, I have a couple of questions... How do you implement his control with an objectdatasource where the update method has no arguments. The update method in the object calls a stored procedure that takes 2 parameters that come from a session variable. Any ideas?
# September 9, 2006 3:54 PM

Shahzad Godil said:

Great control.  Save my week of time.  I was able to implement it using ObjectDataSource without any change.  It simply work by just binding to my object datasource and Insert/update/select work as normal gridview.

In case of any issue if somebody is facing, he can chat me on my msn messenger : shazadgodil@hotmail.com or

EMail : shahzadgodil@gmail.com

# October 16, 2006 2:23 PM

Shahzad Godil said:

Only issue I am facing is that my custom skin is not applying to this grid.

# October 16, 2006 2:25 PM

Tomer said:

How would i prevent from all text boxes to appear editable?,meanning i would like to

make the row editable only after user select the row.

EMail : tomerdr@hotmail.com

# October 17, 2006 3:56 PM

Binod said:

Thanks to all of you.. I have been waiting for the same and have tried to do the same thing.. but could not achieved.

# October 20, 2006 3:38 PM

LSUgirl said:

Great tool.  I do have a question.  I have a sql that contains an outer join.  So, every time, I'm bringing back 2 rows from the database.  That could mean 2 inserts, one insert/one update or 2 updates.  I currently have all the rowstate set to Edit but the Insert isn't working on the Save Row.

Can I change the rowstate dynamically after the grid is dataloaded?  I need to check an id column in the grid view (hidden label) to find if it's an update or insert.

# October 23, 2006 5:24 PM

simon said:

It is a very great tool to use. But I got a problem.... My gridview is binded to an objectDataSource, dont know why updateRow methods can not set the editable field values to the objectDataSource, which results in that all the editable fields appears null in the object of the objectDataSource... do you have any idea???

Thank you

# October 24, 2006 5:02 AM

Harry said:

You need to call the Save() method, otherwise the DataSource is not updated.

# October 26, 2006 11:44 AM

Jesper said:

If there is no data bound to the table then there is an error in the method CreateInsertRow()

Object reference not set to an instance of an object.

this.InnerTable is null

What should be done to fix this?

Regards Jesper

# October 30, 2006 10:58 AM

Judah said:

I think I have the same problem as simon from 10/24.  When updating, any column that I have set visible="false" gets sent to the datasource with a value of NULL.  Any way to make it forward the original value?

Thanks

Judah

# October 30, 2006 6:11 PM

Jesper said:

Hi!

When there is no data from the source, when using bulkedit-insert, only one new insert row is put into the gridview. But no headers are shown! (which is, beacause there is no data)

Is there a way to bind headers to the only insert row anyway?

I would like to use it in such a way that , when I enter this page with the gridview (with two columns) the first time I'll have no data in the database so there should only be an empty insertrow. Above the insertrow I would like a headerrow with with headers describing the columns.

When I have data in the database it should be a number of rows with data and an empty insert row in the bottom.

Does any one have any ideas?

/Jesper

# November 2, 2006 8:02 AM

Evgueni said:

Hello,

I downloaded your solution (it's excellent, by the way), but one thing doesn't work -- the IDReferencePropertyAttribute on SaveButtonID does not cause my Visual Studio to display a drop-down of buttons on the page -- the property appears just like a text box. Do you know why this might be? Thanks.

Evgueni

# November 7, 2006 6:57 PM

Ron Delzer said:

You can simplify the code in your OnLoad override by casting to the IButtonControl interface rather than the specific button class. This version supports Button, LinkButton, and ImageButton and any other class that implements IButtonControl.

       protected override void OnLoad(EventArgs e)

       {

           base.OnLoad(e);

           if (!String.IsNullOrEmpty(this.SaveButtonID))

           {

               IButtonControl button = (IButtonControl)RecursiveFindControl(this.NamingContainer, this.SaveButtonID);

               button.Click += new EventHandler(SaveClicked);

           }

       }

# November 9, 2006 2:48 PM

Jit said:

Nice work , but there are two things I still want to ask. Firstly how do I use a DropDownList (Probably as a template column) and a readonly field value, which will be changed on selection of dropdown value. And I want to save both (DropDownList value and readonly field value as bulk).

# November 13, 2006 2:15 AM

Vincent D'Souza said:

I love this extension of the GridView.  But need some mo