Undo/Redo Manager

Executive Brief

What? The Undo Manager Library adds undo/redo facilities to the MFC Library, with full support for text views, along with simple command handling and menu building.

How? Provide a set of classes and recipes that, as much as possible, provide transparent and simple to use undo/redo facilities.

Why? I am former Mac programmer who wanted to learn about the MFC Library and improve on one of the MFC Library's weak spots.

Introduction

The Microsoft Foundation Classes is a sophisticated application framework which greatly simplifies Windows programming. One of its weakest areas however is the lack of usable undo and redo facilities. While several components of the MFC Library do provide limited support, generally their implementations suffer from one or all of these three drawbacks:

  • single-level, not multiple-level
  • oriented to a single view, not a document
  • private implementations, not easily extendible

The first problem did not exist several years ago as the lack of undo was not unusual, so providing any level of undo seemed like a major feature. For today's users however, an application seems unfinished without this capability and a competitor's product including it certainly has an advantage. It is also a great "what if" feature, allowing users to experiment without needing to manage many saved versions of a document.

The second and third problems are apparent in both the edit and rich edit controls supplied with Windows. With the exception of Notepad and WordPad, these controls are usually a small piece of the overall program and as such, should not maintain their own undo/redo stack. To realize this, consider having two views, say in a split window, that are views of the same document. This situation clearly suggests storing text in the document, rather than in each view, so that both views are always in sync. Likewise, the undo/redo stack should, in some way, be associated with the document and not each individual view, or they potentially can become unsynchronized. And of course, an application may have many more operations than just text editing, but the undo/redo stack of the edit controls cannot be used for non-text operations.

Now suppose the control is the center point of the application. Unless the application is a simple variant of a text editor, the undo/redo capabilities of the edit controls will most likely be useless as their implementation is internal. With no public extension mechanism, generalizing even a built in operation is virtually impossible as you don't know what kind of information the stack contains, nor can you even see the stack itself.

In both cases, when the control is the center point of the application and when it is only a small portion, we can see that the undo capabilities of the MFC Library leave something to be desired. What we need is a public, document-oriented, undo/redo API that integrates well with the MFC Library.

Terminology

This guide assumes familiarity with design patterns as cataloged by the book "Design Patterns: Elements of Reusable Object-Oriented Software", by E. Gamma, R. Helm, R. Johnson, and J.Vlissides, especially the factory method and template method patterns. In a nutshell, a factory method provides a central, customizable function that creates (derived) objects of a specific base type. It is typically used to create "helpers" and the function CBetterEditView::MakeEditAction() is an example.

A template method is usually a non-virtual member function that invokes a number of virtual functions, or bottlenecks, in a specific order. Effectively, a template method is recipe whose steps must be followed, but each step is open to interpretation. It also allows the base part of an object to call "down" into the derived part. The function CREEditAction::Do() is an example of a template method; CREEditAction::PerformOperation() is an example of bottleneck.

I also like to make distinctions between the redefinition and the refinement of member functions. Redefinition means that the base version of a member function should probably not be invoked from within a derived version. Refinement, on the other hand, means that the base version should be invoked at some point, though this may be conditionally so. I have tried to state in each virtual function's commentary which technique seems more applicable. As a guide however, within a subclass, factory methods are usually redefined and bottlenecks are usually refined.

An Undo/Redo Framework

For discussion purposes, define an action as an operation which can be undone, such as typing, changing the font height, or pasting text, and define the object(s) they affect as targets. Actions generally affect a document, dialog, or some other container-like object, and in general, we will call these objects action targets. Operations, conversely, directly manipulate a subpart of the action target, usually a specific view of a document. We will call these more specific targets operation targets to distinguish them from an action target. Note that an operation target often provides an implementation for a specific operation.

An action differs from an operation in that an action can be undone, while an operation cannot be, at least not by itself. This does not mean that an existing operation cannot be used in implementing an action, and on the contrary, just the opposite is often true. An existing (non-undoable) operation is usually used in defining an (undoable) action version, and we'll see this in the implementation examples below.

Because of the additional behavior inherent in an action, it is useful to decompose an action into three distinct facets: doing, undoing, and redoing. Although their roles may seem obvious, it is instructive to see pseudo-code describing these facets, especially when comparing them with actual code.

Do

  1. Save the current or "before" state of the operation target
  2. Perform the operation, producing an "after" state

Undo

  1. If the action has never been undone, save the current or after state
  2. Restore the before state

Redo

  1. Restore the after state.

Note that most actions are lazy. They assume that other actions are ordered and that states are saved only when absolutely necessary. As evidenced above in undo section, the after state is saved only within it and not the do phase. The first property, strict ordering of actions, simplifies the amount of information which must be recorded in order to undo. The second property, saving only when necessary, is an effective optimization in both time and space. For example, consider typing some text then cutting a portion of it. The cut action need only save the text it removes and not all of what was typed. The typing action can also assume that if it is ever undone, the state of the text which existed after typing was complete, is the current state now regardless of any actions which followed.

Because of this laziness, and the fact that not every action is immediate, the duration of action's do phase becomes very important. For immediate actions, like most commands, the duration of the do phase is by definition, very, very short. Actions like typing, however, are not immediate. Yet we must know when they are finished or saving state becomes rather difficult. To accommodate this, we define a behavior, finishing, which saves any necessary state information. We must also guarantee that this behavior will be exercised and the approach taken is simple: an action must finish whenever another action enters its do phase.

Lastly, let's consider how the undo and redo commands function in a typical application. When a user performs a series of actions, say typing, cutting, then pasting, the most recent action done is the action which can be undone. So selecting undo at that point undoes the paste action. Selecting undo a second time undoes the cut action. And finally, selecting undo a third time undoes the typing. The redo command is similar, with the exception that it is the most recently undone action which is redone first. Clearly this last-in, last-out behavior suggests maintaining actions on a stack, and we will often refer to an action target's undo or redo stack when talking collectively about its actions.

Actions and their Class Hierarchy

The class hierarchy of actions is listed below, though obviously many custom actions would be needed for an application that was not text-oriented. Note however that many actions will be simple variants of those below so you can use them as models.The typing actions are particularly interesting as they are the only non-immediate actions listed below.

As a convention, those actions specific to the rich(er) edit view are prefixed with RE while those pertaining to the (better) edit view are prefixed with BE. Because class CEditView is in one sense a simplification of the CRichEditView, the set of actions for CBetterEditView are a subset of those CRicherEditView. Because neither view is actually related via inheritance, and that there really are some major differences between the two views, neither set of actions is actually related to the other.

CAction
CBEAction
CBETextAction
CBEEditAction
CBEReplaceAction
CBETypingAction
CREAction
CRETextAction
CREEditAction
CREReplaceAction
CRETypingAction
CREStyleAction
CREParagraphAction
CREInsertObjectAction

As a running example of how to implement and use an action, we will examine how the cut operation has been extended into an undoable action. This action, since it shares many traits with the other clipboard commands, is capable of pasting and clearing as well. (Copying is not destructive, so it need not be undoable nor does it require an action.) In fact, because it modifies text in an edit view, as many operations for the edit view classes do, it is derived from a base class, CBETextAction. Although this sounds like quite a bit to digest, it is representative of most actions. We'll first look at class CBEEditAction and then follow with a description of class CBETextAction .

CBEEditAction

The constructor for class CBEEditAction is typical of most action constructors. The first argument is the operation target, which in this case, is an instance of class CBetterEditView. The second argument is a resource id representing a specific action type. Unlike commands, actions use a string id to distinguish action types. This not only allows distinguishing a specific operation within a multi-use action, it allows a string resource (a name) to be associated with the action for use in an undo/redo menu. The last argument, which is not used in this particular action, allows for additional user-defined data to be passed. We won't examine the implementation of this constructor because its base class constructor actually performs all relevant initialization.

CBEEditAction(CBetterEditView *operationTarget, UINT actionType, void *data);

Also typical is the Do() member function, especially for multi-use actions. First and foremost, the base version of CBEAction::Do() is called. This is important as most actions automatically notify the undo manager that they exist, and this is done in the function CAction::Do(). You can control this with the autonotification feature, either as you construct the action or later with the CAction::SetAutoNotify() member. Note that if autonotification is off, then you must notify the undo manager yourself with a call to CAction::NotifyUndoManager(). Eventually, this call will tell the previous action that it must finish and hence, its importance. It is also imperative to avoid making major changes to a target until this is done, as the previous action may have state left to save.

Second, a helper or bottleneck function, PerformOperation() is called, making this a example of a template method. In this case, the bottleneck does not actually perform the operation but instead calls yet another routine in the operation target which does. This seems complicated but its utility will become apparent once you realize this action works with cutting, pasting, clearing, and replacing text. With a small amount of work which is decsribed below, it will work with any edit-like operations you define.

At the very end of the Do() routine, the Finish() member function is called. Since clipboard operations are immediate, the action finishes as soon as the operation completes.

void CBEEditAction::Do()
{
CBEAction::Do();
 
// Call helper; subclasses extend that routine
// rather than this one; this kind of template
// method is common in multi-use actions
PerformOperation();
 
// this is a one-shot action
Finish();
}
 
void CBEEditAction::PerformOperation()
{
switch (actionType)
{
// call non-undoable operation to actual do the work.
case IDS_CUT_ACTION   : operationTarget->PerformCut(); break;
case IDS_PASTE_ACTION : operationTarget->PerformPaste(); break;
case IDS_CLEAR_ACTION : operationTarget->PerformClear(); break;
}
}

In a single-use action, you would probably perform the operation or call a PerformXXX() routine directly in your Do() routine. The role of PerformOperation() here is to isolate code specific to an operation from the shared behavior in a multi-use action. Subclasses, whose operations will obviously be very similar to cut, paste or clear, need only to refine/redefine PerformOperation(). See class CBEReplaceAction for an example. To recap, the chain of calls is

    1. CBetterEditView::OnEditCut()         (creates action)
    2. CBEEditAction::Do()                  (saves state, calls bottleneck)
    3. CBEEditAction::PerformOperation()    (switches on action type)
    4. CBetterEditView::PerformCut()        (performs cut operation)

Again, note that the operation target does all of the work. Another possibility, better suited for complicated single-use actions, is to place the brunt of operation's implementation inside the action itself. This keeps the operation target lightweight and simpler to use, but also transfers the responsibility of subclassing onto the action.

Note also that this particular action class does not define redo, undo, or any state-saving routines itself. Instead, that functionality is provided by the base class, CBETextAction.

CBETextAction

The constructor for class CBETextAction passes on its argument to its base class too. Unlike its child class above however, it also saves the current selection, both the selected text and the selection endpoints. This information is stored in an instance of the utility class CTextRange, just as it is in the rich edit version, class CRETextAction. Here however, the endpoints are ints rather than longs and only the string portion is actually relevant to the edit view classes. (Class CTextRange is considerably more general than indicated here and can be used to store text styles, paragraph styles, OLE objects and user-defined functions.)

CBETextAction::CBETextAction(CBetterEditView *aView, UINT type)
   : CBEAction(aView, type)
{
	int start, end;

	// save current selection points
	operationTarget->GetSelection(&start, &end);
	deleted.start = inserted.start = start;
	deleted.end = inserted.end = end;

	// we have not undone this action, so we will need to save
	// the "after" state before reverting to a previous state
	neverUndone = true;

	SaveRange(deleted);
}

No Do() member function is defined in class CBETextAction, leaving a derived class to provide it as CBEEditAction does. Because many of these derived actions are immediate, the Finish() member function will usually be called inside of a derived Do() routine. Class CBETextAction does define this function, and does so somewhat surprisingly. It simply saves the endpoint of whatever was inserted and not the text itself. As we stated before, state (the text in this case) is only saved if the action is undone.

void CBETextAction::Finish()
{  
CBEAction::Finish();
    
int start, end;
    
// save insertion end (long vs. int)
operationTarget->GetSelection(&start, &end);
inserted->end = end; 
}  

Finally, we come to the meat of an action, the Undo() and Redo() member functions. In this case, it is more helpful to consider pasting text over text. Before the action pastes anything, it saves the current selection (fully) in the deleted text range. Then, it performs the paste operation which inserts new text and saves the endpoints, as noted above. To undo the paste, we must save what was pasted, then restore what was deleted. Redo is very similar, differing mainly in the range which is restored. Because both routines share so much, most of the grunt work is performed in SaveRange() and RestoreRange(). We won't consider them here; they mostly copy text to or from the edit view.

void CBETextAction::Undo()
{  
CBEAction::Undo();
    
// save only during the first undo; after
// that, we have the information we need.
if (neverUndone)
{    
neverUndone = false;
SaveRange(inserted); 
}

// deleted any inserted text
DeleteRange(inserted);
    
// insert any deleted text, restore selection
RestoreRange(deleted); 
}
  
void CBETextAction::Redo()
{  
CBEAction::Redo();
    
// re-delete the deleted text
DeleteRange(deleted);
    
// re-insert the inserted text
RestoreRange(inserted); 
}  

Action Targets

An action target is usually a document, dialog or some kind of pseudo-container of the operation target. The action target is also not usually responsible (directly) for performing an operation. For example, consider a simple text editor which follows the document-view paradigm. It probably is composed of an edit view and a document object, or in our world, an operation target and an action target. When a user chooses the cut command, both the edit view and its document are affected, but the latter's change is a consequence of the former's. The document does not implement the cut operation but it is nevertheless aware of it. In general, when an operation target is modified, so is the corresponding action target.

Once an action target is created, it needs to inform the undo manager of its existence by calling CUndoManager::RegisterTarget(). This is commonly done in a refined version of OnNewDocument(), e.g.,

BOOL CPadDoc::OnNewDocument()
{
if (CDocument::OnNewDocument())
{
gUndoManager->RegisterTarget(this);
return TRUE;
}
else
return FALSE; 
}

Likewise, the action target must also remove itself, or memory will be lost until the application quits.

void CPadDoc::OnCloseDocument()
{
CDocument::OnCloseDocument();
gUndoManager->RemoveTarget(this);
}

Operation Targets

An operation target is usually a view of some sort that is directly responsible for performing an operation. For example, consider the text editor example again. Choosing Edit:Cut sends the ID_EDIT_CUT command (eventually) to the edit view. The edit view actually performs the operation of copying the text to the clipboard and clearing the selection. The responsibility lies with the edit view rather than with a frame window, document or the application object itself.

To add undo/redo capabilities to an edit view, the CBetterEditView class overrides a number of command handlers to create an action to carry out the operation. Consider the cut example again. When it handles the ID_EDIT_CUT command, it creates an action that encapsulates the operation. The action will save whatever state is necessary, then perform the operation.

void CBetterEditView::OnEditCut()
{  
// create the appropriate action (IDS_CUT_ACTION);
lastAction = MakeCutAction(IDS_CUT_ACTION);
    
// do the last action
lastAction->Do(); 
}   

The creation of the action resides in a factory method so that subclasses can easily override with a different action. For example, to support automatic word selection, a derived action might automatically extend the selection from a simple collection of characters to the word containing those characters and then perform the cut..

CREAction *CBetterEditView::MakeCutAction(IDS_CUT_ACTION)
{  
return new CREEditAction(this, IDS_CUT_ACTION);    
}  

When the CBEEditAction::Do() member function is invoked, it will save the current selection and call CBetterEditView::PerformCut() to actually cut the selection. We saw this in the section on actions, so here we will concentrate on the implementation of operation. Because it is an operation, we do not need to save any state information. Nor do we need to do much of anything as the cut operation is already defined in the base class, CEditView.

void CBetterEditView::PerformCut()
{  
// call existing operation in base class
CEditView::OnEditCut(); 
}  

Class CBetterEditView and class CRicherEditView extend the CEditView and CRichEditView classes respectively. In general, each override of a handler follows the above logic, so that calling OnEditXXX() should produce an action and PerformXXX() should perform the operation. You can also call the base version CRichEditView::OnEditXXX() directly, in addition to PerformXXX(), if an action is not needed..

Handling Edit:Undo and Edit:Redo

A view not only creates actions and performs operations. It is often responsible for handling an undo/redo command and updating their respective menus. To respond to an undo command (ID_EDIT_UNDO), you would add a message handler as you would normally do for any command. In the implementation of the handler, call the member function CUndoManager::Undo(), passing as an argument a pointer to whatever the action target is. Usually this will be the view's document. The classes CBetterEditView and CRicherEditView already define this, and an update version, so it is largely a matter of defining the appropriate menu items and accelerators to activate them. You can be copy the implementations below to any view you define.

void CBetterEditView::OnEditUndo() 
{  
// tell undo manager to undo the last action
// associate with the document (action target)
gUndoManager->Undo(GetActionTarget()); 
}  

The Undo Manager Library also supplies a basic menu handling class, class CSimpleUndoRedoMenu. It defines a static member function, Set(), which automatically enables, disables, and modifies the text of the menu item as appropriate. It requires the CCmdUI pointer and the action target and produces a menu item with the caption Undo followed by the action name.

void CBetterEditView::OnUpdateEditUndo(CCmdUI* pCmdUI) 
{  
CSimpleUndoRedoMenu::Set(pCmdUI, GetActionTarget()); 
}  

Note that these member functions appear here exactly as they are implemented. By default, the action target is assigned the view's document object in CXXXEditView::OnCreate(). You can override that setting by calling CXXXEditView::SetActionTarget(); just do so before any action is created.

CUndoManager

Rather than being a part of a document or dialog and defining message handlers to communicate, the set of targets and their undo/ redo stacks are connected via single global object, referenced through gUndoManager. While global objects should in general be avoided, I believe this implementation is the best possible one when integrating two separate libraries. This is especially true in the MFC Library, as an individual object can only send arbitrary messages to CWnd derivatives.

Construction & Destruction

Because the undo manager is global, and because there is a potential for subclassing, the undo manager must be dynamically created and destroyed to allow the substitution of a derived object. Within the MFC Library, a convenient place to do this is in the InitInstance() and ExitInstance() member functions of the application. In the application CMultiPadApp, the following overrides appear.

BOOL CMultiPadApp::InitInstance()
{   
gUndoManager = new CUndoManager();
// remainder of InitInstance()
. . .
}
  
 int CMultiPadApp::ExitInstance()
 {   
delete gUndoManager;
return CWinApp:ExitInstance();
}   

A side benefit of dynamic allocation is that it provides a fast way to reclaim all memory used by the undo manager and its internal STL data structures. In a low memory situation, deleting the global instance will delete all action managers, all actions, the actions' saved data, and free the space reserved by the STL allocators. The user will lose the ability to undo/redo previous actions but in light of a catastrophy, this is a small price to pay. Don't forget to recreate the undo manager and re-register all of the previous action targets, or it will be an irrecoverable failure!

Registration

We've already mentioned that an action target must register itself with the undo manager. The prototype for this routine is slightly more general than in the example shown above, and in fact you can limit the number the undo levels and adapt how the undo manager notifies an action target of undo/redo changes. You can also change the options for an existing action target, say to dynamically increase the size of the undo stack or even to change how notification is handled. (Yes, you can call the registration routine again and again.)

The constant UM_DEFAULT_STACK_CAPACITY is programmer-definable, usually in a precompiled header file or in the options for the compiler. If this constant is not defined, it is set to 25. A large value, say LONG_MAX, effectively creates an unbounded stack, as it is doubtful a user could perform that many actions in a sitting.


virtual void RegisterTarget(
                            void *actionTarget,
                            size_t capacity = UM_DEFAULT_STACK_CAPACITY,
                            NotifyFn fn = UMDefaultNotifier
                           );

Notification

The undo manager itself is independent of the MFC Library and thus, an action target is represented with a generic pointer. Rather than forcing the target to be a derived instance of CCmdTarget, or derived instance of any specific class for that matter, the undo manager uses a callback to communicate with the action target. If the undo manager is being compiled along with the MFC Library, the default callback tests for an instance of class CDocument and sets its modified flag appropriately. Note that this behavior is correct even if the action target is really a dialog box, as dialog boxes generally are not interested in such changes.

void UMCmdTargetNotifier(void *actionTarget, UMNotificationData &data)
{
CCmdTarget *cmdTarget = (CCmdTarget *)actionTarget;
if (cmdTarget->IsKindOf( RUNTIME_CLASS(CDocument) ))
{    
((CDocument *)cmdTarget)->SetModifiedFlag(data.isDirty);
}
}

On the other hand, if the MFC Library is not present, the default notification routine does nothing and probably requires replacement by the programmer.

CActionManager

This class connects a single target with its undo and redo stack. It is hidden from view by the undo manager and is never used directly. Most of the member functions in class CActionManager mirror those in class CUndoManager; the routines in the undo manager generally perform a lookup to find the appropriate action manager, then forward the member call to that action manager.

The action manager maintains a marker indicating the size of the action target's undo stack when it was last clean. In the context of a document, clean means saved, and we will assume we are working with a document to keep things concrete. At first, the marker is zero, meaning that all actions must be undone before the document is back to its initial (clean) state. As actions are performed, the document accumulates little changes that become "permanent" once the document is saved. Permanent here does not mean that they cannot be undone. It means that if they are undone, they dirty the document like any other operation. Only if they are redone will the document be back to its last-saved state.

Because the stacks used by an action manager are finite, they may occasionally overflow if the user is patient enough! If this happens, the bottom-most action is discarded and the marker is set to -1, indicating that no amount of undo/redo can return a document to a clean state, unless it is saved again.

CSimpleUndoRedoMenu

This class sets and enables or disables undo and redo labels in Edit menu. It requires a pointer to CCmdUI to adjust and a pointer to the action target it should use when building the menu item. It uses the CCmdUI pointer to identify which stack should be used in constructing the menu item. Also note this class is not usually instantiated as its only member function is static.

void Set(CCmdUI *pCmdUI, const void *actionTarget);  

Implementation Example

To illustrate using the Undo Manager, we shall add multiple undo to the MultiPad sample application included with Visual C++. This application is a simple text editor which uses the multiple document interface. As is, it supports only one level of undo.

Setting Up The Project Workspace

1. Copy the source files for the MultiPad application into a local directory. See the InfoViewer Topic "MultiPad: Edits Files Simultaneously" for information about MultiPad and for information about obtaining its source code.

2. Add the path up to and including the directories, Undo Manager and its subdirectory, UM Header Files, to your default search path. You can do this by using the /I directive or selecting Tools:Options, then clicking on the Directories tab, and finally selecting the Include Files in the Show directories for: combo box. If you plan to use the Undo Manager Library in many projects, the latter approach is preferable since it is global change.

3. Normally, maintaining parity with the existing folder structure in your directories simplifies project management, and the example here assumes you will do this, so create the following folder structure in your project:

Undo Manager
UM Header Files
UM Source Files

4. Add the contents of the Undo Manager directories to their respective folders in your project workspace. Do NOT add the top-level resource script "UndoManager.rc" directly to your project.

5. Switch to the Resource View and right-click on the resource folder. Select the item Resource Includes... from the pop-up context menu. In the second edit-box, entitled Compile-time directives:, add the following line:

#include "UndoManager.rc" 

With the exception of steps 1 and 2, you would follow this recipe for each project that requires the Undo Manager Library. You may also have to add the files to ClassWizard yourself, otherwise they won't be available.

You should be able to build the entire project at this point. Since you have not made the necessary calls to the Undo Manager Library, the built application will be no different than the default MultiPad application, but it is useful self-check.

Modifying the MultiPad Application

1. Add the following includes to the top of file multipad.cpp, just after the include of <locale.h>:

#include "UndoManager.h"
#include "BetterEditView.h"

2. At the beginning of CMultiPadApp::InitInstance(), add the following line create a global instance of the CUndoManager class.

gUndoManager = new CUndoManager(); 

You will also need to define CMultiPadApp::ExitInstance() so that the global object is deleted. It should something like

int CMultiPadApp::ExitInstance()
{
// clean up undo manager
delete gUndoManager;

// call base version
return CWinApp::ExitInstance();
}

3. Change the document template instantiation code so that a CBetterEditView instance is created rather than a vanilla CEditView object. Switch back to the File View and open the file multipad.cpp. In the member function, CMultiPadApp::InitInstance(), change the following code from

...
AddDocTemplate(new CMultiDocTemplate(IDR_TEXTTYPE,
    RUNTIME_CLASS(CPadDoc), RUNTIME_CLASS(CMDIChildWnd),
    RUNTIME_CLASS(CEditView))); 

to

...
AddDocTemplate(new CMultiDocTemplate(IDR_TEXTTYPE,
    RUNTIME_CLASS(CPadDoc), RUNTIME_CLASS(CMDIChildWnd),
    RUNTIME_CLASS(CBetterEditView)));

4. In the document class, CPadDoc, add the following overrides to inform the undo manager about the target.

BOOL CPadDoc::OnNewDocument()
{
if (CDocument::OnNewDocument())
{
gUndoManager->RegisterTarget(this);
return TRUE;
}
else
return FALSE; 
}

void CPadDoc::OnCloseDocument()
{
CDocument::OnCloseDocument();
gUndoManager->RemoveTarget(this); 
}

5. Build the application and test.

Although redo is built into CBetterEditView, no menu item is associated with the command in this example. To add redo, simply add a new menu item with appropriate id, ID_EDIT_REDO. You also probably want to add the appropriate accelerator as well.

A slightly extended test version of MultiPad appears in the Examples folder. It has the the redo menu item already defined. Also included are files necessary to add multiple undo/redo to the WordPad sample application. It illustrates many different techniques described here including undoing basic OLE operations, dialog actions, and dynamically resizing the undo stack. Unfortunately, there are just too many changes to describe here. See the included Read Me files on how to build these applications.

Caveats, Future Directions and Miscellaneous Items

Handling Dialog Boxes with Actions

Perhaps one of the most common forms of an action is the setting of options in a dialog box. For a simple dialog, there is often a handler much like there are for menu commands. If this is true, the trick of overriding the handler, say OnXXXOperation(), and defining a separate operation routine, say PerformXXX(), works well. This is done in several actions, most notably CRicherEditView::OnStyle(), and CXXXEditView:OnReplaceSel() and CXXXEditView::OnReplaceAll().

If instead the dialog is complex, there may be many handlers or a DoDataExchange() routine, and the above technique is inapplicable. A general outline for this situation is as follows:

1. Create a dialog action that records the current settings. Autonotification should not be used, as the action may not actually be necessary (see step 3).

2. Create the dialog as normal.

3. If DoModal() returns IDOK, compare the new settings with those saved in action. If the new settings differ from the old, notify the undo manager of the new action using CAction::NotifyUndoManager(). If they are the same, delete the action as nothing has changed and therefore, there would be nothing to undo.

4. Continue with normal processing.

While the above is technically correct, it places a large burden on the programmer. The MFC Library provides an easy way of transferring information from a view or document to a dialog box and back through the Dialog Data Exchange (DDX). The Visual C++ ClassWizard take this one step further by automatically generating DoDataExchange() routines. Unfortunately, I have not explored custom DDX routines and the ClassWizard enough to extend them with Undo Manager support. I do not believe that this is too difficult, with perhaps the comparison(s) in step 3 being an exception..

Also note that dialog boxes may have their own undo/redo stack distinct from the document to which they apply, though we have not discussed this possibility in any depth. This is useful for complex dialogs which are almost documents in their own right. The technique above applies to the effect a dialog has on a document, not undo within a dialog box.

No Drag & Drop

At present, drag & drop in CRicherEditView is not undoable and hence, disabled. Nearly all of the drag & drop operations are hidden in the Rich Edit Control, and not defined in class CRichEditView or class CRichEditCtrl. This makes them exceedingly hard to customize! I'm working on this now, but essentially, it requires (re-)implementing everything the control does inside of CRicherEditView. Because drag & drop is such a reasonable user expectation, this is probably the biggest deficiency in the Undo Manager Library.

More Sophisticated Undo/Redo Menus

In this first version of the Undo Manager, the handling of the undo and redo menus is quite simple with only the last undo and/or redo action appearing in the menu item. This means that even though the Undo Manager can process multiple actions at a time, the user must undo or redo each individually. A future version will offer a more sophisticated hierarchical menu class that will remove this limitation.

Notes on CBetterEditView and CRicherEditView

Because the CEditView class is so simple, I felt it important to keep class CBetterEditView simple as well. It also provides a nice example in that role. You will however find it rather difficult to extend this class with additional text elements. Most notably, the text actions all use a "fixed" data structure to store range information, making it difficult to store anything other than what they were initially designed to hold. You can of course add new kinds of operations and actions.

In contrast the CRicherEditView class was designed to support an unlimited and unknown number of text elements. A text element is anything that can appear or affects text in an edit view. A character, an object, the bold style type, and center justification are all examples of predefined text elements that the CRicherEditView class handles. A text annotation to indicate a non-printing note or style like "code" which uses a specific font and size, would be attributes that you might add in a subclass.

Class CRicherEditView uses a factory method to create a CTextRange structure (or a derivative) for its internal use and for use outside of the class. This eliminates, for example, the need to subclass the typing action when subclassing the CRicherEditView class in most situations. Class CRicherEditView also uses template methods and bottlenecks to limit the number of member functions needing refinement in a derived version. In most cases, this also limits the overhead associated with "getting" additional text elements. See the implementation notes for details.

I hope you find the Undo Manager Library useful, and if you need a hired gun, I'm available!

Download demo project - 37KB

Download source - 70KB



Comments

  • order cialis without prescription

    Posted by Gaiccantimund on 07/16/2013 01:40pm

    drugs (like elation). On the unaccommodating, an un-prescribed work of the stimulant can do important damage to the vital organs of the body. http://actafarma.com/wordpress/us/ pains, muscle aches and headaches. You should also be hip of the fact that if you combine this treatment with alcohol, the side effects can thrive tranquil worse, from time to time equal

    Reply
  • lilly brand cialis buy ontario

    Posted by Boileammago on 06/02/2013 03:14pm

    the reasons which tease caused difficult relationships. It is defined as human beings's incapacity to pick up satisfactorily erection to demand famous procreative intercourse. If this problem is encountered http://cialisonlinetrustph.net Cialis can be infatuated with a 100% overfed carry to extremes and there drive be no change in it's action. The same is not be fulfilled as a replacement for Sildenafil or Vardenafil, which are upset nearby food. within 15 minutes and undying in search up to 36 hours. Individuals who check all three medications often come to singular conclusions about which cure-all works less ill for them. At the buy Cialis online ~so7871, Cialis Online

    Reply
  • payday loans in plano

    Posted by Affinfova on 05/30/2013 11:41am

    payday loan $yj840! payday loans uk expense of a payday loan.If circumstances require you to try loans, it is important to know that you last wishes as acquire to satisfy unreasonable rates of interest. Sometimes the notice price can [url=http://payday-uk-compare.co.uk]payday-uk-compare.co.uk[/url] you're between paychecks, and surprising expenses come up, they can be a beefy help. Most of these lenders deputize the change completely basic and flexible, not requiring you to fax any

    Reply
  • payday advance in one hour

    Posted by dimialieque on 05/28/2013 03:33pm

    payday loan payday loans in phx az

    Reply
  • cheap canadian cialis

    Posted by gearlerma on 05/17/2013 04:05am

    Viagra is also used to treat pulmonary hypertension arterial disease rank (PAH) and height. Myriad athletes put Viagra as an enhancer of force. Viagra is known to enlarge on blood vessels and for this attend to arrange for more oxygen to the substance parts that leads to viagra sin receta fbgz8246 comprar viagra [url=http://cialis-generique-fr.net]cialis[/url] hybrid with Cialis or any other ED medicines representing that issue. This may under any circumstances preside over to a fall in the blood pressure to a jeopardy likely to be mark that may fit conceivability diurnal living of the living soul employing these medication.

    Reply
  • cialis where to buy

    Posted by Endundunmappy on 05/10/2013 04:54am

    "Blood needs to overspread into the penis, and it needs to get trapped there," A run-of-the-mill erection is more daedalian than it may seem. During physical arousal, the planner sends signals to the penis via nerves that travelling auspices of the spinal cord. These nerves http://viagracialisita.net following enquiry of you adept medical patrimony, you dominion be granted a medicine for Cialis. Guys who turn to account nitrates, alpha-blockers or that have been advised to not pull someone's leg animal project due to the low-down of trim and seemliness issues mustn't Kamagra Pronounced Jelly is an ED treatment utilized to help bonus erectile dysfunction and impotence. Kamagra Voiced Jelly contains the working ingredient Sildenafil Citrate. Kamagra Said Jelly helps with the cadaver's unaffected mechanisms to achieve and weather http://viagraukuk.co.uk does ditty discover at Viagra? The most ostensible rank is the Internet where Canadian pharmacies beget been selling an bounteous amount of medications at prices that are sometimes half of what pharmacies care in the Unanimous States. Certainly, the United

    Reply
  • buy brand name cialis

    Posted by Endundunmappy on 05/09/2013 11:34am

    Debilitation treatment at a callow life-span has some benefits, including a typically improve real and mental grandeur of affairs. When you are in your mid-30s and suffering from erection problems, there is a piercing prospect that your impotence problem is not ViagraPillsOf @ax3782% viagra online buy india How Viagra influences fertility. The upshot doesn't select fertility, however, when a four cannot plan a sprog expected to erectile dysfunction, the drug can be beneficial. http://viagraukuk.co.uk trouble oneself for the benefit of such a problem. Conclusively tried and tested on patients successfully in 1991 and 1992, its efficacy as a efficacious erection medicament surfaced in the epidemic arena. The medicine was later patented on the renown of Pfizer in 1996 and two years later, the

    Reply
  • online south africa buy viagra

    Posted by Endundunmappy on 05/08/2013 06:17pm

    complete process may superintend up to equal period or maybe more. Opposite to in which, through Viagra you can manipulate the results speedily after 1 hour and have all of them regarding 4 hours upmost. http://viagrapillsof.net Raynaud's cancer While a gink may still be in his prime qualification, it would on no occasion smart to consider manifest more concerning the drug. Aging may happen faster than a being expects and in the future you see it, you dominion already be in want of generic Viagra. The subsequent specifics are viagra uk buy generic viagra buy

    Reply
  • buy Viagra

    Posted by uninaExerie on 05/03/2013 12:23am

    it helped individuals converse about nearly this subject. [url=http://buy-viagra-usd.net]generic viagra[/url] Wonderful P-Force Tablets

    Reply
  • payday loans uk compare

    Posted by horEsorotow on 05/01/2013 04:05am

    take into account believe masses to become available to credit for. With these loans it is unsophisticated and casual to journey by specie on the era you required. payday loans uk project payday pros and cons

    Reply
  • Loading, Please Wait ...

  • You must have javascript enabled in order to post comments.

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • This paper introduces IBM Java on the IBM PowerLinux 7R2 server and describes IBM's implementation of the Java platform, which includes IBM's Java Virtual Machine and development toolkit.

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds