Responding to standard messages with ObjectWindowsBy now, most everyone knows that writing a Windows application can be a very complex process. Although programmers encounter many problems when writing their first Windows application, writing functions to handle one of the many Windows messages can be particularly frustrating. To address some of the difficulties Windows developers encounter, Borland provides the ObjectWindows Library (OWL). OWL is a Windows application framework that implements many of the most common parts of a robust Windows program. With OWL (part of the Borland C++ package), Borland has created a new technique for writing functions that respond to Windows messages. In this article, we'll show you how to use OWL and Borland C++ to create a simple Windows application with a window that responds to a specific message. Out with the old, In with the newIf you've been writing Windows applications in C, you're familiar with writing window procedures. Figure A shows the skeleton of a typical C window procedure. Figure A - In C, window procedures are basically switch statements. long FAR PASCAL MainWindProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_COMMAND: switch(wParam) { // Command Processing } break; case WM_PAINT: // Repaint the window break; case WM_LBUTTONDOWN: // User pressed left mouse button break; case WM_RBUTTONDOWN: // User pressed right mouse button break; } return 0; } In an OWL 2.0 application, you'll write a separate function for the code that responds to a given message. By moving the message response code to the individual function level, we can override a base class's message-handling function in a derived class. This may not seem to be a big deal at first. However, when you begin using inheritance to build a family of classes, you can put the behavior that's common to all the classes in a base-class member function. It's much easier to debug such a function than to create common behavior by repeating the message-handling switch statements in each window's window procedures. Handling messages by using OWLTo use the OWL framework in your application, you'll first need to add an #include statement for the appropriate OWL header files. These files declare all the classes and macros necessary to create an OWL application. Next, you'll declare a new class that will respond to Windows messages. To inherit the OWL event-handling behavior, you'll need to derive your class from the TEventHandler class or one of its descendants. Inside your event-handling class, you'll declare the function that responds to the desired Windows message. However, OWL expects this function to have a specific argument list and return type (also known as the function's signature). Fortunately, Chapter 2 of the ObjectWindows Reference Guide provides a number of tables that identify the correct signature for particular message-response functions. To find the correct response-function declaration for a given Windows message, add EV_ to the beginning of the message name and then look for that name in one of the tables in Chapter 2. This name_ plus message name also the name of the OWL macro you'll use later to respond to that particular message. Next, you'll add to the event-handling class the declarations for any data members or member functions you want the class to have. You've almost finished with the OWL requirements for the class declaration, so you can focus here on functionality that isn't related to Windows messages. To complete the class declaration, you'll add the DECLARE_RESPONSE_TABLE macro
inside the class declaration brackets ({}). The format of this macro is DECLARE_RESPONSE_TABLE(className) where className is the name of your class. When you pass the name of your class to this macro, the macro will add the appropriate OWL features to your class's declaration. Now, you need to define the Response Table for this class. To begin defining the table, count your event-handling class's immediate event-handling base classes. (Unless you're using multiple inheritance, you'll usually have just one.) An event-handling base class is one that derives from the OWL TEventHandler class. Add the DEFINE_RESPONSE_TABLE macro to one of your source files and then add
the base-class count to the end of the macro name if the count is greater than zero.
You'll pass this macro the name of your class and name its event-handling base class (or
classes), as we did in the following line: DEFINE_RESPONSE_TABLE1(TMyClass,TEventHandler) Here, TMyClass is our event-handling class, and TEventHandler is its only immediate base class that handles Windows events. Because there's a single event-handling base class, we added 1 to the end of the macro name. Immediately following this macro, you'll add a macro for each Windows message your class will respond to. As described earlier, you form the name of the OWL message macro by adding EV_ to the name of the corresponding Windows message. When you enter the macro names, be sure to insert a comma after each one. If you forget to include the comma, the compiler will report errors for this line. When you finish adding the message macros, you'll add the END_RESPONSE_TABLE macro. Be sure to add a semicolon after this macro, or the compiler will complain. Finally, include the OWL library as part of your project (if you're using the IDE) or make file (if you're compiling from the command line). If you're compiling from the Borland C++ Integrated Development Environment (IDE), select the OWL check box in the Standard Libraries section of the TargetExpert dialog box. If you've already created a target and you want to see if it uses the OWL library, you can still open the TargetExpert dialog box. Right-click on the target name in the Project window and choose TargetExpert... from the pop-up menu, and the TargetExpert dialog box will appear. Now, let's create a Windows application that responds to the message Windows sends when the user presses the left mouse button. To make it easier to follow this code and to allow you to see the power of the OWL message-response strategy, we'll make this example as simple as possible. Writing a Message-Handling ApplicationLaunch the Borland C++ 4.0 IDE. When the IDE main window appears, choose New Project... from the Project menu. When the New Project dialog box appears, enter \bc4\msgapp\msgapp.ide in the Project Path and Name entry field. Select Application [.exe] in the Target Type section's list box; choose Windows 3.x (16) from the Platform combo box. Finally, select the OWL check box in the Standard Libraries section. When you finish, the New Project dialog box should resemble the one shown in Figure B. Click the OK button to create the MSGAPP.IDE project. Open the MSGAPP.CPP source file by double-clicking on its icon in the Project window. When the editor window for MSGAPP.CPP appears, enter the source code from Listing A. Listing A: MSGAPP.CPP #include <owl\applicat.h> #include <owl\framewin.h> class TMsgWindow : public TWindow { public: TMsgWindow(TWindow* parent = 0); { Init(parent, 0, 0);} protected: void EvLButtonDown(uint, TPoint&); DECLARE_RESPONSE_TABLE(TMsgWindow); }; DEFINE_RESPONSE_TABLE1(TMsgWindow, TWindow) EV_WM_LBUTTONDOWN, END_RESPONSE_TABLE; void TMsgWindow::EvLButtonDown(uint, TPoint&) { MessageBox("WM_LBUTTONDOWN Message", "Responding to Message", MB_OK); } class TMessageApp : public TApplication { public: TMessageApp(){} void InitMainWindow() { SetMainWindow(new TFrameWindow(0, "Message Response Program", new TMsgWindow)); } }; int OwlMain(int, char* []) { return TMessageApp().Run(); } This is all the code you need to create an OWL-based Windows application that responds to the WM_LBUTTONDOWN Windows message. Before you can compile and run this application, you'll need to fill in the Module Definition file MSGAPP.DEF. Open the Module Definition file by double-clicking on the MSGAPP.DEF icon in the project window. When the editor window for this file appears, enter the definition from Listing B. Listing B: MSGAPP.DEF
The definitions in this file are the default values Borland C++ 4.0 uses if you don't have a Module Definition file in your project. Adding this file prevents you from seeing a warning from the Linker. Now, let's look at the sections of the source code that respond to the WM_LBUTTONDOWN message. Examining the codeThe first two lines of the MSGAPP.CPP file are #include statements that tell the preprocessor to embed the contents of the OWL header files in this file. The APPLICAT.H file declares the OWL TApplication class, and the FRAMEWIN.H file declares the TWindow and TFrameWindow OWL classes. Next is the declaration of the TMsgWindow class. This is the class we're
creating to respond to Windows messages. Since we've publicly derived this class from the TWindow
class with the line class TMsgWindow : public TWindow it automatically inherits all the default behavior of the TWindow class. Inside the body of the TMsgWindow class declaration, we declare a constructor
and a message-response function. You can find the format and name of this message-response
function in Chapter 2 of the ObjectWindows 2.0 Reference Guide. In Table 2.2 from
that chapter, you'll find a line containing EV_WM_LBUTTONDOWN and void EvLButtonDown(uint modKeys, TPoint& point) You determine the name of this macro by adding EV_ to the WM_LBUTTONDOWN message name. Since we won't use the parameters modKeys or point that OWL passes to this message, we've left the names of those parameters out of our function. We end the TMsgWindow class declaration by adding the line DECLARE_RESPONSE_TABLE(TMsgWindow); This macro expands during compilation to add a number of items to the TMsgWindow class. These additions to the class will allow OWL to call our message-response function at runtime. However, the implementation of the DECLARE_RESPONSE_TABLE macro and the macros that follow is quite complex. If you want to understand what the preprocessor does with these macros, see Under the hood of OWL - Understanding the Response Tables in ObjectWindows. Immediately after the class declaration, you'll see three more macros. Together, these macros sequentially do the following: define the Response Table for the TMsgWindow class, tell OWL that the TMsgWindow class contains a function that responds to the WM_LBUTTONDOWN message, and end the Response Table definition. Implementing our message-response function TMsgWindow::EvLButtonDown() is
fairly simple. When OWL calls this function, we'll merely display a message box named Responding
to Message, which contains the message WM_LBUTTONDOWN Message and an OK button. The remainder of the MSGAPP.CPP program derives from the OWL class TApplication a new application class (TMessageApp) that will use an object of our TMsgWindow class as a client to its main window. Then the program creates an object of the TMessageApp class as part of the OwlMain() function that all OWL framework applications use. If you've ever created a Windows application in C, you're probably surprised at how little code we've shown here. That's the power of the OWL library. Now, let's try out our new Windows application. Testing MSGAPPTo compile and run MSGAPP.EXE, double-click on its name in the Project window. When the IDE finishes compiling and linking the program, its main window appears, as shown in Figure C. Now, click anywhere inside the main window with the left mouse button. As soon as you press the button, you'll see the Responding to Message dialog box, as shown in Figure D. Click the OK button to close this dialog box. To exit MSGAPP.EXE, double-click on the main window's System menu icon. ConclusionObjectWindows 2.0 presents several significant features to the Windows application developer, but the new message-handling architecture is one of the most valuable. Using ObjectWindows 2.0, you can create your own family of classes that respond to Windows messages. You'll be able to move common event-handling functionality to one class and therefore reduce the amount of time you spend tracking down many message-related bugs. |
|