Introduction to PM Programming - Feb 1994

Written by Larry Salomon Jr.

Introduction
The purpose of this column is to provide to the readers out there who are not familiar with PM application development the information necessary to satisfy their curiosity, educate themselves, and give them an advantage over the documentation supplied by IBM. Of course, much of this stuff could probably be found in one of the many books out there, but the problem with books in general is that they don't answer the questions you have after you read the book the first time through.

I will gladly entertain feedback from the readers about what was "glossed over" or what was detailed well, what tangential topics need to be covered and what superfluous crap should have been removed. This feedback is essential in guaranteeing that you get what you pay for.

It should be said that you must not depend solely on this column to teach you how to develop PM applications; instead, this should be viewed as a supplement to your other information storehouses (books, the network conferences, etc.). Because this column must take a general approach, there will be some topics that would like to see discussed that really do not belong here. Specific questions can be directed to the Scratch Patch, where an attempt to answer them will be made.

Last Month
Last month, we took a good hard look at a very typical main function for a PM application. We discussed in detail the WinCreateStdWindow function, as well as the many parameters and flags it can take. Finally, we started looking at window procedures and some of the more important messages that you - as a PM developer - will be interested in.

Since a large portion of PM application development is done in coding the window procedure, this month, we will begin to look more closely at this beast and see how, through the use of various messages, we can tame it in order to accomplish our own goals.

Presentation Spaces, the WM_PAINT Message, and Painting
Since the most important task you will probably have to perform will be painting your window, let us first look at the mechanism used to accomplish this. Reproduced below from last month's column is the simple window procedure that we saw. MRESULT EXPENTRY windowProc(HWND hwndWnd,                           ULONG ulMsg,                            MPARAM mpParm1,                            MPARAM mpParm2) {  switch (ulMsg) { case WM_PAINT: {        HPS hpsPaint; RECTL rclPaint;

hpsPaint=WinBeginPaint(hwndWnd,NULLHANDLE,&rclPaint);

WinFillRect(hpsPaint,&rclPaint,SYSCLR_WINDOW);

WinQueryWindowRect(hwndWnd,&rclPaint);

WinDrawText(hpsPaint,                    -1,                     "Hello world.",                     &rclPaint,                     CLR_BLACK,                     0,                     DT_CENTER|DT_VCENTER);

WinEndPaint(hpsPaint); }     break; default: return WinDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endswitch */

return MRFROMSHORT(FALSE); } Figure 1) Simple window procedure

Hmmm...The variable hpsPaint - of the type HPS - seems to be used quite a bit. What is an HPS? Looking into os2defs.h, we don't see much to help answer this. typedef LHANDLE HPS; typedef HPS *PHPS;

In case you haven't associated the title of this section with our question, an HPS is a handle to a presentation space. Okay, that helps a lot...not! What is a presentation space?

Presentation Spaces and Device Contexts
The easiest way to explain a presentation space is to use the familiar (and usually very vague, as you should remember from your college days :) "logical and physical" explanation. Consider an output device, which has numerous physical characteristics: the size of the output medium, the current colours, the current font, etc. All of these characteristics are physical characteristics, and they are collectively known as a device context.



However, as a programmer, you don't want to have to be cognizant of the types and brands of each output device; thus, OS/2 provides the concept of a logical device which has an analogous set of characteristics. This logical device is called a presentation space (HPS). When you draw on the HPS, PM automatically converts all of the logical characteristics to their physical equivalents.



The point is, and this is the only part you need to remember, any drawing is done on an HPS.

Invalidation
One of the many characteristics that an HPS has is called a clipping region. If you'll recall, last issue in the New Concepts section of this column, figure 5 illustrated many children clipped to their parent. While it was implied that clipping areas - or regions as they are called - are rectangular, this is not always the case.



Figure 4 illustrates a drawing of a box with no clipping applied. Applying a clipping region of a triangular shape yields the following (the clipping boundary is shown as a dotted line for illustrative purposes only):



Whenever any portion of your window needs repainting, the section that needs to be painted is said to be invalid. While PM could in fact provide a precise clipping region that outlines the invalid portion, for performance reasons, it instead provides the coordinates of the smallest rectangle to completely bound the invalid area. Why are we bothering with all of this? Keep reading...

The WM_PAINT Message
Whenever your window has an invalid region, PM sends your window procedure a WM_PAINT message.

WM_PAINT
This message occurs when an application needs to repaint itself. Parameters param1 Reserved. NULL  Reserved value. param2 Reserved. NULL  Reserved value. Returns reply Reserved. NULL  Reserved value. Since we have already said that, in order to do any drawing, you need an HPS, all that is left to reveal is the method by which you obtain one. As you have probably guessed, the function to use is WinBeginPaint. HPS WinBeginPaint(HWND hwndWindow,                 HPS hpsCreated,                  PRECTL prclInvalid);


 * hwndWindow:the window for which the HPS is to be obtained.
 * hpsCreated:used whenever you have already created an HPS and would like to use it instead of having the system allocate one for you. We will always specify NULLHANDLE for this parameter.
 * prclInvalid:points to a RECTL structure to receive the bounding rectangle. This can be NULL.

For reasons beyond the scope of this column, the HPS must be returned to the system once you are finished. The function to do this is WinEndPaint, and its only parameter is the HPS to release. BOOL WinEndPaint(HPS hpsRelease);

Some important things to note:
 * 1) The HPS returned by WinBeginPaint automatically has a clipping region set to the bounding rectangle returned in prclInvalid. This rectangle is returned to the caller to take advantage of any painting optimizations possible by restricting the area that is repainted to only the rectangle.
 * 2) The WinEndPaint call sets the invalid region to empty (empty is also referred to as NULLHANDLE with respect to regions, since they are also a datatype in PM).
 * 3) You cannot use WinBeginPaint and WinEndPaint except in the context of a WM_PAINT message. Should you need to do any drawing in any other message, the functions WinGetPS and WinReleasePS should be used. This is because there will be no invalid region, so everything will be clipped.
 * 4) An HPS is used to allow you to draw only. It does not remember what was drawn, so you must redraw each time you receive a WM_PAINT message. There are ways around this, however, which we will look at in future issues.

Now That I Have an HPS, What do I do Next?
There is an entire system devoted to drawing, known as the Graphical Programming Interface but usually referred to as the Gpi. Since it is so extensive, we will not cover it directly; instead, some of the more common functions will be explained as they are encountered.

In the Win subsystem, however, there are a number of functions which provide access to the more commonly needed functions.
 * WinDrawBitmap:draws a bitmap at a specified position, with some control over appearance.
 * WinDrawBorder:draws a border inside a specified rectangle.
 * WinDrawPointer:draws a mouse pointer or an icon at a specified position.
 * WinDrawText:draws a string at a specified position, with many different options for appearance
 * WinFillRect:fills the specified rectangle with the specified colour.
 * WinInvertRect:inverts (a la xor) the specified rectangle.

I will leave it as a reader exercise to refer to the Programming Reference for more information about these functions.

And On The First Day...
...sent a WM_CREATE message. :)

The final topic for this month's column is two new messages: these are the WM_CREATE and WM_DESTROY messages.

WM_CREATE
This message occurs when an application requests the creation of a window. Parameters param1 pvData (PVOID) Window-specific data that is specified on the call to WinCreateWindow. param2 pcsCreate (PCREATESTRUCT) Points to a CREATESTRUCT structure that specifies the various initial characteristics of the window, e.g. size, position, etc.

Returns reply bReply (BOOL) Success indicator: FALSE  Initialization succeeded. Continue with window creation. TRUE  Initialization failed. Do not create the window.

WM_DESTROY
This message occurs when an application destroys a window. Parameters param1 Reserved. NULL  Reserved value. param2 Reserved. NULL  Reserved value. Returns reply Reserved. NULL  Reserved value. These two messages are sent to allow a window to perform any initialization and termination processing. As shown, if initialization fails, the window procedure should return TRUE to prohibit the creation of the window.

A couple of things should be noted: hwndFrame=WinCreateStdWindow(HWND_DESKTOP,                            WS_VISIBLE,                             &ulCreate,                             CLS_CLIENT,                             "Hello World Sample",                             0,                             NULLHANDLE,                             RES_CLIENT,                             &hwndClient); Since the WM_CREATE message is a direct function call, WinCreateStdWindow hasn't returned yet, and so hwndFrame is still uninitialized. If the unlikely event that you declare hwndFrame to be a global variable, you cannot use its value in the WM_CREATE message processing. Instead, you should use the function WinQueryWindow in the following manner: case WM_CREATE: {     HWND hwndFrame;
 * 1) The window procedure is directly invoked to send the WM_CREATE message.
 * This is different than it being sent or posted to the window procedure in that the window doesn't actually exist until the processing completes and returns FALSE. This significance is noted because, since the window doesn't really exist, certain functions will not work properly (i.e. WinQueryWindowRect, WinQueryWindowPos, etc.). Instead, you should use the values of the fields of the pcsCreate parameter.
 * 1) Last issue, we saw the following code:

:  }   break; Before you shrug your shoulders and say "Yeah, that's nice, but what is the real benefit of these messages?", keep reading...

Think back to the first instalment of this column, where it was said that a window procedure is the common way of referring to the entity called a window class procedure. The point here is that, should you write an application that has to create multiple windows of the same class (which you developed), you can no longer use global or static variables to store information that must be shared among message processing blocks. This is because all windows of the class share these variables; if one window needs to update the value of one variable, the update will affect all windows of that class.

To alleviate this, PM has the idea of window words, which are additional bytes of memory allocated for each instance of a window class. You could, for example, then specify that an additional 4 bytes of memory is to be allocated, and then use those 4 bytes to point to a structure containing instance-specific data. See the section Design in the article Development of a New Window Class - Part 1 in issue 4 of EDM/2 for more information about window words.

Another common reason for using these messages is for creating other window that are the children of your window class. For example, say you want to display a list of choices, and underneath you want to display the text currently selected. There are two defined window classes - listboxes and static controls - which individually do part of what is desired. You could then create them as part of the initialization process and if either should fail, return TRUE to stop the application from continuing.

Summary
This month, we took a much more detailed look at the concept of painting and how presentation spaces are used by the system to hide the specifics of whatever physical output medium is connected to the CPU. We explained update regions and their corresponding bounding rectangles, and their relationship to invalid regions. Finally, we looked at two new messages - WM_CREATE and WM_DESTROY - and briefly touched on their purpose.

If you understood everything in this column and the last one, you should be able to look at intro.zip (provided last month) and understand most - if not all - of the program source code. This should be verified in order to determine your retention percentage, and, should any further clarification be necessary, send me email with your questions.

Next month, we will introduce some of the other controls in order to discuss dialog boxes and how they are used to communicate with the user. We will also take a more detailed look at resources and how they are used in the development of dialog boxes. Finally, we will continue our perusal of the messages that are commonly used in PM application development.