Introduction

This article presents a simple way to add undo/redo support (http://en.wikipedia.org/wiki/Undo) to your applications. The framework is able to handle various scenarios. However, I am not claiming that it is able to handle all possible scenarios, nor that it is the smartest framework.

I have used Microsoft Visual C++ and Windows XP Professional. The framework can easily be ported to other operating systems.

How to

The framework is based on a simple concept: action (command) objects. An action, or command object encapsulates a user request. Actions have numerous advantages over ad-hoc handling of user requests (http://en.wikipedia.org/wiki/Command_pattern).

Using the Framework

Setting up the Framework

Unpack the KisWinBin.zip file. The default path I am using is d:\. This is also the path used by the demo application.

Prepare your project:

Add the kis path to your project.

Add the kis.lib library to your project.

Add the kis.dll library to your project output directory.

Add the #include “kis.h” directive to your STDAFX.H file.

Add an action executor member to your document class. All actions are executed through an action executor. A default executor is provided. You may write your own executors in a way that takes advantage of specific cases.

Questions?

How do I change history size?

The history size is managed by two methods: C_ActionExecutor::GetMaxBytesCount and C_ActionExecutor::SetMaxBytesCount. Rate of consumption of history space depends on how large the action objects are. The size of an action object is given by C_Action::GetBytesCount.

How large is an action object?

It depends. The method C_Action::GetBytesCount responds to C_ActionExecutor’s question “How much memory did you consume?”. Your derived C_Action object should never answer “zero”, because the size of your action is sizeof(C_Action), at least. The more accurate the answer is, the better the estimation of history usage is.

How do I disable/enable the history memory?

If the size of history memory is set to zero, then the history is disabled. Setting the size to a value greater than zero enables it.

How do I list the history?

You may iterate through history using C_ActionExecutor’s methods GetUnexecuteCount, GetReexecuteCount, GetUnexecuteName, GetReexecuteName.

Why can’t I directly use the action object?

The short answer is the framework is not supposed to work that way. [There is a joke (real story?) about an IQ test run on a group of cops. The test consisted of a board with oddly shaped holes and corresponding pegs which should be fitted in the holes. The result of the test was 1% of the cops were very smart and 99%... very strong.]

Let us say Execute, Unexecute and Reexecute methods are public. Some clients would be tempted to write code like this:

Seems fine until you realize the client forgot to save the executed action into history. Did he forget? (This would be really embarrassing if the product was already delivered.) Did he really intend to locally execute the action? It is hard to tell.

Hiding action’s Execute, Unexecute and Reexecute methods makes the framework less error prone. Let us say that you know nothing about the framework and you want to add a Clear command handler in an already existing project. First you will try to directly call the Execute method. You will soon realize Execute is protected. "Why? How can I execute the action?" You discover that you need an action executor. "Why do I need it?... Aaa, undo/redo!"

It is true it may be necessary to execute some actions with no need to undo them. This is a rare case, in my opinion, and it may be easily solved using a local action executor. Using a local action executor clearly states developer’s intention to locally use the action.

How can I locally execute one or more actions?

Since the execute method is not public, you cannot directly execute an action. In order to execute it, you first need to create a local action executor and then call the executor’s execute method.

I can't see any friend C_ActionExecutor; declaration inside C_Action. Nor is C_ActionExecutor a derived class of C_Action (which would certainly be strange, but would be another way to get things done). So how on earth does C_ActionExecutor_Default::Execute() call the protected method C_Action::Execute()? I can't even see how this compiles! Please enlighten me!

Maybe I'm nitpicking, but your code now violates the One Definition Rule and may produce undefined behaviour -- technically, the compiler is allowed to produce a different memory layout for the two versions of C_Action (although in this case I can't think why it would in practice).

Could you please change this to just use friend instead? That's what it's for! I know it's a bit of a pain because friendship isn't transitive, so you'd need to reorganise things a little bit. Here is one way to do it:

1. Introduce a new class, C_ActionProxy. This is just a simple class that holds a pointer to a C_Action object. It contains only a private single-argument constructor and public methods that forward to each of the protected methods in C_Action. Grant friendship to C_ActionExecutor from this class.
2. Grant friendship to C_ActionProxy from C_Action.
3. Add a non-virtual protected method proxy() to C_ActionExecutor that takes a single pointer to C_Action and returns a C_ActionProxy object initialised with that pointer. This method can be inline. In fact it can even be static.
4. In the code for C_ActionExecutor_Default, replace all calls to methods of C_Action (e.g. spAction->Execute() with calls to the forwarding functions in C_ActionProxy (e.g. proxy(spAction)->Execute()).

This design maintains the desirable property that the only way to call the protected methods of C_Action is to derive a class from C_ActionExecutor.

I do not see any problem with the access specifier trick (I admit this is not orthodox coding, though). I do not have the entirely text regarding the ODR but I am pretty sure the declaration trick does not violates the ODR. I doubt the protected/public specifier changes the class signature.

Adding additional classes will make the framework more complicated, which I was hardly trying to avoid while adding as much functionality as possible. Please note that C_Action is also used by C_StackOfSPAction.

Maybe there is a cleaner, smarter solution but I do not see it right now. Please feel free to change the code the way you like it. Please let me know if you reach a more acceptable solution.

Thank you for reading my code,
ZDF What is good is twice as good if it's simple.

I work mostly in VB.net, so I'm not sure how I'd adapt something for C++, but I am interested in how to handle a good undo/redo framework. One of my big concerns in implementing such a thing is ensuring that undo/redo functions actually behave sensibly. I have a couple programs I've bought which I 'almost' really like, but the "undo" doesn't consistently return to the state before the last action and the "redo" doesn't consistently return to the state before the "undo". The only sensible way to use the application is to consistently save different versions of the document, so that if I find something got 'oopsed' I can hopefully go back and see what happened and then by hand fix the necessary parts of the new document to match what they should have been (as shown in the old one).

The wrong behavior of the applications you are referring may be for a number of reasons; most of them derived form ad-hoc handling of undo/redo. The most likely one is someone forgot to push the document’s state into the undo/redo stack. From the user point of view”undo” will step back two steps instead of one, and redo will never redo both steps. I have tried to protect the user against this kind of mistakes by hiding action’s execute method (see the “Questions?” section): the action is executed through the action-executor (the undo/redo stack). The executor knows which methods to call and in which order.

I do not know which solution is the best. I can only recommend you to try my solution and see if it suits you.

Your project defaults have a few hardcoded values that make it difficult to download and build without first reconfiguring.

just an fyi

I was also able to get the undo to stop working. I drew about 7 lines, a rectangle, an ellipse, then hit clear. After a few undo's (undo clear, undo elliplse, no more undo) it stopped working. It looks like there's an undo limitation? Draw lots and lots of line and start doing undo until it says NO MORE yet there's still lots of lines left.

Great work - so far as I could see in just a short time.
But why are you using a "gome grown" smart ptr.
I know how std::auto_ptr is not usable for the case, but why are you refusing to use the shared ptr[^] from boost.org[^]?

Personally, I would trust this code more than any code I find elsewhere. After all, there is a rigorous perr-review scheme in place.

But nonetheless, your article seems to be great.

"We trained hard, but it seemed that every time we were beginning to form up into teams we would be reorganised. I was to learn later in life that we tend to meet any new situation by reorganising: and a wonderful method it can be for creating the illusion of progress, while producing confusion, inefficiency and demoralisation."

Although you provide a link to the Command Pattern on wikipedia, the text doesn't mention the Command Pattern at all. You should state if you used the Command Pattern as the basis for your code or not. That allows an experienced software developer to immediately get a conceptual understanding of your design, making the details much easier to grasp.

I think you are right: this pattern is usually known as command pattern, not action pattern. I was trying to avoid the command word because of the possible confusion with MFC terms like “Command Routing”, “Command Targets”, etc.