Undo doesn't work in sample application for the resources collection if you don't change something at the project.

Undo doesn't work in sample application for the resources collection if you don't change something at the project.

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


alef posted on Tuesday, August 21, 2007

Hi,

Try the following:

Open a project.

Change a role in the resources grid.

Click cancel and you see that the modifications aren't rolled back.

I've found that the undo will work for the resources if we do also a change on the root object (project).

I'm using the version 2.1.4.

 

Alef

 

 

 

 

 

jhw replied on Tuesday, August 21, 2007

This is what I do in the case of child collections:

Add to collection:

Create a property in the parent object to add the item so that beginEdit can be called in the parent. This property simply calls the child collection add property. If I just directly call the child collection add, then begin edit cannot be called for the parent.

 

Remove from collection

Same as add except that it does the remove.

 

Update a collection:

Does not work for me so I only use undo for add remove operations. If a person did a remove/add operation then perhaps that would work.

alef replied on Wednesday, August 22, 2007

Thank you for describing the work-around for adding and removing items to the collection.

I've investigated the problem a little bit deeper.

What I've discovered is that the first item in the resources collection is just by opening the form already on level 2. The root object is still on level 1.  So what I don't understand is that at some way BeginEdit is called on the first object in the child collection without doing this through the root object.

Apparently I was wrong with saying you have to edit the root project. I've discovered the following :

1) the following situation doesn't work

Edit the role for the first resource.

Click on Cancel.

2) the following situation works

Edit the role for the first resource.

Click on whatever column on the second resource.

Click on Cancel

 

RockfordLhotka replied on Friday, August 24, 2007

Yes, there are some issues with the 2.1.4 code - both in BusinessListBase, but more importantly in the data binding code in PTWin.

Look at the PTWin code for 3.0.1 and use the techniques shown there to do your data binding and that should help a lot.

There are some bug fixes in BusinessListBase in 3.0.1 as well, though I don't think they affect what you are seeing here.

The key, and what I really didn't fully appreciate until recently, is that there's a rule you simply can't break when using data binding:

Never interact with the business object directly while it is data bound. Instead, always interact with the bindingsource object.

This means that things like canceling or saving require totally unbinding the object from the bindingsource objects and then (if the form stays open) rebinding.

Ugly and sad, but true.

RockfordLhotka replied on Friday, August 24, 2007

Now that I think about it, the 3.0.2 test code in PTWin may be better. I’m still refining some of that code as I write the 3.0 ebook, and so (right now) the best source is the C# code in svn.

 

Rocky

 

From: RockfordLhotka [mailto:cslanet@lhotka.net]
Sent: Friday, August 24, 2007 12:11 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] Undo doesn't work in sample application for the resources collection if you don't change something at the project.

 

Yes, there are some issues with the 2.1.4 code - both in BusinessListBase, but more importantly in the data binding code in PTWin.

Look at the PTWin code for 3.0.1 and use the techniques shown there to do your data binding and that should help a lot.

There are some bug fixes in BusinessListBase in 3.0.1 as well, though I don't think they affect what you are seeing here.

The key, and what I really didn't fully appreciate until recently, is that there's a rule you simply can't break when using data binding:

Never interact with the business object directly while it is data bound. Instead, always interact with the bindingsource object.

This means that things like canceling or saving require totally unbinding the object from the bindingsource objects and then (if the form stays open) rebinding.

Ugly and sad, but true.



alef replied on Sunday, August 26, 2007

I've tested your version 3.0.2 and the particular case of Undo that I was describing is working with this new version.

But the following case is not working in this new version 3.0.2 :

1) Open project "Expert C# 2005 Business Objects"

2) Change the role of the first resource to "Sponsor"

3) Change the role of the second resource to "Sponsor"

4) Click with the mouse on whatever cell on the first resource (the grid will fire some events which will disturb the undo level)

5) Click on Cancel. You'll see nothing is rolled back.

 

Also the following case is not working

1) Open project "Expert C# 2005 Business Objects"

2) Change the role of the first resource to "Sponsor"

3) Click with the mouse on whatever cell on the second resource (the grid will fire some events which will disturb the undo level)

4) Click on Cancel. You'll see nothing is rolled back.

 

When I change the Cancel_Button_Click event in the PTWin application version 2.1.4 everything is solved:

private void Cancel_Button_Click(object sender, EventArgs e)

{

   projectBindingSource.RaiseListChangedEvents = false;

   resourcesBindingSource.RaiseListChangedEvents = false;

   this.resourcesBindingSource.CancelEdit();

   this.projectBindingSource.CancelEdit();

   projectBindingSource.RaiseListChangedEvents = true;

   resourcesBindingSource.RaiseListChangedEvents = true;

   projectBindingSource.ResetBindings(false);

}

Maybe can you consider this for your 3.0 ebook. The GUI comes to be more complex. I should prefer only to cancel the root object, but this is not working. Also take note that you need first to cancel the resourcesBindingSource and then projectBindingSource, otherwise it will not work.

I'm also obeying to your rule : Never interact with the business object directly while it is data bound. Instead, always interact with the bindingsource object.

But totally unbinding is apparently not necessary, just disabling the events while canceling is enough.

I don't know really 100% the why of this, but I've come to this solution by trying all the possible combinations. Maybe it is for you easier to understand why this is working?

Alef

 

RockfordLhotka replied on Monday, August 27, 2007

When looking at 3.0.2 it is important to use both the new CSLA and the new code in ProjectTracker (PTWin), because there are substantial changes in both.

 

And please realize that the code in PTWin is not the latest – the latest code that properly unbinds for Save/Apply is only in svn at the moment. But the Cancel/Close code in PTWin should correctly unbind the objects.

 

Your code is actually not sufficient. It is even harder than that to get it totally right – check the 3.0.2 PTWin code to see what I mean.

 

Rocky

 

 

From: alef [mailto:cslanet@lhotka.net]
Sent: Sunday, August 26, 2007 5:00 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: Undo doesn't work in sample application for the resources collection if you don't change something at the project.

 

I've tested your version 3.0.2 and the particular case of Undo that I was describing is working with this new version.

But the following case is not working in this new version 3.0.2 :

1) Open project "Expert C# 2005 Business Objects"

2) Change the role of the first resource to "Sponsor"

3) Change the role of the second resource to "Sponsor"

4) Click with the mouse on whatever cell on the first resource (the grid will fire some events which will disturb the undo level)

5) Click on Cancel. You'll see nothing is rolled back.

 

Also the following case is not working

1) Open project "Expert C# 2005 Business Objects"

2) Change the role of the first resource to "Sponsor"

3) Click with the mouse on whatever cell on the second resource (the grid will fire some events which will disturb the undo level)

4) Click on Cancel. You'll see nothing is rolled back.

 

When I change the Cancel_Button_Click event in the PTWin application version 2.1.4 everything is solved:

private void Cancel_Button_Click(object sender, EventArgs e)

{

   projectBindingSource.RaiseListChangedEvents = false;

   resourcesBindingSource.RaiseListChangedEvents = false;

   this.resourcesBindingSource.CancelEdit();

   this.projectBindingSource.CancelEdit();

   projectBindingSource.RaiseListChangedEvents = true;

   resourcesBindingSource.RaiseListChangedEvents = true;

   projectBindingSource.ResetBindings(false);

}

Maybe can you consider this for your 3.0 ebook. The GUI comes to be more complex. I should prefer only to cancel the root object, but this is not working. Also take note that you need first to cancel the resourcesBindingSource and then projectBindingSource, otherwise it will not work.

I'm also obeying to your rule : Never interact with the business object directly while it is data bound. Instead, always interact with the bindingsource object.

But totally unbinding is apparently not necessary, just disabling the events while canceling is enough.

I don't know really 100% the why of this, but I've come to this solution by trying all the possible combinations. Maybe it is for you easier to understand why this is working?

Alef

 



alef replied on Monday, August 27, 2007

I've downloaded version 3.0.2 (test 2), so I am really testing both the new CSLA code and the new code in ProjectTracker (PTWin). I've verified also sub version and downloaded the latest file of ProjectEdit.cs but there is only a change in the SaveProject method, not in the Cancel_Button_Click.

Do I need to look also for new files in the CSLA framework? I've looked at BusinessBase.cs and UndoableBase.cs but they are the same.

RockfordLhotka replied on Monday, August 27, 2007

Hmm. The reason I'm asking is because you aren't calling the UnbindBindingSource() helper method - but rather are simply calling CancelEdit() on the bindingsources. I know for a fact that doesn't work, because I started there.

The problem is that immediately upon calling CancelEdit() the bindingsource will also call BeginEdit() on your object - leaving the object still at edit level 1. Things get kind of messed up due to that.

This is why I created that UnbindBindingSource() helper you can see in the 3.0.2 PTWin code.

alef replied on Monday, August 27, 2007

Oh, I now understand you that you are asking if I'm really using 3.0.2 PTWin code.

When I discovered the misbehavior in version 2.1.4, I posted this message and you suggested to look at your new code. I did that but I found out that the code 3.0.2 was not solving the undo. So I went back to the version 2.1.4 and started to find a solution in this version 2.1.4 and this is the reason you are not seeing UnbindBindingSource() helper method.

So I think your new version 3.0.2 is not yet completely solving the undo.

The solution I describe is really working in 2.1.4. I don't know what is happening with the EditLevel (it is not so easy to debug this), but anyway the UI is behaving correctly.

Is it possible for you to test the cases I've described in your code 3.0.2 and you'll see that is not working for these cases.

I hope that the solution I provided is really a good one, because I want to keep the UI as simple as possible, and I find even my soluton is too much code. Ideally we should just call CancelEdit() on the root object, we should try to reach this target.

PederSvaleng replied on Tuesday, August 28, 2007

I've been following this thread because we have some problems with n-level undo when using 3.0.2 version.

In project tracker I've tried both cslacs-3.0.1-070723 and cslacs-3.0.2-070809 and the undo mechanism is only working in the 3.0.1 version. In the 3.0.2 it behaves strange. Just thought I'd let you know :)

It's difficult for me to try the 3.0.1 version in our program because of some exceptions problems in 3.0.1. You fixed it 3.0.2 so I'm very happy about that.

--

Peder

 

 

 

 

RockfordLhotka replied on Tuesday, August 28, 2007

Ha!

This is only semi-relevant, but I'm being pulling in several directions at once just now - work, family, ebook, etc. So 3.0.2 is, of course, a work in progress, and not all was checked in.

So if you look at the svn code NOW you should find code that works (C#).

Make sure to get the updated WinPart along with ProjectEdit and ResourceEdit.

The issue is this (relatively major) change in how IEditableObject is handled that makes IEO only ever do 1-level undo - just like a DataTable. This appears to be the only way to get data binding to really be happy, because that's what it expects from IEO.

BUT we want a form-level Cancel button too, which requires n-level undo.

Yet that rule "never touch the object while it is data bound" remains true.

So if you look at the code you'll see BeginEdit() called before binding. And CancelEdit() or ApplyEdit() after binding (and before rebinding if that occurs).

When I get some time I'll get the VB updated as well and refresh the 3.0.2 download, but that might be next week the way things are going just at the moment.

RockfordLhotka replied on Tuesday, August 28, 2007

OK, I got a refresh of 3.0.2 up - both VB and C# - with the (I believe) working PTWin code.

PederSvaleng replied on Wednesday, August 29, 2007

That was fast! And now it works :) Thank you VERY much Rocky! PTWIN is now working as expected, and so is our program.

Big Smile [:D]

PederSvaleng replied on Wednesday, August 29, 2007

Csla.Core.UndoException: Edit level mismatch in CopyState
   at Csla.Core.UndoableBase.CopyState(Int32 parentEditLevel)
   at Csla.Core.UndoableBase.Csla.Core.IUndoableObject.CopyState(Int32 parentEditLevel)
   at Csla.Core.UndoableBase.CopyState(Int32 parentEditLevel)
   at Csla.Core.UndoableBase.Csla.Core.IUndoableObject.CopyState(Int32 parentEditLevel)
   at Csla.BusinessListBase`2.CopyState(Int32 parentEditLevel)
   at Csla.BusinessListBase`2.Csla.Core.IUndoableObject.CopyState(Int32 parentEditLevel)
   at Csla.Core.UndoableBase.CopyState(Int32 parentEditLevel)
   at Csla.Core.UndoableBase.Csla.Core.IUndoableObject.CopyState(Int32 parentEditLevel)
   at Csla.Core.UndoableBase.CopyState(Int32 parentEditLevel)
   at Csla.Core.BusinessBase.BeginEdit()

I was probably to fast.. When saving I'm getting this exception in my program. I thing that this worked in cslacs-3.0.2-070809. But this is way beyond my knowledge..

Another question, and it's bit offtopic just to make sure I'm not doing anything wrong. When sending a business object to a dialog form, do I have to what I'm doing under? Or can I skip: turning of RaiseListChangedEvents, UnbindingSource, RebindUI? Or is it just a matter of taste?

private void OnSelectCareProvision( )
{
   
try
   
{
         PatientOfOtherProvider pat = (PatientOfOtherProvider)bsPatientOfOtherProviderList.Current;
         
DlgPatientCareProvision dlg = new DlgPatientCareProvision( );       //Form

         this.bsPatientSwitch.RaiseListChangedEvents = false; //root
         
this.bsPatientOfOtherProviderList.RaiseListChangedEvents = false; //child list
         
this.bsEmploymentList.RaiseListChangedEvents = false; //child list
         UnbindBindingSource(
this.bsPatientOfOtherProviderList, false, false );
         UnbindBindingSource(
this.bsEmploymentList, false, false );
         UnbindBindingSource(
this.bsPatientSwitch, false, true );

         dlg.PatientOfOtherProvider = pat;
         
 DialogResult result = dlg.ShowDialog( );
         
switch ( result )
         {
            
case DialogResult.OK:
            
RebindUI( true, true );
      
}
}

I thought I just could do this:

private void OnSelectCareProvision( )
{
   
try
   
{
         PatientOfOtherProvider pat = (PatientOfOtherProvider)bsPatientOfOtherProviderList.Current;
         
DlgPatientCareProvision dlg = new DlgPatientCareProvision( );  //Form             
         dlg.PatientOfOtherProvider = pat;
         
 DialogResult result = dlg.ShowDialog( );
         
switch ( result )
         {
            
case DialogResult.OK:
            ///////
RebindUI( true, true );
            //Do nothing here..

      }
}

 

I've been trying different things here, but I've always get exception when saving my root business object (which worked before);

 

 

PederSvaleng replied on Wednesday, August 29, 2007

private void BindUI( )
{
   aPatient.BeginEdit( );
   
this.bsPatientSwitch.DataSource = aPatient;
}

aPatient.BeginEdit() gives the exception. But I guess you already knew :)

RockfordLhotka replied on Wednesday, August 29, 2007

PederSvaleng:

Another question, and it's bit offtopic just to make sure I'm not doing anything wrong. When sending a business object to a dialog form, do I have to what I'm doing under? Or can I skip: turning of RaiseListChangedEvents, UnbindingSource, RebindUI? Or is it just a matter of taste?

No, I don't think any of this is optional. Every time I think there's a shortcut, it turns out to cause problems. So as far as I can tell, the sequence (and order counts!) of the code in the latest 3.0.2 PTWin is correct.

If you find another reliable solution please let us know!

In general:

  1. Before binding, call BeginEdit() on the root
  2. To cancel, unbind all objects (as shown in PTWin), then call CancelEdit() on the root
  3. To save, unbind all objects (as shown in PTWin), then call ApplyEdit() on the root and save it

Unbinding is a multi-step process, and seems harder than it should be, but it is what it is.

ajj3085 replied on Wednesday, August 29, 2007

Rocky,

Is it always necessary to call BeginEdit before binding?  Lets suppose that I don't have an undo operation that the users can initiate.

I ask this because my users found an odd error.

Basically, if the users change the quote they are working on by selecting a new item in a drop down list, then click on Save on my toolbar, they get a ValidationException:  Object cannot be saved while edited.

If they click off the dropdown and click Save, it works fine.    Is this related?


EDIT:  This got a bit weirder; Save doesn't exhibit the problem if I change the controls to update on propertychange, but another toolbar button (Lock) which calls a method on the object, then attempts to save, does. 

RockfordLhotka replied on Wednesday, August 29, 2007

You only need to call the explicit BeginEdit(), CancelEdit() and ApplyEdit() on the unbound object if you have a form-level Cancel button.

If you don't have a form-level Cancel button, then the user has no way to request a complete undo. The most they could do is press ESC in a grid row to request that row be undone - and that's automatic with data binding.

You do still need to unbind the object before saving it however. That is inescapable to get reliable results, otherwise at least one object will be at an elevated edit level when Save() is called...

But please be aware that 3.0.2 includes a change to the IEditableObject interface works to bring its behavior in line with the DataTable (as data binding expects), and so what works in 3.0.2 won't work prior to that point. In fact, I am not sure anything can work reliably prior to 3.0.2 if you have parent-child data and in-place grid editing.

RockfordLhotka replied on Wednesday, August 29, 2007

ajj3085:
Rocky,
Basically, if the users change the quote they are working on by selecting a new item in a drop down list, then click on Save on my toolbar, they get a ValidationException:  Object cannot be saved while edited.

If they click off the dropdown and click Save, it works fine.    Is this related?

Oh, I just noticed that the button is on a toolbar. I don't think those buttons (by default) take focus, and that could be your problem. If they don't take focus, it is possible that the last control (especially complex controls like a dropdown) might not complete data binding updates before the click event runs.

Isn't there an option on a toolbar button to make it take focus though?

ajj3085 replied on Wednesday, August 29, 2007

Rocky,

Thanks for the replies.  I'll have to check out the new PT example, because I don't unbind the object before calling save.  I DO turn off all RaiseListChangedEvents, and call EndEdit on all the binding sources.

I've found though that calling EndEdit on the Document's binding source causes a BeginEdit followed by a property set (for some reason the combo doesn't do this even though its set to update on PropertyChange), so the Edit is ended than started again somehow (BeginEdit ignores the call because BindingEdit is true).  More frustrating is that the combobox already set the value previously.

The result is that the root objects EditLevel remains at 1 even after calling EndEdit.  Argh.

It may be the focus causing this problem, I'll have to investigate.  I'm not sure if the toolbar has an option to make it focus though; its the UltraToolbarsManager from Infragistics, not the standard toolbar.

Thanks for the tips.  I'll post back with my findings.

RockfordLhotka replied on Wednesday, August 29, 2007

Yes, the issue with just calling EndEdit() is that the bindingsource immediately calls BeginEdit() again.

 

So the only way to really get to edit level 0 for all objects is to unbind.

 

Rocky

ajj3085 replied on Wednesday, August 29, 2007

Hmm, I didn't think this was the case if RaiseEvents was off..

Its odd that this ususally works, and its only combobox + toolbar that leads to the issue.

ajj3085 replied on Wednesday, August 29, 2007

So it seems the new model is to set RaiseEvents to false, then simply unbind the BindingSource controls instead of just calling EndEdit on all of them? 

I changed the code so that it follows this same pattern, and it seems to work.

RockfordLhotka replied on Wednesday, August 29, 2007

Yes, that’s correct. Though “simply unbind” is a bit misleading, since you do have to find the specific current object, then disconnect binding, then end the edit by calling IEditableObject directly on the object.

 

And you must unbind children first, then the root, but you can’t reset the child bindingsource.DataSource until after you’ve cleared the root – it is a delicate sequence of calls L

 

OK, maybe it isn’t quite as delicate as I think – but after messing with this as much as I have, these are my conclusions :)  It is like magic.

 

Hermione comes to mind: “It isn’t alo-ha-more-AH, its alo-ha-MORE-ah.”

 

Rocky

 

From: ajj3085 [mailto:cslanet@lhotka.net]
Sent: Wednesday, August 29, 2007 10:33 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: Undo doesn't work in sample application for the resources collection if you don't change something at the project.

 

So it seems the new model is to set RaiseEvents to false, then simply unbind the BindingSource controls instead of just calling EndEdit on all of them? 

I changed the code so that it follows this same pattern, and it seems to work.


PederSvaleng replied on Thursday, August 30, 2007

Now thing are getting a bit more stable here. It's just one little thing that happens when I save the root object..

PatientSwitch temp = aPatient.Clone( );
aPatient = temp.Save(); // This goes good.

aPatient.BeginEdit( ); // This gives and copy state exception.
this.bsPatientSwitch.DataSource = aPatient;

So my workaround is

PatientSwitch temp = aPatient.Clone( );
PatientSwitch temp2 = temp.Save( );
aPatient =
PatientSwitch.GetPatientSwitch( temp.RoleId ); 

aPatient.BeginEdit( ); // This works 
this.bsPatientSwitch.DataSource = aPatient;

It seems that there is a wrong edit level in one of the child objects in the aPatient's object graph. After the saving. I don't now what my question really is but I just thought I'd let you know. Maybe I'm doing something wrong.. :) (as usual).

 

 

ajj3085 replied on Thursday, August 30, 2007

Before you clone the aPatient instance, you should call ApplyEdit.

Before you do that, you need to se the bsPatientSwitch.DataSource = null.

ajj3085 replied on Thursday, August 30, 2007

Indeed, its amazing how much databinding I've been able to learn from your explorations in this.  I don't think I'd have figured it out myself.

It seems that setting the root's BS.Datasource to null works; the other BindingSource's datasources are the root binding source, so setting the root seems to unbind them all.

PederSvaleng replied on Thursday, August 30, 2007

Thanks you. Of course you are right. My mistake. I tried to what you said, but it didn't work. you mean like this? 

this.bsPatientSwitch.DataSource = null;
this.aPatient.ApplyEdit( );
try
{
PatientSwitch temp = aPatient.Clone( );
//PatientSwitch temp2 = temp.Save( );
//aPatient = PatientSwitch.GetPatientSwitch( temp.RoleId );
aPatient = temp.Save( );
}

One other thing. Our graph is indeed hugh. What happens (with editlevels) if you have a root object inside the graph of another root object? Will this cause problems. Another thing that made problems is this way of doing a selection in a list:

private void OnSelectEmployment( )
{
try
{
   
Employee aEmp = (Employee)bsEmploymentList.Current;

Correct me if I'm wrong but this call seems to raise the edit level of aEmp. Scary?????

Rocky doesn't to it this way, but I thoght it would be legal to do what I do. Since editlevel is hard for me to work out, I got confused using this statement.

 

 

 

ajj3085 replied on Thursday, August 30, 2007

Hmm, I don't think its supported to have a root containing another root, unless the contained root object is a switchable object. 

The code you added was all I needed to get working again, but you may want to try what Rocky suggests, which is to unbind everything, etc. 

It seems you have a list of employees which you then select one of for editing; that's unusual, normally you'd have a list of EmployeeInfo objects (which are readonly) and use that for selection and display only.  Then when you need to edit you'd load the full blown editable object.   If you switch to that model, it may help get things  under control.  Just my two cents though.

PederSvaleng replied on Thursday, August 30, 2007

I agree. I've learned big big time from this thread! I do as Rocky suggests, which is to unbind everything, I just didn't show it :). The root object is a swithable object but it behaves as a root.

I see your point when selecting the employee, it's just that our model is like that :)

renegrin replied on Tuesday, September 04, 2007

Excellent thread.

 

PederSvaleng replied on Wednesday, September 05, 2007

Just would like to say that everything runs very smoothly now, after reading this thread. I'm now able to pass the object from an editable child collection to a modal dialog, and get it back (undo works fine). Life is good! But when I try to remove an item from my list in my main form, things are just as good. 

I have one datasource that are bound to one root object (aPatient).

if ( dgEmploymentList.SelectedRows.Count > 0 )
{
   
int pos = bsEmploymentList.Position;
   
this.aPatient.PersonPlayer.EmploymentList[pos].Organization.ApplyEdit( ); //workaround
   
this.aPatient.PersonPlayer.EmploymentList.RemoveAt( pos );
}
...
RebindUI(false,true).

As you can se from my code I manually call applyEdit() to the Organization object to get it's editlevel down to 0. If I don't, then the edit level in Organization is 1 when a call aPatient.CancelEdit() and then Exception!

Anybody knows what I'm doing wrong? The other objects in the list has editlevel 0.

Copyright (c) Marimer LLC