How do I? - Part 2

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 Again. Welcome to the second column on PM programming. In this column simple Presentation Manager programming problems and philosophies will be discussed. This column is aimed at people who are interested in PM programming or are simply curious what makes PM programs tick. To understand this column, a little programming experience (preferably in C++) is recommended.

Last month we looked at the event paradigm. This means messages. It was also explained how messages are sent to and handled by windows. A few messages like WM_QUIT were mentioned in the previous column.

This time we'll take a closer look at some messages. While we're still in the process of setting up a basic application window, we'll take a look at two messages used in the creation of a frame window. The basic program we created last month will be used as a starting point for our new explorations. After this article we'll be able to create a simple control in our frame window (actually on the client area) and size/place it appropriately.

Get the message?

First let's refresh our memories. Messages are data-structures that are sent to a window procedure (queue at first) upon an event. For example, a button-click, a mouse-click on a window or the resizing of the window. Messages can also be sent to a certain window by a program, to invoke a certain action. For example, if we would like to know the currently used font in a MLE control (MLE is the Multi-Line-Edit control; it's the control the e.exe editor is build around), we could send the MLM_QUERYFONT message to that MLE. The message would return this information to us.

Most of the time however, messages are the result of an event and by letting our window procedure act upon these messages we can have some influence on how events are handled.

Creation messages

In our basic program we created a frame window (actually 5 windows that are glued together) with a call to WinCreateStdWindow. All the things that happen because of that call can be seen as events in the OS/2 system. This means there are messages we can act upon when a window is created.

We do have a choice in this: we don't have to handle the messages, there is always the possibility to let the default window procedure handle these messages. We will take a look at two messages that can play a key roll at window-creation time. (Note that there are more messages that we can use in the frame creation process. These messages are more complex and won't be discussed yet.)

1) WM_CREATE

The first message we discuss is a message that always occurs in frame window creation. This is the WM_CREATE message. This is the first message a window will receive after its initialization. At the moment this message is received, the window isn't visible yet and doesn't have a size or place on the desktop.

What do we normally do with the WM_CREATE message? Well, there are a number of possibilities.

  • Program initiation tasks. At the moment the WM_CREATE message arrives, you'll know the window is being created. A number of settings your program uses can be retrieved at that time from the OS2.INI or a private INI file. These values can then be used in further processing.
  • The WM_CREATE message receives a pointer to a CREATESTRUCT. This structure contains all the parameters that were involved in the creation of the window and a number of them we used in the WinCreateStdWindow call. The CREATESTRUCT looks like this:
typedef struct _CREATESTRUCT 
        {
        PVOID   pPresParams;       //  Presentation parameters.
        PVOID   pCtlData;          //  Control data.
        ULONG   id;                //  Window identifier.
        HWND    hwndInsertBehind;  //  Window behind which the window is to be placed.
        HWND    hwndOwner;         //  Window owner.
        LONG    cy;                //  Window height.
        LONG    cx;                //  Window width. 
        LONG    y;                 //  Y-coordinate of origin.
        LONG    x;                 //  X-coordinate of origin.
        ULONG   flStyle;           //  Window style.
        PSZ     pszText;           //  Window text.
        PSZ     pszClassName;      //  Registered window class name.
        HWND    hwndParent;        //  Parent window handle.
        } CREATESTRUCT;

We can use this information for further initiation if we want to. Note that all this information can be obtained by querying the window, but by caching it (keeping it at hand), you could achieve some performance improvements. (I should note here that I've seldom seen programs that do this.)

  • The other message parameter of the WM_CREATE message contains a pointer to the so-called controldata (CTLDATA). This structure has a different form for every window-class (control) OS/2 provides. It can be used to take a few shortcuts (save lines of code) when setting up controls like buttons etc. (Note: we're not that far yet, we're still in the process of creating a simple frame window.) In future columns the CTLDATA will emerge again when we take a look at simple controls. The keyword here is LAZY. Don't do anything yourself which the system can do for you.
  • The last and most used function of the WM_CREATE message is to set up controls in the client area. Most applications operate this way. ***In our example of this month we will set up a Button in the centre of the Client area. We can do this by using the following code:
 WinCreateWindow(hwnd,              // Parent handle (Client area)
                WC_BUTTON,         // Window class (Button)
                "This is a button",// Window text
                WS_VISIBLE | BS_DEFAULT, // Button flags
                0, 0, 0, 0,        // client window has no size or position at this point
                hwnd,              // Owner handle (Client area)
                HWND_TOP,          // Z-order ontop
                100,               // Window ID 
                0,                 // Control
                0);                // Presentation Parameters 

The API call used to do this is the WinCreateWindow API. This is call that will create a single button. (Note that the WinCraeteStdWindow creates a complex set of windows.) Stuff like parents and owners as well as Z-order and Window ID's will be explained next month. For now it's essential that we notice that there are four 0's where the x, y, cx and cy parameters are. These parameters are used for sizing and placing a window. The client area at this point has no size or place, so giving the button a size or place in this window has no meaning at all. For example it could be placed out of the boundaries of the client window and thus be invisible and unreachable.

What happens next? After the window has been created, it should be placed on the Desktop with a certain position and size. Mainly we're interested in the size. If we obtain the size, we're able to resize and position the button we created. If a window is resized (giving it its initial size is also resizing), the PM will send a WM_SIZE message.

2) WM_SIZE

The WM_SIZE message is mostly used to resize the controls in the client-area, but it can also be used to prevent a window from being sized beyond or over a certain size. The example we work out here is giving the button a size and place. We want the button in the centre of the client area. This means we have to reposition the button every time the main window is resized. This is no problem because every time this happens a WM_SIZE is sent. So with one piece of code we can handle the creation situation as well as the resizing. If we used the info in CREATESTRUCT during WM_CREATE we would have to write two code pieces, one for creation and one for sizing!

Besides *placing the button in the middle of the client area* we want it to have a size 1/3 of the cx and cy of the client area.

The WM_SIZE message gives us just the information we need to do this. The WM_SIZE message passes the old window-size in mp1 and the new window-size in mp2. We're interested in the new window size. (Note: This is the size of the client-window, not the frame itself!)

In the previous article the MPARAM message parameters were discussed. These parameters were ULONG variables. (A ULONG is a 32 bit variable.) The cx and cy (x and y size) are shorts (16 bit). The mp1 and mp2 of the WM_SIZE are composed of two shorts and therefore make a ULONG. We can retrieve the two shorts out of this one ULONG with so-called helper-macros. The helper macros we need are SHORT1FROMMP and SHORT2FROMMP. So *to get* the values for place (within the client area) and size of the button we can use these macros.

To set the position and size of the button we'll use the WinSetWindowPos API. Through this API a window can be sized, moved, made invisible etc. We will only use it to resize and move the Button. The complete code for our little experiment looks like this:

case WM_SIZE:
     {
     short x = SHORT1FROMMP(mp2)/3;
     short y = SHORT2FROMMP(mp2)/3;

     WinSetWindowPos(hwndButton,        // Place and size button
                     HWND_TOP,          // ONTOP in Z-order
                     x,                 // x coord
                     y,                 // y coord
                     x,                 // cx size
                     y,                 // cy size
                     SWP_SIZE|SWP_MOVE);// Change size|move en Show
     }
break;

Try to figure out how this works. It would be advisable to look up the precise working of WinSetWindowPos. This is a really great API.

To see how this code performs, take the example.exe file (ZIP, 13.7k) and resize the frame window in a number of different ways. You'll see that the Button always is displayed in the middle and that it's sized to 1/3 of the height and 1/3 of the width.

Concluding Notes

In this article we've seen a way to set up a control in a client window and size it according to the size of the frame window (client area). In most cases however, the client area will contain only one control, the main working space of you application. For example, an edit control, a spreadsheet control, a drawing control, etc. Dialog boxes that give applications the opportunity to have a specialized interaction with the user aren't created in the way we described in this article. (Usually dialogs can't be sized anyway!)

I hear some of you thinking, "how about menus, status bars, toolbars, etc.?" Well, there are ways to implement these controls as part of the frame-window-conglomerate. This has the advantage that we don't have to provide the sizing, etc. In a future column I will take time to investigate and explain this.

Next month however, we will take a look at a way to add some extra controls to our frame window. This will be simple menus and scroll-bars. For now, if you have any comments, please let me know. Of course kudos are also welcome (grin).