Programming for the OS/2 PM in C:The window message processor

From EDM2
Jump to: navigation, search

by Rick Papo

<< Introduction Continues - Index - Window Resources & Controls >>

Part III. - The window message processor

In last month's article we went through the basic steps required to create a simple windowed application. It did nothing special except exist, though by building itself upon the basic Frame window class it inherited the natural behaviours of that class: it occupied a rectangular region of the screen, had a frame which could be resized with the mouse, it had a system menu from which you could close, move, size, minimize, maximize and restore the window, as well as summon the system task list window. In addition, it added itself to the system task list. That was all nice, but not really useful yet. The window has no behaviour of its own other than that provided by the basic Frame window class. This month we will begin to extend the window's behaviour by adding a client window of our own design to the frame.

A Presentation Manager standard window is built around a frame window. The frame window, depending on the style options selected, will have a number of child windows whose positions are determined by the frame window. These child windows are of various classes and behaviours, and can include the Title-Bar, the System Menu, Minimize and Maximize/Restore buttons, and horizontal and vertical scroll bars. In more complex versions of OS/2 such as those used in Japan or China, there may be yet more control windows created as children of the frame window. All these windows are created, positioned and coordinated by the frame window as part of its normal behaviour. Most of this behaviour should not be modified by the programmer, but one of the windows is specifically for the programmer to customize: the client window.

In a standard window, the client window is the rectangular region bounded on the sides and below by the frame border and/or the horizontal and vertical scroll bars (depending on the frame style), and above by a menu, the title bar, or by the frame border (again depending on the frame style). The client window can be developed from almost any window class, but normally is done by extending the functionality of the system default client window processor function WinDefWindowProc. The client window is created in a three step process: the programmer must first create a new class message processor, then the new class must be declared to the system, and then finally an instance of the window class must be created.

A class message processor is a function of the following form:

MRESULT EXPENTRY Processor (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);

All the parameters and the result are 32 bits, to speed processing. The first parameter is the handle of the window to which the message is directed. This must be specified because the multiple instances of the window class can be created, and the same message processor is used for every one of them. The second parameter is the message number and is used to determine what the message means and how to interpret the two 32-bit parameters.

If you wanted to, you could create a complete window message processor in which every possible message supplied by the system would be processed validly, but that would be a lot of work and unnecessary. A much easier solution is to implement the window class as an extension of one already existing. The recommended method to make a class message processor is with a switch statement with cases for each message you want to process in a unique way, and to then pass all other messages to the message processor for the class you are basing your new class on. Sometimes you will entirely replace the message processing, at other times you will do your new processing first, at yet other times after the parent message processor is called. A normal class message processor function will have the following form:

MRESULT EXPENTRY MessageProcessor (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2) {
      switch (msg) {
         case MESSAGE1: {
            /* Replacement code for handling MESSAGE1. */
            return (0); }
         case MESSAGE2: {
            WinDefWindowProc (hwnd, msg, mp1, mp2);
            /* Additional code for handling MESSAGE2 after default processing. */
            return (0); }
         case MESSAGE3: {
            /* Additional code for handling MESSAGE2 before default processing. */
            break; }
      } /* endswitch */
      return (WinDefWindowProc (hwnd, msg, mp1, mp2));
}

Once you have created your class message processor, you must add code to register the new class with the system. The system function WinRegisterClass is used for this. The function allows you to name the class, specify the message processor, set some class style attributes, and have the system allocate some additional memory to be kept with each window instance that is created. You can find an example of how to call this function in the sample code at the end of the article.

Finally, having created the message processor and registered it under a class name, it is possible to create an instance of the new window. This is done in exactly the same way as the frame window was created, except that this time instead of specifying WC_FRAME as the window class, we now give the name used in the WinRegisterClass call. In the case of the client window attached to a frame window, we must also specify the frame window as the client window's parent and owner, and the client window ID must be FID_CLIENT, a system-defined constant. The client window should be created before the frame window is sized, because one of the functions of the frame window is to managed the location and size of its child windows, including the client window. If you were to size the frame window first, then create the client window, it would not be sized nor positioned correctly ... until the next time you resize the frame.

It is possible to proceed this far without writing any new functionality into the client window message procedure, simply passing all messages to the default window processor, WinDefWindowProc. Such a window would be rather strange to see: since the client window completely covers the inner portion of the frame window, the frame window does not fill that area, leaving that job to the client window. The only problem is that the client window is not painting it! When the window first appears it looks like a frame with a transparent window in it, letting what is below show. This is only an illusion, though. If you tried to move the window around you would quickly see that the window now carries around a copy of what was there before with it.

Therefore, the first thing that needs doing with a replacement client window based on WinDefWindowProc is to add code for the window to update its visible contents. This is called painting the window. In most programs this operation is performed whenever the system asks for it. This will happen when the window becomes visible, when it grows, and in some cases whenever it changes size or location. When the operating system wants a window to paint itself, it will send the WM_PAINT message to it. A case for WM_PAINT must be added to the switch statement within the message processor.

Processing WM_PAINT has a certain minimum amount of work that must be done: the function WinBeginPaint must be called to obtain a handle to the window's presentation space. The presentation space is what all graphic operations (move, draw, fill, etc) are performed on. Before returning from the message processor function the function WinEndPaint must also be called, to notify the system that you have finished with the presentation space and release it back to the system. If your message processor does not call these two functions the system will continue to treat the window as not having being updated yet.

Once you have a presentation space handle, then nearly all of the graphics functions available from OS/2 are usable. To clear the window to a solid colour you must do four things: set the colour, determine the window size, set your graphic position to one of the corners of the window and then fill a box to the opposite corner of the window. The following sequence of instructions would be performed:

// Get the window presentation space for painting on.
HPS hPS = WinBeginPaint (hwnd, 0, 0);

// Set the current color to white.
GpiSetColor (hPS, CLR_WHITE);

// Find out how large the window is.
RECTL Rectangle; WinQueryWindowRect (hwnd, &Rectangle);

// Position to the lower-left corner of the rectangle.
POINTL Point; Point.x = Rectangle.xLeft; Point.y = Rectangle.yBottom;
GpiMove (hPS, &Point);

// Fill a box to the upper-right corner of the rectangle.
Point.x = Rectangle.xRight; Point.y = Rectangle.yTop;
GpiBox (hPS, DRO_FILL, &Point, 0, 0);

// Release the presentation space and let PM know you're done.
WinEndPaint (hPS);

There are many other things you could do in processing the WM_PAINT message. In the example code that accompanies this article you will find a slightly more elaborate example which paints the Hungarian national flag within the window. It differs from the above example mainly in using 24-bit RGB colours instead of the default palletised colour scheme.

<< Introduction Continues - Index - Window Resources & Controls >>