I want to implement undo / redo functions in my application. I have
identified 20 or more user actions / transactions that could be un done / re done. Would you follow the general pattern in D6 itself and implement a Change Manager (RefactoryChangeManager) and classes for each of the change (RefactoryChange and subclasses). The RefactoryChange objects are essential GOF DP Command pattern afaict. Or would you base the implementation around the Command class in D6 - the class comment indicates that it is intended to implement undo / redo. My only hesitation is that it is very heavily linked to the commandQuery functions in D6 that it might be hard to keep distinct my actions that need undo support. Any suggestions on the approach? Thanks note: using D6 professional. |
Hi Edward,
> Or would you base the implementation around the Command class in D6 - > the class comment indicates that it is intended to implement undo / > redo. I have used the base implementation in a couple of apps and it did what I needed. I suspect that the behavior of RefactoryChangeManager could be implemented using the base system ... although having a global history across all topShells would require modifying your shell(s) to use a global commandHistory. > My only hesitation is that it is very heavily linked to the > commandQuery functions in D6 that it might be hard to keep distinct my > actions that need undo support. I don't think it is that heavily linked that if you felt you needed to modify it, you couldn't. Most of the behavior is delegated to your shell (see Shell>>addCommandHistory: , Shell>>clearCommandHistory) which you can modify if needed. You need to mark all commands that don't invalidate your undo history as #beBenign ... and it can be easy to miss commands that should be benign. Adding a temporary 'Sound beep' to Shell>>addCommandHistory: when a command is not marked as benign can help track them down. If you need more complex tests for deciding when to invalidate the command history, you can just implement your own #addCommandHistory: method. I also found that I needed to occasionally invalidate the command history from "outside" a command ... which I did with a 'self topShell clearCommandHistory'. One other technique I have used a bit, was to allow my model(s) to trigger an event with an "undo command", which my main shell would then add to its commandHistory. I would use this mainly in situations where say a command is performed in a Dialog, and I want it to be able to be undone by my main shell. One last thing ... I am looking through some of my code, and I always guard against 'Command current' being nil. I have a feeling I came across a problem caused by (from a distant memory) a command sending another command (via View>>onCommand:). I can't recall the details ... in any case, I personally always guard against 'Command current' being nil. Steve -- [hidden email] |
> I can't recall the details
My memory has returned :) ... accessing the current command via the 'Command current' global can be tricky. Take a look at Command>>value. This can unexpectedly set Command current to nil if you have "nested" commands. For example: MyPresenter>>changePerson | newPerson | newPerson := MyPersonDialog showModel. Command current undoWith: [self setPerson: self existingPerson]. self setPerson: newPerson In this example, if MyPersonDialog performs any commands, Command current will be nil when you are trying to set the #undoWith: block. A work around is to grab another reference to the Command current at the start of the method (and to be safe, check that it is what you expect). For example: MyPresenter>>changePerson | newPerson command| command := Command current. self assert: [command command == #changePerson]. newPerson := MyPersonDialog showModel. command undoWith: [self setPerson: self existingPerson]. self setPerson: newPerson Another workaround would be to change Command.Current to hold a stack of commands ... although that might introduce other problems. Steve -- [hidden email] |
Free forum by Nabble | Edit this page |