How do I? - Part 1

From EDM2
Jump to: navigation, search
How Do I? / Part
1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19

by Eric Slaats

Hi and welcome to "How Do I?". In this column, simple Presentation Manager programming problems and philosophies will be discussed. How Do I? is aimed at people who are interested in PM programming or are simply curious what makes PM programs tick. To understand the topics covered here, a little programming experience (preferably in C++) is recommended.

This month we'll take a look at a some basics needed in every PM program. In other words, how do I create a window?

Is PM programming easy?

I have a background in C and Unix, but when I took it upon myself to learn how to program for the Presentation Manager (two years ago) I was shocked with the number of possibilities. There was so much ground to cover I really didn't know where to start. When I took a look at the IPF files which describe the OS/2 API (programming interface), I didn't understand most of the functions described, let alone the interaction between the different pieces of a PM program. There was talk of messages, events, GPI, WPS, SOM, etc. Simply dazzling.

What was lacking for me then was a simple instruction of what everything meant and how things interact. So here is a look at some simple basics of what makes a Presentation Manager program tick.

The Event paradigm

The OS/2 user shell is a Graphical User Interface (GUI) called the Workplace Shell (WPS). The graphical subsystem on top of which the WPS is build is called the Presentation Manager (PM). The PM provides graphics functions, management of the screen, Clipboard, DDE and other communications. If a graphical program for OS/2 is built, it is built by using the Presentation Manager.

GUI's are modelled on a event driven paradigm and PM programs work by the grace of events. This means to understand a PM program we've got to understand the event driven paradigm. In an event driven system actions by programs are caused by events. Normally the interaction of the user with the system generates a number of events. For example:

  • Activation of a menu option
  • Moving of the mouse
  • Resizing of a window
  • Hitting a key
  • Dragging an object
  • Closing a window
  • Clicking on a window area

As you can see, virtually every action we undertake within the PM will cause an event. When an event occurs, it causes the operating system to send a message to the program. These messages give the program the means it needs to act upon an event. So rather than following a sequential program flow, PM programs send, receive and respond to messages (events).

The Presentation Manager is organized around the concept of windows. Every area on an OS/2 screen is occupied by a window; every control in OS/2 is a window. So a button, a title-bar, an entryfield, a titlebar icon, a menu-item, etc. are all windows. This leads to the conclusion that a "normal" application window is built from a number of different windows and this is indeed the case.

The windows of an application are the means by which the application and the user interact. Every event a user causes is the result of an action on a window.

How does the PM know where to send the messages that are being generated by using the system? For this, every application creates a message queue. All messages generated by a PM program's windows will be sent to this queue. A message queue is actually a place where messages are stored until they are handled (there are some subtle differences, but they are beyond the scope of this article).

Before we discuss how messages are handled by a PM program let's see how a message looks. Messages in PM are described by the queue message structure (QMSG):

typedef struct _QMSG
        {
        HWND    hwnd;   // Window handle the message is addressed to
        ULONG   msg;    // The message
        MPARAM  mp1;    // Message parameter 1
        MPARAM  mp2;    // Message parameter 2
        ULONG   time;   // Timestamp when the message occurred
        POINTL  ptl;    // Mouse position when the message occurred
        ULONG   reserved;
        } QMSG;

The first four members of this structure are the ones we're really interested in. For people who never did any PM programming these members may look a little awkward, so let's explain them.

When programming for the Presentation Manager a large number of extra data types are used. They are defined in the header files you get with your compiler. In the above structure a number of them are mentioned.

HWND hwnd. This is called a window-handle (it is actually a unsigned long). It holds a 32 bit number that uniquely identifies a window on our desktop. The hwnd member in the message identifies the recipient of the message.

ULONG msg. ULONG is the OS/2 type for an unsigned long. In the above message structure the ULONG msg contains a 32-bit number that is the message. All messages in the PM system are actually numbers, but we call them by the names defined in the PMWIN.H header file. Many of the message names begin with WM_ (Window Message). Some examples of messages as they are defined in PMWIN.H are:

WM_COMMAND      // A menu-item or Button is clicked
WM_CHAR         // A keyboard key is hit
WM_MOUSEMOVE    // The mouse is moved
WM_PAINT        // Repaint (a part) of the window

MPARAM mp. Actually the MPARAM type is also a ULONG. A message can use the two message parameters to store information needed to process the message. This can be all kinds of information, for example, the identity of the menu-item that caused the event, or the part of the window that needs to be repainted.

Two ULONG may seem a little cramped if you want to pass a lot of information, but it's not. A 32-bit number can be a pointer to a memory area somewhere on the system. So you can pass virtually any amount of information with a message (just pass the pointer).

The message loop

Now that we know how a message looks, where it is kept before processing (the queue) and how it ends up there, we're ready to process the message.

After a program creates a message queue, it can create windows. All the messages for the windows created in the program's thread are sent to that message queue. We can extract messages from the message-queue (in FIFO order) with a small piece of code that almost every PM program has. It is called the message loop. This message loop will, in its simplest form, look like this:

QMSG qmsg; // Holds one message

while (WinGetMsg (hab, &qmsg, NULLHANDLE, 0, 0)
        WinDispatchMsg (hab, &qmsg);

WinGetMsg is the API call we use to take messages out of the queue. The message which is fetched from the queue is put in the variable qmsg. The while loop will continue to execute until a message called WM_QUIT comes along. This will also terminate your application. (WM_QUIT is put in the message queue if you select "close" from the system-icon menu of an application.) The last three parameters of WingetMsg are set to zero.

The second line in this small piece of code is the message dispatcher. The WinDispatchMsg function will send the message to the appropriate window. What happens is that a piece of code called the window-procedure handles the message. Before we take a look at the window procedure we need to know how a window procedure is attached to a window. For that we need to know how a window is created.

Creating a window.

OS/2 has a number of API's to handle the creation and destruction of windows. For this introduction program we need two of them, one for creation and one for destruction at the moment the program terminates. The easiest way to create a standard frame window with everything in it is to call WinCreateStdWindow. In our basic program, the call to this API will look like this (a number of variable names have been chosen):

hwndFrame = WinCreateStdWindow (HWND_DESKTOP,     // Parent  (HWND)
                                WS_VISIBLE,       // Style (visible)  (ULONG)
                                &flFrameFlags,    // Creation flags  (PULONG)    
                                "SampleClass",    // Class name  (PSZ)
                                "Titlebar Text",  // Titlebar text  (PSZ)
                                0,                // Client style  (ULONG)
                                NULLHANDLE,       // Resource handle  (HMODULE)
                                0,                // Frame ID  (ULONG)
                                &hwndClient);     // Client handle  (PHWND)

In the comment line after each parameter for this API is a short explanation as well as the data type it has. Again we run into some data types we don't know. Only those which are needed now are explained below; the rest will be attended to in a future column.

HWND_DESKTOP. The first parameter is the handle to the parent window of the frame window we're creating. The parent window is the window in which the window that is created will be displayed. In most cases this will be the desktop window (this is how Multiple Document Interface's can be created).

WS_VISIBLE. Every window can be tailored to a style. We'll be looking at those in the future. For now we only need to tell the PM that this window has to be visible.

&flFrameFlags. This parameter is a pointer to a ULONG that contains information about how the window has to be created. With this one you can do a lot! Every bit in this ULONG has a meaning. For example the first bit means, "Add a title bar to the frame window." In the PMWIN.H header file a number of identifiers are defined. By OR-ing them together, the ULONG has a number of its bits set. This way the PM knows how to create the frame window. Our little program uses the following creation flags (most of them will speak for themselves):

ULONG flFrameFlags = FCF_TITLEBAR   |FCF_SYSMENU |FCF_SHELLPOSITION |
                     FCF_SIZEBORDER |FCF_MINMAX  |FCF_TASKLIST;

pszClient. This is a string containing the window's Class name. This is needed to attach a window procedure to this window. We'll attend to that in a minute.

The next three parameters are set to 0. We won't do anything with them at the moment. In future columns these parameters will play a more profound role and will be viewed in depth.

&hwndClient. If the call to WinCreateStdWindow is completed, hwndClient will contain the handle to the client window. This is the area contained within the Frame. Most likely this is the area where most of your application will be. For example, if your application is an editor, the display and handling of the text will be done in the client area.

The window procedure.

Every window is based on a certain window class. It is the window class that defines the window procedure for a window. For example a Button is a window of the class WC_BUTTON. Within OS/2 there are default window procedures present for every type (class) of window OS/2 supports.

If you write your own application, it is very likely that you want to handle most events your application will generate. For example, your application will probably have a menu. Every time a menu-item is activated a message will be sent to the window procedure of your application's main window. But of course, the PM can't predict what your program does. This means you've got to create your own window procedure and tell the PM where that window procedure is. This is done by registering your window. The following line from the sample program will register our class.

WinRegisterClass (hab, "SampleClass", ClientWndProc, CS_SIZEREDRAW, 0);

Most important are the second and third parameter. The second gives the class name. Note that we've used this name in the WinCreateStdWindow. (Normally a separate variable or define is used to set the name. This is easier in maintenance and it's a better programming style.) The third parameter is the window procedure function pointer. This means our program must have a window procedure named ClientWndProc.

The window procedure itself looks like a big case statement which handles all the messages we want to handle. By default it will call the default window procedure for normal windows in the OS/2 kernel.

Next time we will take a closer look at the window procedure. For now we just present it. For our sample program, the window procedure looks like this:

//----------------------------------------------------------------
//Window procedure
//----------------------------------------------------------------

MRESULT EXPENTRY ClientWndProc (HWND hwnd, ULONG msg, MPARAM mp1,
MPARAM mp2)
     {
     switch (msg)
          {

//--------------------------------------------------------------------
          // Fill client with default color

//--------------------------------------------------------------------
          case WM_ERASEBACKGROUND:
          return MRFROMSHORT(TRUE);
          }
     return (WinDefWindowProc (hwnd,msg,mp1,mp2));
     }

Putting it all together.

The most important ingredients necessary for creating a simple window are discussed above. However, some have been left out. If a program is started, we need a message queue. Also, the PM has to assign a handle (HAB) to a program. This handle is necessary to reference several different instances of the same program. The initialization code looks like this:

//--------------------------------------------------------------------
// Initialize application and create message queue
//---------------------------------------------------------------------
hab = WinInitialize (0);                // Init application
hmq = WinCreateMsgQueue (hab, 0);       // Create message queue

After the message loop terminates (WM_QUIT), we've got to clean up after the program. This means destroy the window, destroy the message queue and tell the PM the HAB isn't valid anymore. The following code takes care of business:

//-------------------------------------------------------------------
// Clean up (destroy window, queue and hab)
//-------------------------------------------------------------------
WinDestroyWindow (hwndFrame);
WinDestroyMsgQueue (hmq);
WinTerminate (hab);

Click howdoi.zip here (ZIP, 12.8k) to download the source code for the whole program and the resulting .EXE file, which shows a normal window with titlebar, min/max buttons, a title-bar-icon, and a sizing border. All these components are active which means the window can be dragged, minimized, maximized and sized. You'll note that the client area won't accept color drops and the new size and place won't be remembered, etc. A lot of work remains until a full application emerges.

Next month we'll continue this example and complete a template program that will be used as a base for future columns. This month the emphasis was on the main window, next month we'll take a closer look at the window procedure and some basic messages.

If there are any questions, please mail them and I'll try to answer them next month.