Add Undo

.
Download the source. The zip file is 2KB.

Adding Undo to an Existing Application

For me, the utility of a new class often depends on how easy it is to add to an existing application. I really like the Scribble tutorial that is found on the Visual C++ CD-ROM so I'm going to use the Scribble application as an example application for adding undo support.

There are four steps necessary to add undo to an application.

  1. Add CUndo to the project by adding the undo.h header to stdafx.h and by adding a reference to this class in the CDocument derived classes that you wish to support undo.
  2. Add new code to save the current state of the project whenever the user makes a change worthy of noting.
  3. Wire Undo/Redo into the menuing system.

Adding CUndo to Scribble

The first step is to either move the undo.h include into your project directory or to add the directory that contains undo.h to the include search directory. I prefer moving the undo.h file into the project directory. That way if I want to give someone access to my project I don't have to remember to include pieces of code in several directories.

The next step is to make undo.h available to the files that need it. You could do this by adding an include command at the top of each file that refers to undo.h. I tend to be a bit lazy and just add the line to the stdafx.h file. This causes undo.h to be included into most of the files in the project. I would add the following line to the stdafx.h file:

#include "undo.h"

Now the project knows about the CUndo class. To make the functionality of CUndo available to the Scribble application we must add a reference to this class in the definition of the ScribbleDoc class. To do this we must edit one line in the file scribdoc.h. That line is changed from:
class CScribbleDoc : public COleServerDoc

to

class CScribbleDoc : public COleServerDoc, public CUndo

At this point, the CScribbleDoc class contains support for undo/redo, all we need to do is take advantage of it.

Add Code to Save the Undo State

The key to making undo useful is deciding when to save the state of the application. I won't kid you, in many applications this can be a difficult decision. However, in the Scribble application we will save the state whenever a stroke is completed and in a couple of other special places.

In the Scribble application, the stroke is saved in the OnLButtonUp()member function of the CScribView class. The logical place to save the state would be to save it after the stroke has been added to the CScribbleDoc. The OnLButtonUp() member functions looks like this (minus a bunch of comments):

void CScribbleView::OnLButtonUp(UINT, CPoint point) 
{
	if (GetCapture() != this)
		return; 

	CScribbleDoc* pDoc = GetDocument();

	CClientDC dc(this);
	OnPrepareDC(&dc);  
	dc.DPtoLP(&point);

	CPen* pOldPen = dc.SelectObject(pDoc->GetCurrentPen());
	dc.MoveTo(m_ptPrev);
	dc.LineTo(point);
	dc.SelectObject(pOldPen);
	m_pStrokeCur->m_pointArray.Add(point);

	m_pStrokeCur->FinishStroke();

	pDoc->UpdateAllViews(this, 0L, m_pStrokeCur);

	ReleaseCapture();   
	pDoc->NotifyChanged();
	return;
}

The natural place to save the state would be after the m_pStrokeCur->FinishStroke() statement. To save the state add the following line of code after that statement:

pDoc->CheckPoint();

The CheckPoint() member function saves the current state. Placing a CheckPoint() command here saves the state after every stroke is completed.

At first glance, this may seem like all the states that need to be saved for undo/redo support. However, it turns out there are a couple of other cases that are important. When the CScribbleDoc class is first instantiated or when the file is newed or opened it is necessary to save the state, otherwise you won't be able to undo to the initial document state.

To do this, we need to execute the CheckPoint() member function in both the CScribbleDoc::OnNewDocument() member function and the CScribbleDoc::OnOpenDocument() member function. Both of these member functions implementation are similar. The CheckPoint() member function should be added after the statement containing the reference to InitDocument(). The following code shows the updated functions:

BOOL CScribbleDoc::OnNewDocument()
{
	if (!COleServerDoc::OnNewDocument())
		return FALSE;
	InitDocument();
	CheckPoint();
	return TRUE;
}

BOOL CScribbleDoc::OnOpenDocument(LPCTSTR lpszPathName) 
{
	if (!COleServerDoc::OnOpenDocument(lpszPathName))
		return FALSE;
	InitDocument(); 
	CheckPoint();
	return TRUE;
}

After this change, the undo/redo commands are implemented. All that is necessary to make undo/redo work is to add menu support so that the user can access this functionality.

Add Undo/Redo Menu Support

Most applications created using the AppWizard in Visual C++ have a menu selection for undo. However, they don't provide a menu selection for redo. To make redo available we need to add a redo menu item following the undo item in all of the edit menus defined in the application (and there are several of them). I used the identifier ID_EDIT_REDO and define the caption as "&Redo\tCtrl+Y". As the caption suggests, I also define the accelerator Ctrl+Y for each of the redo menu entries.

Using the ClassWizard, we can now add the skeleton code that implements undo and redo. To do this select the Message Maps tab and CScribbleView class. Add functions for both the COMMAND and UPDATE_COMMAND_UI message to the ID_EDIT_UNDO and ID_EDIT_REDO Object identifier's. This will create skeleton functions for OnEditUndo(), OnEditRedo(), OnUpdateEditRedo(), and OnUpdateEditUndo(). The implementation for each of these functions follow:

void CScribbleView::OnEditRedo() 
{
	CScribbleDoc* pDoc = GetDocument();
	pDoc->Redo();	
	pDoc->UpdateAllViews(NULL);
}

void CScribbleView::OnUpdateEditRedo(CCmdUI* pCmdUI) 
{
	CScribbleDoc* pDoc = GetDocument();
	pCmdUI->Enable(pDoc->CanRedo());
}

void CScribbleView::OnEditUndo() 
{
	CScribbleDoc* pDoc = GetDocument();
	pDoc->Undo();
	pDoc->UpdateAllViews(NULL);
}

void CScribbleView::OnUpdateEditUndo(CCmdUI* pCmdUI) 
{
	CScribbleDoc* pDoc = GetDocument();
	pCmdUI->Enable(pDoc->CanUndo());
}

Undo/Redo is now fully implemented all that is necessary is to test the result.

Taking a Quick Test Drive

We now have an application that has undo/redo working. Figure 5 shows the application running with five strokes. Each stroke is a number that was drawn in numeric order. Notice only the undo selection is available initially.


Figure 5 - Scribble with 5 strokes on the screen.

Figure 6 shows the application after undo has been selected the first time. Notice that now both the undo menu item and the redo menu items available.


Figure 6 - Scribble application after undo is selected the first time. Notice that both undo and redo are available now.

Figure 7 shows the screen after a new stroke (a box) has been added. Notice that only the undo menu item is enabled. This is because the redo list is cleared every time the CheckPoint() member function is called.


Figure 7 - Scribble application after a new stroke (the box) has been added. Notice that redo is no longer enabled.



Comments

  • Where can i get a Scribble program?

    Posted by nierong3000 on 11/04/2008 08:35am

    This article help us to add undo to an Existing Application, but i can't create a program than can use undo functionality, where can i get a scribble program?

    Reply
  • OLE support

    Posted by Legacy on 03/12/2000 12:00am

    Originally posted by: Vaclav Jedlicka

    This is a great class!
    I used it in 1 application and it works perfectly.
    Headaches arrived when I tested it in apps that support
    embedded OLE items(for example the DRAWCLI sample).
    When I insert an OLE item, after several UNDO/REDO
    cycles it asserts in OLECLI1.CPP(789) on this:

    ASSERT(m_lpObject != NULL);

    it is in the function
    BOOL COleClientItem::IsModified()

    I cannot find where the problem is. I do not know much
    about the MFC OLE support so I am not able to fix it.

    I tested it in VS6, on Windows NT 4 Workstation.

    Vaclav Jedlicka

    Reply
  • Some interesting issues

    Posted by Legacy on 01/26/1999 12:00am

    Originally posted by: Iuliu Rus

    Forgive me if i got it wrong, but these are a few conculsions i draw
    afrer a look at the CUndo class:
    1.The undo information is actually the current state of your document.
    That mean that not only the class doesn't store only the differences
    between the previous state and the actual state, but , because of its architecture, it's also very hard to change it to store only the modified part of the document.The action that happened before you call Checkpoint() is not transmited by any means to the Store fuction.So you cannot know what exactly changed and have two options:
    To save the entire satte-the easy one.
    And to compare the previous saved state with this one-and this is very hard
    2.If the current document state is stored in a CMemFile every time an action takes place,the memory usage will be terifying.The result is that you cannot undo only very few actions (the undo "buffer" must be small).

    So if you really want a "pro" undo capability ,you must use another alternative.The Command pattern From "Design Patterns" ,Gamma etc...
    is a good choice.

    But i like this class:It makes adding the undo and redo capabilities
    an easy job

    Excuses for my English,to all that discover grammar mistakes in this text.

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

Top White Papers and Webcasts

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds