Step 2: Handling events

Back Home Up Next

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 response table declaration in the class declaration.
The first line of a response table definition is always the DEFINE_RESPONSE_TABLEX macro. The value of X depends on your class' inheritance, and is based on the number of immediate base classes your class has. In this case, TDrawWindow has only one immediate base class, TWindow.
The last line of a response table definition is always the END_RESPONSE_TABLE macro, which ends the event response table definition.
Between the DEFINE_RESPONSE_TABLEX macro and the END_RESPONSE_TABLE macro are other macros that associate particular events with their handling functions.

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:

You no longer have to manually extract information from the WPARAM and LPARAM values.
The predefined functions allow for compile-time type checking, and prevent hard-to-track errors that can be caused by confusing the values encoded in the WPARAM and LPARAM values.

For example, both WM_LBUTTONDOWN and WM_RBUTTONDOWN contain the same type of information in their WPARAM and LPARAM variables:

WPARAM contains key flags, which specify whether the user has pressed one of a number of virtual keys.
The low-order word of the LPARAM specifies the cursor's x-coordinate.
The high-order word of LPARAM specifies the cursor's y-coordinate.

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:

Main windows are discussed in the chapter "Application and module objects" of the ObjectWindows Programmer's Guide.
Interface objects in general, such as windows, dialogs, controls, and so on, are discussed in the chapter "Interface objects" of the ObjectWindows Programmer's Guide.
Response tables are discussed in the chapter "Event handling" of the ObjectWindows Programmer's Guide.
Window classes are discussed in the chapter "Window objects" of the ObjectWindows Programmer's Guide.
Predefined response table macros and their corresponding event-handling functions are listed in Chapter 3 of the ObjectWindows Reference Guide.
 


Copyright © 1998-2001 Yura Bidus. All rights reserved.