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
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.
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
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.
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.
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.
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
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
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.
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.
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.
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
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.
That was fast! And now it works :) Thank you VERY much Rocky! PTWIN is now working as expected, and so is our program.
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);
aPatient.BeginEdit() gives the exception. But I guess you already knew :)
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:
Unbinding is a multi-step process, and seems harder than it should be, but it is what it is.
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.
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?
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
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.
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.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).
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;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( )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.
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 :)
Excellent thread.
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