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.
Basic C++ & STL understanding is required. To understand the demo application, basic MFC understanding is also required.
I have used Microsoft Visual C++ and Windows XP Professional. The framework can easily be ported to other operating systems.
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).
In the Box
The framework is enclosed in
kis namespace (http://en.wikipedia.org/wiki/KISS_Principle) and contains the following:
- An action interface,
- An action executor interface,
- A default action executor creator,
- A smart pointer template,
Using the Framework
To use the framework, you must follow three steps:
- Set up the framework.
- Write the actions.
- Use the actions.
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.
m_spActionExecutor( kis::CreateActionExecutor_Default() )
- An action class is a class derived from
C_Action is fully documented in the header file (kis_action.h) so you should be able to write actions with no help.
- Ideally, you should write action classes for every user request in your application; it does not matter whether the request should or should not be undo-able.
- Use exceptions to let the client know something went wrong.
- Make sure the execution is atomic in the sense that it never leaves the target in an invalid state.
- Use action creators to create action objects; do not expose the derived action class.
SP_Action CreateAction_Clear ( C_Document* a_pTarget );
class C_Action_Clear : public C_Action
C_Action_Clear ( C_Document* a_pTarget )
SP_Action CreateAction_Clear ( C_Document* a_pTarget )
return SP_Action( new C_Action_Clear( a_pTarget ) );
Actions cannot be directly executed. An action object protects most of its methods. To execute an action, you need an action executor object.
- Executing an action:
kis::SP_Action spAction = CreateAction_Clear( this );
m_spActionExecutor->Execute( spAction );
catch ( )
- Un-executing the last executed actions:
m_spActionExecutor->Unexecute( 1 );
- Re-executing the last un-executed actions:
m_spActionExecutor->Reexecute( 2 );
How do I change history size?
The history size is managed by two methods:
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
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.
unsigned int C_DemoAction_InMemoryClearDrawing::GetBytesCount() const
return sizeof( *this ) + m_pClearedDrawing->GetBytesCount();
Keeping the history memory usage low may be accomplished by using temporary files. In this case, the number of allocated bytes is decreased by the size of streamed object.
unsigned int C_DemoAction_ClearDrawing::GetBytesCount() const
return sizeof( *this ); }
m_pDoc->m_Drawing.Write( m_Stream );
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
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
Reexecute methods are
public. Some clients would be tempted to write code like this:
SP_Action spAction = CreateAction_Clear();
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.
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?
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
kis::SP_ActionExecutor spActionExecutor = kis::CreateActionExecutor_Default();
spActionExecutor->Execute( CreateAction_Clear( this ) );
spActionExecutor->Execute( CreateAction_Insert( this, "abc" ) );
catch ( ... )
spActionExecutor->Unexecute( spActionExecutor->GetUnexecuteCount() ); }
The Demo Application
The demo is a simple drawing application. It implements actions for following user requests:
- Add a random segment. This action shows how to create an action that does not require parameters.
- Change history size. This action shows how to: create a non undo-able action, acquire parameters using a dialog box, and change the history size.
- Add a segment/rectangle/ellipse. These actions show how to acquire parameters using the mouse and how to add a new graphic object to the drawing.
- Clear the whole drawing. This action shows how to keep history memory usage low.
Although it is possible to list the whole history (the same way applications like Microsoft Word do), the undo/redo user interface elements list only the most recent action.
- May 16, 2006: Article first published
- June 14, 2006: "Questions?" section changed
- February 03, 2009: Minor bug fixed
- February 16, 2013: Typo