5

I'm using the BulkEditGridView control as discussed http://roohit.com/site/showArc.php?shid=bbb62, and it's perfect for my needs. The problem I'm having is that whenever I save, every visible row (I have paging enabled) gets updated. Stepping through the code I see that grid.DirtyRows.Count is equal to the number of items per page minus 1 when I click the save button.

I can't find where rows are set as dirty. Any suggestions where I can look?

My code-behind has only this:

using System;
using System.Web.UI.WebControls;
using System.Collections.Generic;
using System.Collections;
using System.Data.Common;

public partial class MSDS_MSDS_Admin_GridUpdate : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            gridMSDS.DataKeyNames = new String[] { "id" };
            gridMSDS.DataBind();
        }
    }
}

EDIT: Here is the aspx code.

<%@ Page Language="C#" MasterPageFile="~/MSDS/MSDS.master" AutoEventWireup="true" EnableEventValidation="false" CodeFile="GridUpdate.aspx.cs" Inherits="MSDS_MSDS_Admin_GridUpdate" Title="Untitled Page" %>

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>
<%@ Register Assembly="RealWorld.Grids" Namespace="RealWorld.Grids" TagPrefix="cc2" %>
<asp:Content ID="Content1" ContentPlaceHolderID="PageContent" runat="Server">
    <br />
    <asp:Button ID="btnSave" runat="server" Text="Save" Width="100%" />
    <cc2:BulkEditGridView ID="gridMSDS" runat="server" AllowPaging="True" AllowSorting="True"
        DataSourceID="sqlData" EnableInsert="False" InsertRowCount="1" PageSize="20"
        SaveButtonID="btnSave" AutoGenerateColumns="False">
        <Columns>
            <asp:BoundField DataField="ID" HeaderText="ID" InsertVisible="False" Visible="false"
                ReadOnly="True" SortExpression="ID" />
            <asp:BoundField DataField="ChemicalTitle" HeaderText="ChemicalTitle" SortExpression="ChemicalTitle" />
            <asp:TemplateField HeaderText="SheetDate" SortExpression="SheetDate">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("SheetDate") %>' Width="85px"></asp:TextBox>
                    <cc1:CalendarExtender ID="TextBox1_CalendarExtender" runat="server" Enabled="True"
                        TargetControlID="TextBox1">
                    </cc1:CalendarExtender>
                </EditItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label2" runat="server" Text='<%# Eval("SheetDate") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:BoundField DataField="Filename" HeaderText="Filename" SortExpression="Filename" />
            <asp:BoundField DataField="Manufacturer" HeaderText="Manufacturer" SortExpression="Manufacturer" />
            <asp:BoundField DataField="UsageDept" HeaderText="UsageDept" SortExpression="UsageDept" />
            <asp:TemplateField HeaderText="Notes" SortExpression="Notes">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox5" runat="server" Text='<%# Bind("Notes") %>' TextMode="MultiLine"></asp:TextBox>
                </EditItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label6" runat="server" Text='<%# Bind("Notes") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Status" SortExpression="Status">
                <EditItemTemplate>
                    <asp:DropDownList ID="ddlStatus" runat="server" DataSourceID="sqlStatus" DataTextField="DisplayValue"
                        DataValueField="Value" SelectedValue='<%# Bind("Status") %>'>
                    </asp:DropDownList>
                    <asp:SqlDataSource ID="sqlStatus" runat="server" ConnectionString="<%$ ConnectionStrings:NCLWebConnectionString %>"
                        SelectCommand="getOptionList" SelectCommandType="StoredProcedure">
                        <SelectParameters>
                            <asp:Parameter DefaultValue="msds_Status" Name="ListName" Type="String" />
                        </SelectParameters>
                    </asp:SqlDataSource>
                </EditItemTemplate>
                <ItemTemplate>
                    <asp:DropDownList ID="ddlStatus" runat="server" DataSourceID="sqlStatus" disabled="true"
                        BackColor="White" DataTextField="DisplayValue" DataValueField="Value" SelectedValue='<%# Bind("Status") %>'>
                    </asp:DropDownList>
                    <asp:SqlDataSource ID="sqlStatus" runat="server" ConnectionString="<%$ ConnectionStrings:NCLWebConnectionString %>"
                        SelectCommand="getOptionList" SelectCommandType="StoredProcedure">
                        <SelectParameters>
                            <asp:Parameter DefaultValue="msds_Status" Name="ListName" Type="String" />
                        </SelectParameters>
                    </asp:SqlDataSource>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Health" SortExpression="Health">
                <EditItemTemplate>
                    <center>
                        <asp:TextBox ID="TextBox2" runat="server" Style="text-align: center" Text='<%# Bind("Health") %>'
                            Width="25px"></asp:TextBox>
                    </center>
                </EditItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label3" runat="server" Text='<%# Bind("Health") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Fire" SortExpression="Fire">
                <EditItemTemplate>
                    <center>
                        <asp:TextBox ID="TextBox3" runat="server" Text='<%# Bind("Fire") %>' Width="25px"></asp:TextBox></center>
                </EditItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label4" runat="server" Text='<%# Bind("Fire") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Reactivity" SortExpression="Reactivity">
                <EditItemTemplate>
                    <center>
                        <asp:TextBox ID="TextBox4" runat="server" Text='<%# Bind("Reactivity") %>' Width="25px"></asp:TextBox></center>
                </EditItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label5" runat="server" Text='<%# Bind("Reactivity") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:BoundField DataField="DateUpdated" HeaderText="DateUpdated" SortExpression="DateUpdated"
                ReadOnly="True" />
            <asp:BoundField DataField="UpdatedBy" ReadOnly="True" HeaderText="UpdatedBy" SortExpression="UpdatedBy" />
        </Columns>
    </cc2:BulkEditGridView>
    <asp:SqlDataSource ID="sqlData" runat="server" ConnectionString="<%$ ConnectionStrings:NCLWebConnectionString %>"
        SelectCommand="SELECT [ID], [ChemicalTitle], dbo.dateOnly([SheetDate]) As [SheetDate], [Filename], [Manufacturer], [UsageDept], [Notes], isnull([Status], 4) as [Status], [Health], [Fire], [Reactivity], [DateUpdated], [UpdatedBy] FROM [msds_Sheets] ORDER BY [ChemicalTitle]"
        UpdateCommand="msds_UpdateSheet" UpdateCommandType="StoredProcedure">
        <UpdateParameters>
            <asp:Parameter Name="ID" Type="Int32" />
            <asp:Parameter Name="ChemicalTitle" Type="String" />
            <asp:Parameter Name="SheetDate" DbType="DateTime" />
            <asp:Parameter Name="Filename" Type="String" />
            <asp:Parameter Name="Manufacturer" Type="String" />
            <asp:Parameter Name="UsageDept" Type="String" />
            <asp:Parameter Name="Notes" Type="String" />
            <asp:Parameter Name="Status" Type="Int16" />
            <asp:Parameter Name="Health" Type="Int16" />
            <asp:Parameter Name="Fire" Type="Int16" />
            <asp:Parameter Name="Reactivity" Type="Int16" />
            <asp:ProfileParameter Name="UpdatedBy" Type="String" PropertyName="Username" />
        </UpdateParameters>
    </asp:SqlDataSource>
</asp:Content>

Testing Procedure is as follows:
-Load the page.
-Edit something in the first row.
-Click save button.

MAW74656
  • 3,449
  • 21
  • 71
  • 118
  • See if this post helps http://onthefencedevelopment.com/?p=68 – Bala R Apr 29 '11 at 20:22
  • I'm having trouble with the Dirty Rows in the gridview, not the entire page being flagged as dirty. – MAW74656 Apr 29 '11 at 20:25
  • This sounds like the behavior it should have. What's the problem? – Joe Phillips May 02 '11 at 18:07
  • No definately not. The dirty rows should only be the ones I've edited. Shouldn't they? One of my fields is a UpdatedDateTime field, and I don't want unchanged rows to get a new datestamp. – MAW74656 May 02 '11 at 18:07
  • The purpose is that its easy to edit any column in any row. A user could edit all of them, but probably will just edit some of them. – MAW74656 May 02 '11 at 18:10
  • 1
    I cannot duplicate this behavior, even with paging enabled. (i.e. it only updates the rows that I change.) Can you post your `BulkEditGridView` tag with its attributes, please? Also, can you post anything relevant to the pager as well? – Joel Lee May 02 '11 at 21:09
  • @Joel- Please see edit for relavant markup. I wasn't sure initially what to post. – MAW74656 May 02 '11 at 21:23
  • Thx, I'll have a look. What version of the .NET framework are you using? (BTW, I think you need to remove the "-" at the end of `@Joel-`, otherwise SO will attempt to notify `Joel-`, and I won't see it.) – Joel Lee May 02 '11 at 22:48
  • Something similar happened to me where the dropdownlist was causing the dirty flag. Its pre-bound value was nothing, but the top blank value of the rendered dropdown on post apparently did not equal nothing. I wound up coding around it by setting the isDirty = false if the required fields were also blank. – Eric H May 03 '11 at 01:07
  • @Joel I didn't know that. I usually put - to after someone's tag, I'll stop doing that. Thanks for looking. – MAW74656 May 03 '11 at 13:53
  • @Eric - I do have a dropdown.... What choice do I have if I need a dropdown there? – MAW74656 May 03 '11 at 13:54
  • What version of ASP.NET are you using? I have created a cut down version of your code with a small test database, and I am still unable to reproduce the undesirable behavior that you are seeing. The `Status` drop down does not cause any problems, even when the underlying data contains null values for the Status field. I still want to do some tests with the CalendarExtendar, and will let you know if that turns up anything. – Joel Lee May 04 '11 at 08:07
  • Targeted for asp.net 3.5 – MAW74656 May 04 '11 at 14:04
  • 1
    I did some additional testing, including the `SheetData` field with `CalendarExtender`. I've tried quite a few things, and your code is working fine for me. A few questions: (1) What does your `btnSave_Click` do? It isn't needed for save, since the BEGV control does that for you. (2) Do the rows come back as dirty even if you edit nothing? (i.e. just press `Save`), (3) What is your test sequence? (i.e. give me a few details about what you do, paging, etc., before pressing `Save`), (4) Are there any scripts on the page (other than the ones that are added by the `ToolkitScriptManager`)? Thx – Joel Lee May 05 '11 at 16:10
  • @Joel -See my latest edit. I've changed the code listings to include the entire current file, and added some detail about my test procedure. No special scripts are there. – MAW74656 May 05 '11 at 17:13
  • 1
    I still can't make it break, so we need to figure out what's different about my code/environment and yours. If it were me, I would: (1) create a new dummy master page, and have your page reference that. (2) Comment out the `TemplateField` for `SheetDate` and replace it with a `BoundField`. (You need this because I don't think your `CalendarExtender` will work without your master page, which apparently has the `ToolKitScriptManager`.) – Joel Lee May 05 '11 at 18:30
  • If changing the master page doesn't help, try temporarily replacing the the `TemplateField` for `Status` with a `BoundField`. I don't think the drop down is an issue, but it would be good to eliminate all doubt. – Joel Lee May 05 '11 at 18:31
  • If neither of the previous suggestions help, then we need to look at possible differences between your version of BEGV and mine. – Joel Lee May 05 '11 at 18:32
  • @Joel -You should post these suggestions as an answer. You've done so much work already, I'd like to have the option of awarding you the bounty. – MAW74656 May 05 '11 at 18:42
  • Sure, I have been planning to post the results when it is clear that we are getting somewhere. I'll go ahead and do that now. I also have some suggestions for streamlining what you've got. BTW, part of my motivation in doing the work is that it is a learning exercise for me. I've learned a lot from this. – Joel Lee May 05 '11 at 19:11
  • If necessary, we can use SO chat to figure this out in real time. I need to step away for a few mins, will be back. – Joel Lee May 06 '11 at 20:23

2 Answers2

4

The code you have posted looks fine. My test code is almost identical to yours, but I cannot produce the unwanted results you are seeing.

The most significant difference between your code and mine is that I don't have the code for your master page. My master page is the default page created by VS 2008. Other than that, the differences are in the SQL and the underlying database. Specifically, I am not using any stored procedures, and I have made reasonable assumptions about the data types involved: the ID is int, Status is smallint, SheetDate is Date, DateUpdated is DateTime, and everything else is varchar.

EDIT: Your SheetDate appears to be just the date portion of an underlying DateTime value from the database. The fact that your query transforms the underlying data value for display purposes is not relevant, because the grid view can't tell that it is getting a modified value. END OF EDIT

Here are a few suggestions for diagnosing the problem:

  • Test without master page, or with a very simple dummy master page that is guaranteed not to have scripts that interact with the BulkEditGridView. Since the ToolkitScriptManager is apparently on your master page, you will need to either move the script manager control to the page itself, or else temporarily get rid of the CalendarExtender element, which requires the script manager.

  • Temporarily replace the Status template field with a bound data field, to eliminate possibility of interaction with the DropDownList. The drop down list did not cause problems in my tests, but there may be subtle differences between your code and mine.

  • If neither of these help, then a possible cause is a problem in your version of the BulkEditGridView.

EDIT - Additional suggestions for diagnosis:

  • Based on examination of the source code for BulkEditGridView, it appears that the code is properly tracking which rows of the grid are "dirty". Therefore, the most likely cause of a spurious dirty row is that one or more controls in the grid are incorrectly detecting a change to their data content. You can detect this in the debugger, using the source code for BulkEditGridView, rather than the pre-compiled DLL.

  • Set a breakpoint at the start of HandleRowChanged, the event handler which detects changes in any cell of the grid. By examining the sender parameter of that object in the debugger, you can tell which controls in the grid are undergoing value changes. In particular, you will be able to tell which controls, if any, are incorrectly triggering a value change event.

  • Once you determine which control(s) are incorrectly reporting that their value has changed, you can focus on why this is happening.

END OF EDIT

Some other suggestions to improve the code are as follows. These will not solve the original problem, but will make the code cleaner:

  • In all of the template fields, remove the ItemTemplates. They can never be used, since the BulkEditGridView forces every row to be in the edit state.

  • In the code behind, the BindData() call is not needed, since the source data is specified in the declarative markup, and therefore the grid view control will automatically bind the data.

Joel Lee
  • 3,656
  • 1
  • 18
  • 21
  • I can try it without, but I believe I put that databind() in there while trying to make the sorting and paging work. Also, the Item Templates are probably not needed. I just let them autogenerate at first, then I changed things. – MAW74656 May 05 '11 at 19:32
  • 1
    @MAW - Databind shouldn't be needed for sorting and paging, since that requires postback, so your Databind code wouldn't execute then anyway. I kind of thought the item templates were autogenerated. It wasn't obvious to me that they weren't needed until I looked at the BEGV source code. – Joel Lee May 05 '11 at 19:58
  • SheetDate is actually a datetime which I'm using a user defined sql function to truncate into just a date. I tried replacing the RealWorldGridView dll, no dice. I also tried adding a scriptmanagerproxy onto the content page, no dice. – MAW74656 May 06 '11 at 19:00
  • Just tried the BEGV code in a new web form, same behavior. I'm going to re-download the the BEGV and replace my dll. – MAW74656 May 06 '11 at 19:06
  • Fresh dll, new web form, and with databind() commented out, same thing. – MAW74656 May 06 '11 at 19:13
  • @Joel -I think I might just have to do this manually. Can I make my own dirty rows listing and loop through it myself to save rows? – MAW74656 May 06 '11 at 19:14
  • I guess you could do that, but I don't think you can improve on what BEGV is doing. The real issue is what causes the GridView to detect that a row has changed. That's what we need to pinpoint. Do you have the source code? – Joel Lee May 06 '11 at 19:42
  • I just re-downloaded the dll from http://aspnetrealworldcontr.codeplex.com/releases/view/1674. Source code is there. – MAW74656 May 06 '11 at 19:45
  • Have a look at how they track dirty rows. I think you will come up with pretty much the same thing. Set a breakpoint at the beginning of `HandleRowChanged`. That handles changes due a TextBox, CheckBox, or a DropDownList. See AddChangedHandlers for where sets up the event handler. – Joel Lee May 06 '11 at 20:04
  • I couldn't do it better, agreed. But why isn't his working!? – MAW74656 May 06 '11 at 20:09
  • That's what we have to figure out. Need to isolate what control is causing the problem. Set the breakpoint on the `HandleRowChanged`, see if you can figure out which control is causing spurious dirty. The handler should fire once for each changed cell (not row) of the GV. So it may fire multiple times for a single row. One thing you could try is to, one at a time, comment out the code that adds the handlers in `AddChangedHandlers`. e.g., comment out adding the handler for drop downs. If the problem goes away, you know that DDL is the culprit. If DDL is the problem, then we can focus on why – Joel Lee May 06 '11 at 20:20
  • I think I'm missing something. When using the dll (the precompiled version of the control) Visual Studio doesn't debug within it. How can I debug that code? – MAW74656 May 06 '11 at 20:24
  • 1
    You will have to use the source code, not the precompiled dll. There are a couple of ways to do that. – Joel Lee May 06 '11 at 20:51
  • I created a chat room that we can use, if you want. Using comments is too slow. Click on chat and search for "Bulk" in the room filter. – Joel Lee May 06 '11 at 21:11
  • @MAW ignore earlier remark about commenting out stuff in `AddChangedHandlers`. That will work, but you can just look at the `sender` argument when the handler fires to figure out which control is responsible for the event. – Joel Lee May 06 '11 at 21:22
  • @MAW - Due to work changes, I will not be available until after 5pm CST, going forward. But I am still interested to find out what is causing this, without regard to bounty, I will edit my answer to include the additional diagnosis suggestions made in comments above, and will look for any results/comments after work. Good luck. – Joel Lee May 09 '11 at 04:39
  • @Joel -Any time you are able to check and respond is fine by me. Your deserve the bounty, but I will continue troubleshooting and posting the results here. – MAW74656 May 09 '11 at 14:04
  • The problem is the Textbox Calendar extender. Its causing all rows to be set as dirty. Commenting that out makes it work right. Now I have to find a way to use it without breaking the BEGV, as I'm not going to let my user's type in dates. That way lies madness. – MAW74656 May 09 '11 at 15:12
  • I updated the ACT, no change. I tried to wrap the textbox and calendar extender in an updatepanel, and the save code couldn't see the textbox. – MAW74656 May 09 '11 at 16:16
  • @MAW - Well, that's progress! But still a mystery because I'm using CalendarExtender, with no problems. To make sure that you are actually using the updated ACT, check the version number of the ACT dll in your web site's bin directory. The most recent for .net 3.5 is version number `3.5.50401.0`. If the version number is correct, you might try deleting the ASP.NET temporary files in the .NET v2.0 framework, just to make sure you aren't picking up an earlier version of the JIT-compiled code. – Joel Lee May 10 '11 at 01:01
  • @Joel -Which temporary files are you referring to? – MAW74656 May 10 '11 at 14:21
  • @MAW - Please ignore remark about deleting temp files. It won't hurt, but it won't help either. The location of these files for .net 3.5 is `%Windir%\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files` I have had to delete these on several occasions due to problems with web page code and stuff in App_Code. I was thinking that DLLs in the bin directory got copied to the temp folder as well, but they don't. Also, JIT code (i.e. native code from JIT compiler) is not placed in the temp folder. It is cached in memory. Apologies for confusion. – Joel Lee May 11 '11 at 04:17
  • 1
    I am at a loss, since I cannot duplicate this. See if the code works if you just return `sheetDate` in your query, without calling `dbo.dateOnly`. I have tried my code with a full `DateTime` value here, and it works fine. I can't see how truncating the date would make any difference whatsoever, but it is the only significant difference between my code and yours. I'll try to check for a response at lunch time. – Joel Lee May 11 '11 at 04:43
  • HOLY HELL! It likes things without dbo.dateOnly()! WTF! Ok, so now we found the problem (even if we're not sure why), is there a way I can only show the date part in the textbox? My user's will be confused with a time there. – MAW74656 May 11 '11 at 14:06
  • I am so mad about this! Why is it so hard to find documentation on things? The answer is to format the datetime into just a date within the bind tag of the textbox like so: `Text='<%# Bind("Sheetdate", "{0:M/dd/yyyy}") %>'`. All fixed! Thanks for all your help! – MAW74656 May 11 '11 at 14:24
  • @MAW - Glad it's fixed. I've used stuff like that where there is insufficient documentation and you are reduced to reinstalling stuff and one-at-time disabling what you **think** is the most likely cause of the error. – Joel Lee May 11 '11 at 20:19
0

I am not an expert in BulkEditGridView control, but this is what I found at Matt Dotson's blog entry

You may not be able to know that the row has been updated, unless you watch for the change explicitly. First, for each row add a change handler

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

You can use this snippet

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

Then you define a dirty row list and add rows that needs to be updated there (in the event handler)

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

Finally, to commit changes, iterate through all the dirty rows and save changes

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

      dirtyRows.Clear();
   }

And here is your ASPX code

<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>
oleksii
  • 35,458
  • 16
  • 93
  • 163
  • I saw that, but looking at the source code of the control, that's all built into the control. The give-aways are the use of this., base., and override. This is part of what's confusing me. – MAW74656 May 02 '11 at 19:27
  • This seems to be the code of the BulkEditGridView control itself, so theoretically you just need to call just Save method. I can't see in your code that you actually called Save, have you? – oleksii May 02 '11 at 19:42
  • His article says that isn't needed. He's very proud of providing a good developer experience. I'll see if it makes any difference. – MAW74656 May 02 '11 at 20:27
  • No effect with an explicit call to Save(); – MAW74656 May 02 '11 at 20:34
  • @MAW74656 - You are correct. `Save()` is called internally by the control when the `Save` button is clicked. Clicking it again won't do anything, because the save method clears the `dirtyRows` list (see code above in this answer). – Joel Lee May 02 '11 at 21:05