Introduction
I am pleased to submit a tutorial to the Code Project, as I have benefited very much from the generous and helpful resources here.
This tutorial tries to explain how I developed undo/redo capability for my own applications. It works very well for me; perhaps others here might find it useful. I'll refer to it as the "DocVars" method.
The basic philosophy of the DocVars method is enumerated in the statements below:
-
Packaging - Place all of a document�s undo-able and redo-able variables into a separate class (CDocVars
, for example).
-
Handling and modifying the DocVars as a complete package - If one of the variables in the document�s current DocVars needs to be changed, a new instance of the DocVars is created reflecting the change.
-
Serving � Manage a list of CDocVars
as the modification of the document proceeds. This tutorial uses CUpdateMgr
for this job. Management responsibilities include:
-
Custodial Duties � A single class must take the responsibility of supporting the CURRENT and LIVE DocVars. This tutorial uses the document as the DocVars custodian. In this capacity, the document is the central clearing-house for the application�s undo-able/redo-able variables. All important variables are maintained by the document, and to gain access to them, all other classes must consult the document.
The conversation below should help to explain how the system works.
CView :
|
Hey, CDocument ! What you got? |
CDocument :
|
That all depends on what you want it for. |
CView :
|
I need to make a change to one of your variables. |
CDocument :
|
OK, I�m gonna send you a copy of my current DocVars package. Make whatever changes you need. When you�re done, send it back to me. |
CView :
|
The whole package?! Geeze! I just want to make one teensy weensy change. Can�t you just send me the one variable I want? |
CDocument :
|
DUDE! P-A-C-K-A-G-E. OK? |
CView :
|
Ok, but it seems like the hard way to do things. |
CDocument :
|
Quit yer complainin� and get back to work. |
CView :
|
Right, I�ve made the change to my DocVars copy. You want it? |
CDocument :
|
Yeah, send it back to me. |
CView :
|
What happens now? |
CDocument :
|
I make a unique copy. If the DocVars has pointer variables, I have to allocate memory for new pointers and then fill them up with the parameters from the DocVars you just sent me. Afterwards, I send it along to CUpdateMgr . He adds it to his list and sends a copy back to me when he�s done. |
CView :
|
You gotta be kiddin� me!! What a tangled bureaucracy!! You�re as bad as the Department of Motor Vehicles. |
CDocument :
|
Please just shut yer yapper and let me do my job. |
CDocument :
|
Hello, CUpdateMgr ? I got a new DocVars package for ya. Please update your list. |
CUpdateMgr :
|
Got it. I'm adding it to the end of the list. Done. Now I�m sending a copy back to you. |
CDocument :
|
Got it. Thanks. The package you returned to me is now my current DocVars. |
|
LATER THAT DAY... |
CMainFrame :
|
Hey, CDocument ! Some geek with a mouse just clicked the �UNDO� button. |
CDocument :
|
Thanks for the message. I�ll tell CUpdateMgr . |
CDocument :
|
Hey CUpdateMgr ! I just got an Undo request. |
CUpdateMgr :
|
Okeeday. Let me scroll back one place on the list of DocVars. There it is. Ok, I�m sending you the older DocVars. |
CDocument :
|
Got it, and it is now my current DocVars. |
|
MUCH LATER THAT DAY... |
CView :
|
Hey CDocument ! What you got? |
CDocument :
|
That all depends. What do you need? |
CView :
|
I just need to check the value of one of your DocVars variables. |
CDocument :
|
OK, I�m gonna send you a pointer to my current DocVars. |
CView :
|
Beautiful! |
CDocument :
|
But you leave them variables alone. NO CHANGES! Ya Hear, BOY!? |
CView :
|
Why not? |
CDocument :
|
Because you will completely disintegrate the good thang me and CUpdateMgr got goin� here, and you�ll ruin the whole Undo/Redo feature. |
CView :
|
Roger! No changes. Just lookin�. |
Using the code
CDocument
or Not
Although the example app uses the MFC document/view setup, the doc/view setup is not a requirement. The trick is to set a single class as the custodian. It doesn�t need to be a CDocument
. For example, a dialog-based app could use the main dialog class as the DocVars custodian.
Serialization
The example app uses MFC serialization. In order to make this possible, CDocVars
and all of the DocVars�s class-based variables must support serialization. Since CObject
supports serialization, I derived CDocVars
from CObject
. If you need DocVars variables based on custom classes, consider deriving them from CObject
. Just remember that when you derive a new class from CObject
, you will need to write a copy constructor for it. In addition, you will need to prepare an assignment operator for (=) and possibly others depending on your needs. See DocVars.h in the example app�s source to see how I did it.
Templates
The CUpdateMgr
uses a custom list class, CtObjectList
. This list class is a template class derived from CList
. For me, templates are a bit of a mystery. But one thing is certain, they are extremely convenient. In this example, I had to prepare an (=) assignment operator, and I overrode only the Serialize()
function. Please note that CtObjectList
expects to handle serializable types. In the present case, it handles CDocVars
types which are derived from the serializable CObject
class.
Infinite Undo
The Serialize()
override function in the CUndo_Redo_DemoDoc
class is setup so you can preserve all Undo states to the origination of the document. That is, no matter how many editing sessions you have, you will still be able to Undo to the very beginning. The down side of this feature is that you can end up with pretty hefty files. If this is a problem, I recommend that you place a limit on the number of DocVars in the UpdateMgr�s list and make this a user-configurable value. Or you could simply disable this intersession feature by commenting-out the #define INFINITE_UNDO
statement in Undo_Redo_DemoDoc.cpp.
Pointers
Don�t forget that if you are using pointer variables in your DocVars
, you have to associate the pointers in each DocVars with unique spaces in memory. Otherwise, the DocVars pointer variables throughout the list of DocVars all will be pointing to the same memory address � which kinda defeats the purpose. I�ll leave it to you to figure out the best way to do that.
DocVarsGetByValue()
This function is intended to be used mainly as a way for the document to deliver a copy of its current DocVars. This is so the calling function can modify its copy of the DocVars set without directly affecting the document�s current DocVars.
DocVarsGetPointer()
Call this function when you need to find out the value of a variable in the document�s current DocVars. DO NO MODIFICATIONS of DocVars variables while in possession of this pointer. Otherwise, total chaos.
Example MSVS Project
I am supplying the example source code in the form of a Microsoft Visual C++ .NET Solution. Users of MSVC++ 6 might find it easier to create a new SDI/DocView Workspace and then simply add the files from the example.