Introduction
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.