Undo / redo problem


I have encountered a strange problem concerning the undo / redo operations, and I haven't managed to fix it myself. I was searching arround, but haven't managed to find anything helpful.
In my project I have a GoView for displaying the graphs, but I also have a special data structure representing the graph in the background for computing. I have a linked list with a bunch of instances of a special node type called tasks. Each task has a linked list with it's normal blocks (each block belongs to only one task). So it's basically a tree structure with two levels and a GoDocument as a root level. Now, if I delete some of these objects from a GoDocument, they are also removed from the structure. I can also add them there and move them arround.
The problem occures when I try to undo. Graphically they come back to the GoDocument, but they are not returned to the structure (of course, that's logical, I should implement custom stuff myself). How could I put them back to the right place on undo? Mind that there are two levels, and if a task with some of it's blocks is removed, there could be a case the blocks are returned before the task, so I wouldn't be able to attach them to anything (because the task is still missing from the structure). It seems to me some delayeds method should be used. Can anyone help, please?

So you have this two-level data structure in (or with) each GoDocument, but they are not GoObjects, and you want them to support undo/redo.

As an independent issue, this data structure reflects some of the GoObjects (nodes and links) in the GoDocument.

If I understood you correctly, you have already set up a GoDocument.Changed event handler (or overridden GoDocument.OnChanged) so that you can automatically update your data structures whenever any changes are made to the GoDocument, whether by user manipulation or by some code that modifies the document. And you have implemented your data structures so that if you make any direct changes to them, you also make any appropriate changes to (including creating and removing) the corresponding GoObjects in the document.

It sounds like the easiest thing is to integrate into the existing GoUndoManager by calling GoDocument.RaiseChanged with some new Hint values that you define, as you make changes to your data structures, including adding, removing, and modifying. You will also need to override GoDocument.ChangeValue to handle those new hints, so that when an undo or a redo occurs, you use the information in the GoChangedEventArgs to recover/restore the desired state.

In the samples there are several examples of implementing undo/redo managed state in subclasses of GoDocument. I think all of the examples just implement simple property values, not collections like what you have, but you can check.

The UpdateDemo sample just implements the two-way updating of the document and the internal data structures. I don’t think it implements additional Hints to allow state to be undo/redo managed independently of any GoObjects.

Oh, just to reinforce the message – GoDiagram’s undo/redo architecture is completely extensible, and I believe GoDiagram uses this mechanism to implement undo/redo in exactly the same manner that you can for your own classes and state.

In other words, I believe our internal support for undo/redo only uses the publically accessible mechanisms of GoUndoManager and IGoUndoableEdit.

But you too can make use of the predefined mechanisms that GoDocument and GoChangedEventArgs provide, just to make your programming easier. That’s what I’m recommending in my previous post.

Hello again.

After some time I have returned to the project. I understood pretty clearly now what I have to do. But when I started doing it, I came to a new problem.
I assigned a new hint and called RaiseChanged. Then I overrode the ChangeValue, so I could add support for the recovering. I planned to do different hints for normal objects (blocks) and different hints for special objects (tasks). So I first started to deal with blocks. Each block has assigned task. So I thaught that I can simply use "block.Task.Add(block);" function. That actually works. When this is executed, the block is returned in the structure, it becomes a part of the document ("isInDocument=true") and is visible. But when I delete it again (so the process is: insert block -> delete it -> undo -> delete it again), it is not a part of a document and block's task (correctly) but it's appearance still exists on the document (visually is not removed). And when I click on it, I get an exceptions "Selected objects must belong to the view or its document" and "Cannot access a disposed object. Object name: 'Form1'."(if I click "resume" after the first exception is displayed). I have a feeling I'm doing something terribly wrong, but I have tried some other approaches that I could think of, and everything resulted in same behaviour. If you need, I can post additional code (there's a lot of it) or exception details.
There's maybe one more important thing to state. I haven't used OnChanged or Changed to deal with the document changes, because at the time of programming that part I wasn't really fammiliar with Northwoods Go. So the best thing at that time seemed OnLayerChanged methods. I used those to deal with the insertions or deletions of the objects. I don't know if this is important for this issue. Can you please elaborate on this?

Your block and task objects are not GoObjects, right? They are your own independent classes that are associated with (belong to?) a GoDocument?

(If they were GoObjects, and in particular if task were a GoGroup, then you wouldn’t need to do anything to get undo/redo support.)

I don’t understand why your Form1 is being Dispose’d.

GoObject.OnLayerChanged is always called when the GoObject.Layer property changes. GoDocument.RaiseChanged is also called when the GoObject.Layer changes. The order in which they are called depends on whether it’s being added to a layer or being removed from it. (Also, GoDocument.Changed event handlers are not called when GoDocument.SuspendsUpdates is true, but that should never be the case for you.)

Actually, those are the nodes extending GoMultiTextNode. And, yes, they belong to a document, like in it’s layers, but also as a custom structure for easier programming and understanding the code. But I still do need to fix the undo / redo support, because the only thing supported is getting them back to the layers / document. Upon undo, they do become a part of a GoDocument and visible, but they don’t become a part of the structure they were deleted from. Maybe the reason for that is in the fact that I have used GoMultiTextNode’s OnLayerChanged instead of GoDocument’s OnChanged?

EDIT: It seems switching the code from OnLayerChanged to document's OnChanged without properly specifying ChangeValue does not help me at all (at least for now). I think I have to specify ChangeValue correctly, but I still don't know how.

Well, undo/redo of adding and removing of GoObject from GoGroups is already supported by GoDiagram, so you don’t need to do anything there.

Are you saying you have additional collections that you need to maintain? Like an extra list in each Task, that holds some number of Blocks?

If so, then each time you add a Block to a Task you call RaiseChanged with your new Hint value, passing the needed information such as the Task, the added Block, and maybe the index of the Block in the Task’s list (if that matters). Same goes for removing a Block from a Task. Note that the reason there are so many arguments to GoDocument.RaiseChanged, and also that there are so many properties in GoChangedEventArgs, is so that you can easily pass all the information you need to be able to undo or to redo that modification.

In any case it doesn’t sound like you care about whether a Block is added to or removed from a layer, but whether it is added to or removed from a Task. So overriding OnLayerChanged is probably the wrong event to check for.