Introduction to PM Programming - Feb 1994

From EDM2
Jump to: navigation, search

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 curiousity, 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 colors, the current font, etc. All of these characteristics are physical characteristics, and they are collectively known as a device context.

Figure 2)  Physical output device

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 analagous 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.

Figure 3)  Logical output which results in the figure 2

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)  No clipping

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):

Figure 5)  Triangular clipping

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 color.
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:

  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.
  2. Last issue, we saw the following code:
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;

        :
      hwndFrame=WinQueryWindow(hwndWnd,QW_PARENT);
        :
   }
   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 installment 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.