Step 2: Handling events
You can find the source for Step 2 in the file STEP02.CPP in the directory
EXAMPLES\OWL\TUTORIAL. Step 2 introduces response tables, another very important
ObjectWindows feature. Response tables control event and message processing in
ObjectWindows applications, dispatching events on to the proper event-handling functions.
Step 2 also adds these functions.
Adding a window class
Add the response table to the application using a window class called TDrawWindow.
TDrawWindow is derived from TWindow, and looks like this:
class TDrawWindow : public TWindow
{
public:
TDrawWindow(TWindow* parent = 0);
protected:
// override member function of TWindow
bool CanClose();
// message response functions
void EvLButtonDown(uint, TPoint&);
void EvRButtonDown(uint, TPoint&);
DECLARE_RESPONSE_TABLE(TDrawWindow);
};
The constructor for this class is fairly
simple. It takes a single parameter, a TWindow * that indicates the parent window
of the object. The constructor definition looks like this:
TDrawWindow::TDrawWindow(TWindow *parent)
{
Init(parent, 0, 0);
}
The Init function lets you initialize
TDrawWindow's base class. In this case, the call isn't very complicated. The only thing
that might be required for your purposes is the window's parent, and, as you'll see, even
that's taken care of for you.
Adding a response table
The only public member of the TDrawWindow class is its constructor.
But if the other members are protected, how can you access them? The answer lies in
the response table definition. Notice the last line of the TDrawWindow class definition.
This declares the response table; that is, it informs your class that it has a response
table, much like a function declaration informs the class that the function exists, but
doesn't define the function's activity.
The response table definition sets up your class to handle Windows
events and to pass each event on to the proper event-handling function. As a general rule,
event-handling functions should be protected; this prevents classes and functions
outside your own class from calling them. Here is the response table definition for
TDrawWindow:
DEFINE_RESPONSE_TABLE1(TDrawWindow, TWindow)
EV_WM_LBUTTONDOWN,
EV_WM_RBUTTONDOWN,
END_RESPONSE_TABLE;
You can put the response table anywhere in
your source file.
For now, you can keep the response table fairly simple. Here's a
description of each part of the table. A response table has four important parts:
The two macros in the
middle of the response table, EV_WM_LBUTTONDOWN and EV_WM_RBUTTONDOWN, are response table
macros for the standard Windows messages WM_LBUTTONDOWN and WM_RBUTTONDOWN. All standard
Windows messages have ObjectWindows-defined response table macros. To find the name of a
particular message's macro, preface the message name with EV_. For example, the macro that
handles the WM_PAINT message is EV_WM_PAINT, and the macro that handles the WM_LBUTTONDOWN
message is EV_WM_LBUTTONDOWN.
These predefined macros pass the message on to functions with
predefined names. To determine the function name, substitute Ev for WM_, and convert the
name to lowercase with capital letters at word boundaries. For example, the WM_PAINT
message is passed to a function called EvPaint, and the WM_LBUTTONDOWN message is passed
to a function called EvLButtonDown.
Event-handling functions
As you can see, two of the protected functions in TDrawWindow
are EvLButtonDown and EvRButtonDown. Because of the macros in the response table, when
TDrawWindow receives a WM_LBUTTONDOWN or WM_RBUTTONDOWN event, it passes it on to the
appropriate function.
The functions that handle the WM_LBUTTONDOWN or WM_RBUTTONDOWN
events are very simple. Each function pops up a message box telling you which button
you've pressed. The code for these functions should look something like this:
void TDrawWindow::EvLButtonDown(uint, TPoint&)
{
MessageBox("You have pressed the left mouse button",
"Message Dispatched", MB_OK);
}
void TDrawWindow::EvRButtonDown(uint, TPoint&)
{
MessageBox("You have pressed the right mouse button",
"Message Dispatched", MB_OK);
}
This illustrates one of the best features
of how ObjectWindows handles standard Windows events. The function that handles each event
receives what might seem to be fairly arbitrary parameter types (all the macros and their
corresponding functions are presented in Chapter 5 in the ObjectWindows Reference Guide).
Actually, these parameter types correspond to the information encoded in the WPARAM and
LPARAM variables normally passed along with an event. The event information is
automatically "cracked" for you.
The advantages of this approach are two-fold:
For example, both
WM_LBUTTONDOWN and WM_RBUTTONDOWN contain the same type of information in their WPARAM and
LPARAM variables:
EvLButtonDown and
EvRButtonDown also have similar signatures. The uint parameter of each function
corresponds to the key flags parameter. The values that are normally encoded in the LPARAM
are instead stored in a TPoint object.
Encapsulated API calls
You might notice that the calls to the MessageBox function look a
little odd. The Windows API function MessageBox takes an HWND for its first parameter. But
the MessageBox function called here is actually a member function of the TWindow class.
There are a large number of functions like this: they have the same name as the Windows
API function, but their signature is different. The most common differences are the
elimination of handle parameters such as HWND and HINSTANCE, replacement of Windows data
types with ObjectWindows data types, and so on. In this case, the window class supplies
the HWND parameter for you.
Overriding the CanClose function
Another feature of the TDrawWindow class is the CanClose function.
Before an application attempts to shut down a window, it calls the window's CanClose
function. The window can then abort the shutdown by returning false, or let the shutdown
proceed by returning true.
From the point of view of the application, this ensures that you
don't shut down a window that is currently being used or that contains unstored data. From
the window's point of view, this warns you when the application tries to shut down and
provides you with an opportunity to make sure that everything has been cleaned up before
closing.
Here is the CanClose function from the TDrawWindow class:
bool TDrawWindow::CanClose()
{
return MessageBox("Do you want to save?", "Drawing has changed",
MB_YESNO | MB_ICONQUESTION) == IDNO;
}
For now, this function merely pops up a
message box stating that the drawing has changed and asking if the user wants to save the
drawing. Because there's no drawing to save, this message is fairly useless right now. But
it'll become useful in Step 7, when you add the ability to save data to a file.
Using TDrawWindow as the main window
The last thing to do is to actually create an instance of this new
TDrawWindow class. You might think you can do this by simply substituting TDrawWindow for
TFrameWindow in the SetMainWindow call in the InitMainWindow function:
void InitMainWindow()
{
SetMainWindow(new TDrawWindow);
}
This won't work, for a number of reasons,
but primarily because TDrawWindow isn't based on TFrameWindow. For this code to compile
correctly, you'd have to change TDrawWindow so that it's based on TFrameWindow instead of
TWindow. Although this is fairly easy to do, it introduces functionality into the
TDrawWindow class that isn't necessary. As you'll see in later steps, TDrawWindow has a
unique purpose. Adding frame capability to TDrawWindow would reduce its flexibility.
The second approach is to use a TDrawWindow object as a client in a
TFrameWindow. This is fairly easy to do: the third parameter of the TFrameWindow
constructor that you're already using lets you specify a TWindow or TWindow-derived object
as a client to the frame. The code would look something like this:
SetMainWindow(new TFrameWindow(0, "Sample ObjectWindows
Program", new TDrawWindow)); With this approach, TFrameWindow
administers the frame window, leaving TDrawWindow free to take care of its tasks. This
makes for more discreet and modular object design. It also lets you easily change the type
of frame window you use, as you'll see in Step 10.
Notice that the new TDrawWindow construction in the
TFrameWindow constructor doesn't specify a parent for the TDrawWindow object. That's
because there isn't yet anything to be a parent. The TFrameWindow object that will be the
parent hasn't been constructed yet. TFrameWindow automatically sets the client window's
parent to be the TFrameWindow once it has been constructed.
Where to find more information
Here's a guide to where you can find more information on the topics
introduced in this step:
|