Undo / Redo advice

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Undo / Redo advice

Edward Stow
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.


Reply | Threaded
Open this post in threaded view
|

Re: Undo / Redo advice

Steve Alan Waring
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]


Reply | Threaded
Open this post in threaded view
|

Re: Undo / Redo advice

Steve Alan Waring
> 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]