Step 6: Painting the window and adding a menu
There are a few flaws with the application from Step 5. The biggest
problem is that the drawing window doesn't know how to paint itself. To see this for
yourself, try drawing a line in the window, minimizing the application, then restoring it.
The line you drew is gone.You can find the source for Step 6 in the files STEP06.CPP and
STEP06.RC in the directory EXAMPLES\OWL\TUTORIAL.
Another problem is that the only way the user can access the
application is with the mouse. The user can either press the left button to draw a line or
the right button to change the pen size.
In Step 6, you'll make it possible for the application to remember
the contexts of the window and redraw it. You'll also add some menus to increase the
number of ways the user can access the application.
Repainting the window
There are two problems that must be dealt with when you're trying to
paint the window:
Storing the drawing
In the earlier steps of the tutorial application, the line in the
window was drawn as the user moved the mouse while holding the left mouse button. This
approach is fine for drawing the line, but doesn't store the points in the line for later
use.
Because the line is composed of a number of points in the window, you
can store each point in the ObjectWindows TPoint class. And because each line is composed
of multiple points, you need an array of TPoint objects to store a line. Instead of
attempting to allocate, manage, and update an array of TPoint objects from scratch, the
tutorial application uses the Borland container class TArray to define a data type called
TPoints. It also uses the Borland container class TArrayIterator to define an iterator
called TPointsIterator. The definitions of these two types look like this:
typedef TArray<TPoint> TPoints;
typedef TArrayIterator<TPoint> TPointsIterator;
The TDrawWindow class adds a TPoints
object in which it can store the points in the line. It actually uses a TPoints *,
a protected member called Line, which is set to point to a TPoints array created in
the constructor. The constructor now looks something like this:
TDrawWindow::TDrawWindow(TWindow *parent)
{
Init(parent, 0, 0);
DragDC = 0;
PenSize = 1;
Pen = new TPen(TColor::Black, PenSize);
Line = new TPoints(10, 0, 10);
}
TPoints
The Borland C++ container class library and the TArray and
TArrayIterator classes are explained in detail in Chapter 1 of the Class Libraries Guide.
For now, here's a simple explanation of how the TPoints and TPointsIterator container
classes are used in the tutorial application. To use the TArray and TArrayIterator
classes, you must include the header file classlib\arrays.h.
The TArray constructor takes three parameters, all ints:
Here's the statement that
allocates the initial array of points in the TDrawWindow constructor:
Line = new TPoints(10, 0, 10);
The array of points is created with room
for ten members, beginning at 0. Once ten objects are stored in the array, attempting to
add another object adds room for ten new members to the array. This lets you start with a
small conservative array size, but also alleviates one of the main problems normally
associated with static arrays, which is running out of room and having to reallocate and
expand the array.
Once you've created an array, you need to be able to manipulate it.
The TArray class (and, by extension, the TPoints class) provides a number of functions to
add members, delete members, clear the array, and the like. The tutorial application uses
only a small number of the functions provided. Here's a short description of each
function:
| The Add function adds a member to
the array. It takes a single parameter, a reference to an object of the array type. For
example, adding a TPoint object to a TPoints array would look something like this:
// Construct a TPoints array (an array of TPoint objects)
TPoints Points(10, 0, 10);
// Construct a TPoint object
TPoint p(3,4);
// Add the TPoint object p to the array
Points.Add(p);
|
| The Flush function clears all the
members of an array and resets the number of array members back to the initial array size.
It takes no parameters. To clear the array in the previous sample code, the function call
would look something like this:
// Clear all members in the Points array
Points.Flush();
|
| The GetItemsInContainer function
returns the total number of items in the container. Note that this number indicates the
number of actual objects added to the container, not the space available. For example,
even though the container may have enough room for 30 objects, it might only contain 23
objects. In this case, GetItemsInContainer would return 23.
|
TPointsIterator
Iterators-in this case the TPointsIterator type-let you move through
the array, accessing a single member of the array at a time. An iterator constructor takes
a single parameter, a reference to a TArray of objects (the type of objects in the array
is set up by the definition of the iterator). Here's what an iterator looks like when it's
set up using the Line member of the TDrawWindow class:
TPointsIterator i(*Line);
Note that Line is dereferenced because the
iterator constructor takes a TPoints & for its parameter, and Line is a TPoints
*. Dereferencing the pointer makes Line comply with the iterator constructor type
requirements.
Once you've created an iterator, you can use it to access each
object in the array, one at a time, starting with the first member. In the tutorial
application, the iterator isn't used very much and you won't learn much about the
possibilities of an iterator from it. But the tutorial does use two properties of
iterators that require a note of explanation:
Using the array classes
Once the Line array is created in the TDrawWindow constructor, it is
accessed in four main places:
Paint function
In standard C Windows programs, if you need to repaint a window
manually, you catch the WM_PAINT messages and do whatever you need to do to repaint the
screen. This might lead you to think that the proper way to repaint the window in the
TDrawWindow class is to add the EV_WM_PAINT macro to the class' response table and set up
a function called EvPaint.
You can do this if you want. However, a better way is to override the
TWindow function Paint. TDrawWindow's base class TWindow actually does quite a bit of work
in its EvPaint function. It sets up the BeginPaint and EndPaint calls, creates a device
context for the window, and so on.
Paint is a virtual member of the TWindow class. TWindow's
EvPaint calls it in the middle of its processing. The default Paint function doesn't do
anything. You can use it to provide the special processing required to draw a line from a
TPoints array.
Here is the signature of the Paint function. This is added to the
TDrawWindow class:
void Paint(TDC&, bool, TRect&);
where:
In the current case, you
always want to clear the window. You can also assume that the entire area of the drawing
needs to be repainted. The Paint function implements this basic algorithm:
- Create an iterator to go through the points in the line.
- Select the pen into the device context passed into the Paint function.
- If this is the first point in the array, set the current point to the coordinates
contained in the current array member.
- While there are still points left in the array, draw lines from the current point to the
point contained in the current array member.
The TDrawWindow::Paint
function now looks something like this:
void
TDrawWindow::Paint(TDC& dc, bool, TRect&)
{
bool first = true;
TPointsIterator i(*Line);
dc.SelectObject(*Pen);
while (i) {
TPoint p = i++;
if (!first)
dc.LineTo(p);
else {
dc.MoveTo(p);
first = false;
}
}
}
Menu commands
There are a number of steps you need to perform to add a menu choice
and its corresponding event handler to your application:
- Define the event identifier for the menu choice. By convention, this identifier is all
capital letters, and begins with CM_. For example, the identifier for the File Open menu
choice is CM_FILEOPEN.
- Add the appropriate menu resource to your resource file.
- Add an event-handling function for the menu choice to your class. The ObjectWindows 2.5
convention is to name this function the same name as the event identifier, except omitting
the underscore and using initial capital letters and lowercase letters for the rest. For
example, the function that handles the CM_FILEOPEN event is named CmFileOpen.
- Add an EV_COMMAND macro to your class' response table, associating the event identifier
with the event-handling function. This macro takes two parameters; the first is the event
identifier and the second is the name of the event-handling function. For example, the
response table entry for the File Open menu choice looks like this:
EV_COMMAND(CM_FILEOPEN, CmFileOpen),
The EV_COMMAND macro requires the signature of the event-handling function to take no
parameters and return void. So the signature of the event-handling function for the
File Open menu choice looks like this:
void CmFileOpen();
Adding event identifiers
You need to add identifiers for each of these menu choices. Here's
the definition of the event identifiers:
#define CM_FILENEW 201
#define CM_FILEOPEN 202
#define CM_FILESAVE 203
#define CM_FILESAVEAS 204
#define CM_ABOUT 205
These identifiers are contained in the file
STEP06.RC. The ObjectWindows style places the definitions of identifiers in the resource
script file, instead of a header file. This cuts down on the number of source files
required for a project, and also makes it easier to maintain the consistency of identifier
values between the resources and the application source code.
The actual resource definitions in the resource file are contained in a
block contained in an #ifndef/#endif block, like so:
#ifdef RC_INVOKED
// Resource definitions here.
#endif
RC_INVOKED is defined by all resource compilers,
but not by C++ compilers. The resource information is never seen during C++ compilation.
Identifier definitions should be placed outside this #ifndef/#endif block,
usually at the beginning of the file.
Adding menu resources
For now, you want to add five menu choices to the application:
Each of these menu choices needs to
associated with the correct event identifier; that is, the File Open menu choice should
send the CM_FILEOPEN event.
The menu resource is attached to the application in the
InitMainWindow function. You need to call the main window's AssignMenu function. To get
the main window, you can call the GetMainWindow function. The InitMainWindow function
should look like this:
void InitMainWindow()
{
SetMainWindow(new TFrameWindow(0, "Drawing Pad", new TDrawWindow));
GetMainWindow()->AssignMenu("COMMANDS");
}
Adding response table entries
Each event identifier needs to be associated with its corresponding
handler. To do this, add the following lines to the response table:
EV_COMMAND(CM_FILENEW, CmFileNew),
EV_COMMAND(CM_FILEOPEN, CmFileOpen),
EV_COMMAND(CM_FILESAVE, CmFileSave),
EV_COMMAND(CM_FILESAVEAS, CmFileSaveAs),
EV_COMMAND(CM_ABOUT, CmAbout),
Adding event handlers
Now you need to add a function to handle each of the events you've
just added to the response table. Because these functions will eventually grow rather
large, you should declare them in the class declaration and define them outside the class
declaration.
The declarations of these function should look something like this:
void CmFileNew();
void CmFileOpen();
void CmFileSave();
void CmFileSaveAs();
void CmAbout();
Implementing the event handlers
The last step in implementing the event handlers is defining the
functions. For now, leave the implementation of these functions to a bare minimum. Most of
them can just pop up a message box saying that the function has not yet been implemented.
The functions that are set up this way are CmFileOpen, CmFileSave, CmFileSaveAs, and
CmAbout. Here's how these functions look:
void
TDrawWindow::CmFileOpen()
{
MessageBox("Feature not implemented", "File Open", MB_OK);
}
The only function that's implemented in this step
is the CmFileNew function. That's because it's very easy to set up. All that needs to be
done is to clear the array of points and erase the window. The CmFileNew function looks
like this:
void
TDrawWindow::CmFileNew()
{
Line->Flush();
Invalidate();
}
Where to find more information
Here's a guide to where you can find more information on the topics
introduced in this step:
|