CancelEdit does not work for BusinessListBase

CancelEdit does not work for BusinessListBase

Old forum URL: forums.lhotka.net/forums/t/2898.aspx


David posted on Thursday, May 17, 2007

I am trying to do something that I thought would be dead easy but it’s proving to be anything but that. I have a simple editable root list object that I want to edit in a data grid view. I want the user to be able to edit the data in the grid, and then click a save or cancel button to save or cancel ALL the changes. The save bit is easy, but I cannot get the cancel to work.

In my simplified test example I have created the BusinessListBase object and a form with a binding source control, a data grid view and a save and cancel button. In the form load I get the list object and assign it to the binding source. In the cancel button click event I call myList.CancelEdit.

I can get it to cancel the update to the last row in the grid, but if I change more than one row it will not cancel the others. I have read through quite a few other posts on this issue and I am more confused than when I started. Is there a simple and reliable way to get the cancel to work or should I give up on it now?

david.wendelken replied on Friday, May 18, 2007

We got this working in a web environment just yesterday.  At first, we didn't think it worked, but we were failing to set/get the cached copy of the list at the right times.

We were only going for one level of undo, because in the web environment we're only editing one record at a time (so far. :).

David replied on Friday, May 18, 2007

I'm not implementing caching, just trying to do a CancelEdit on a single instance of an object. I also only want one level of undo, but once I edit more than one row in the grid I can't even get one level of undo. I would be interested to hear what you changed to get it working though.

In truth, my object started out with caching (implemented just like in the book using a shared variable), and eventually I need to get back to that. I have removed it at this point as I need to get the Cancel working reliably.

In fact if it weren't for my need to use a cached object I would simply achieve my goal by discarding the object. However, because it does use caching I need to get the Cancel working reliably.

RockfordLhotka replied on Friday, May 18, 2007

I'm working on this at the moment - doing some research. It doesn't look all that good.

Data binding is very aggressive about calling BeginEdit on the current row. To the point that immediately after you do bindingSource.CancelEdit/EndEdit it calls BeginEdit.

This means that the current row is always one edit level above all other rows. Even unbinding the list leaves that row at an elevated edit level, because they don't do an EndEdit or CancelEdit during the unbinding process.

It looks like this sort of approach may solve the issue:

    private void cancelButton_Click(object sender, EventArgs e)
    {
      // get business object reference
      DataList list = (DataList)this.dataListBindingSource.DataSource;

      // cancel current row
      this.dataListBindingSource.CancelEdit();

      // unbind the UI
      UnbindBindingSource(this.dataListBindingSource);

      // cancel the list and restart editing
      list.CancelEdit();
      list.BeginEdit();

      // rebind the UI
      this.dataListBindingSource.DataSource = list;
    }

    private void UnbindBindingSource(BindingSource source)
    {
      System.ComponentModel.IEditableObject current = this.dataListBindingSource.Current as System.ComponentModel.IEditableObject;
      this.dataListBindingSource.DataSource = null;
      if (current != null)
        current.EndEdit();
    }

This presupposes that you've called BeginEdit() manually on the business list BEFORE binding it to the bindingSource in the first place.

David replied on Friday, May 18, 2007

Thanks for the post Rocky, that appears to work well. I've tested it with both the DevEx grid and the DataGridView and it's fine with both.

When you say 'it doesn't look good', do you mean it's not a particularly simple/clear solution, or is there something else that you are concerned about? I get very nervous about including somthing in a commercial application that's fragile.

RockfordLhotka replied on Friday, May 18, 2007

“Doesn’t look good” merely means that the code behind a cancel button is a lot more complex than I think it should be. I keep thinking that this is just not a complex goal, but that code is many times longer than I’d like to see… (2 lines seems about right to me) But it is what it is, and this technique does solve the issue.

 

You should know that this morning’s research did uncover a somewhat obscure bug in CSLA itself, which is now fixed in svn – both BLB and BB were impacted in the solution. You could back-port the changes into 2.1.4 though, they aren’t complex.

 

The bug manifests only in the case that:

 

1.       A user selects a row

2.       The user deletes THAT row

3.       The user clicks the form-level Cancel

 

The result is that the deleted-and-now-undeleted row has an elevated edit level (1 higher than it should) and so you’ll get some unpredictable results.

 

One of the things on the wish list is to catch incorrect edit levels like that and throw an exception. I think that may have to come higher in the priority list, because it would certainly help debug/diagnose this type of issue much more rapidly.

 

Rocky


No virus found in this outgoing message.
Checked by AVG Free Edition.
Version: 7.5.467 / Virus Database: 269.7.3/809 - Release Date: 5/17/2007 5:18 PM

Patrick.Roeper replied on Friday, September 28, 2007

I've just run into this issue myself... Was this fixed in CSLA 3? (I am not sure where the wish list is...)

ajj3085 replied on Friday, September 28, 2007

Undo issues seem to really be fixed in 3.0.2 test 4.  There's a bug in 3.0.1 for sure, it might be there in 3.0 as well, I'm not sure.

RockfordLhotka replied on Friday, September 28, 2007

Yes, the issue is resolved in 3.0.2. Though the resolution is mostly in your code, not in CSLA itself, though I did make a couple small (but important) changes to CSLA as well.

The big changes though, are in PTWin, and you need to model your UI code directly after the approach taken in the PTWin forms - which is a fair amount of code, but at least it works.

Thankfully WPF is simpler, and in the long run it'll replace Windows Forms, so this complexity is (kind of) a moot point.

Patrick.Roeper replied on Friday, September 28, 2007

When we started implementing our UI code we were not following the databinding code that you have in PTWin (unknowingly). Once I was beta'ing CSLA 3 and we were getting edit-level exceptions, we've moved all Save(), New(), and Delete() calls on the UI to generic methods that follow your PTWin examples:

public static T SaveRoot<T>(T businessObject) where T : BusinessBase<T>

{

return SaveRoot(businessObject, null, false);

}

public static T SaveRoot<T>(T businessObject, BindingSource bindingSource, bool rebindDataSource) where T : BusinessBase<T>

{

if (bindingSource != null)

{

bindingSource.RaiseListChangedEvents = false;

}

T Clone = businessObject.Clone();

Clone.ApplyEdit();

try

{

businessObject = Clone.Save();

businessObject.BeginEdit();

if (rebindDataSource)

{

if (bindingSource != null)

{

bindingSource.DataSource = null;

bindingSource.DataSource = businessObject;

}

}

}

catch (DataPortalException ex)

{

MessageBox.Show(ex.BusinessException.ToString(), "Error saving", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

}

catch (Exception ex)

{

MessageBox.Show(ex.ToString(), "Error Saving", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

}

finally

{

if (bindingSource != null)

{

bindingSource.RaiseListChangedEvents = true;

}

}

return businessObject;

}

public static void UndoRoot<T>(T businessObject) where T : BusinessBase<T>

{

businessObject.CancelEdit();

}

public static void DeleteRoot<T>(T businessObject) where T : BusinessBase<T>

{

try

{

businessObject.Delete();

businessObject.ApplyEdit();

businessObject.Save();

}

catch (DataPortalException ex)

{

MessageBox.Show(ex.BusinessException.ToString(), "Error deleting", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

}

catch (Exception ex)

{

MessageBox.Show(ex.ToString(), "Error deleting", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

}

}

public static T SaveRootList<T, C>(T businessObject)

where T : BusinessListBase<T, C>

where C : IEditableBusinessObject

{

return SaveRootList<T, C>(businessObject, null, false);

}

public static T SaveRootList<T, C>(T businessObject, BindingSource bindingSource, bool rebindDataSource)

where T : BusinessListBase<T, C>

where C : IEditableBusinessObject

{

if (bindingSource != null)

{

bindingSource.RaiseListChangedEvents = false;

}

T Clone = businessObject.Clone();

Clone.ApplyEdit();

try

{

businessObject = Clone.Save();

businessObject.BeginEdit();

if (rebindDataSource)

{

if (bindingSource != null)

{

bindingSource.DataSource = null;

bindingSource.DataSource = businessObject;

}

}

}

catch (DataPortalException ex)

{

MessageBox.Show(ex.BusinessException.ToString(), "Error saving", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

}

catch (Exception ex)

{

MessageBox.Show(ex.ToString(), "Error Saving", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

}

finally

{

if (bindingSource != null)

{

bindingSource.RaiseListChangedEvents = true;

}

}

return businessObject;

}

public static void UndoRootList<T, C>(T businessObject)

where T : BusinessListBase<T, C>

where C : IEditableBusinessObject

{

businessObject.CancelEdit();

businessObject.BeginEdit();

}

 

I would expect that this will port over correctly to the new framework, but my generic 'UndoRootList()' method will not need the supplementary 'BeginEdit()'... Am I overlooking something?

RockfordLhotka replied on Friday, September 28, 2007

I don’t have time to look through your code right now, so I’ll assume it is a correct abstraction of the 3.0.2 code from PTWin. But make sure it is 3.0.2 – because even 3.0.1 had bugs! This has been changing fast, and only 3.0.2 appears to be correct.

 

The challenge is where you have multiple bindingsource controls, because the order in which they are processed is critical, and you must differentiate between the root and any child bindingsource controls. Unfortunately, I didn’t see any way to ask the control whether it is a “root” or “child”, and I didn’t see any way to abstract this process, because only the author of a given form knows the relationships between the different controls…

 

Rocky

Copyright (c) Marimer LLC