Introduction to PM Programming - Jan 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.

Looking Back and Looking Forward

Last issue, we discussed some of the fundamental concepts of PM such as windows and window classes, events, resources, and handles. This issue we will take these basic concepts and discuss the components of our first PM application entitled (what else?) "Hello World" (provided in intro.zip).

Introduction

After reading this installment, you should:

  • Be able to explain the purpose of each line in the main() function of a typical PM application.
  • Understand the hierarchical organization of windows.
  • Know what some window styles are.
  • Be able to describe the purpose of a frame creation flag.
  • Be able to define the terms top-level window and well-behaved application.

...as well as a few other things.

Back in the dark ages when all of OS/2 and PM was 16-bit, there were ring 2 stack (which I will not define) requirements imposed by PM applications that exceeded the default size provided when OS/2 started a new process. Because of this and other reasons, PM required that you call the function WinInitialize() at the begining of your application, which in turn called DosR2StackRealloc() to increase the size of the ring 2 stack and performed some additional housekeeping to enable your to successfully call other PM functions.

While the need to increase the ring 2 stack may or may not have disappeared, the housekeeping initialization didn't. So, the first call in a PM application should always be WinInitialize() (and have the corresponding call to WinTerminate() before each exit point of your application). This function returns something called an anchor block, which is used as a parameter to other functions; for the curious, the value of the anchor block is the process identifier in the high word and a one in the low word (actually, the low word contains the thread identifier, which will always be one for quite a while in this column). This might change in the future, though, so do not depend on this always being true.

Also, many PM functions indirectly cause messages (referred to last time in the section Events) to be sent to various windows, and messages require a message queue so the call the typically follows WinInitialize() is WinCreateMsgQueue() (and the corresponding call to WinDestroyMsgQueue() prior to the call to WinTerminate()). The reason why sending messages requires a message queue is beyond the scope of this column.

Instead of questioning why you have to bother with these things, just accept them as givens, and you will save yourself much worrying.

After the first two calls, there might be some application initialization, but inevitably there is one or more calls to WinRegisterClass() to register the window classes used by the application with PM. If you'll remember, this associates a window class name with a window class procedure.

This is then followed by a call to WinCreateStdWindow(); this function creates a frame window and other standard window components.

The WinCreateStdWindow() function takes many parameters and is described below:

HWND WinCreateStdWindow(HWND hwndParent,
                        ULONG ulFrameStyle,
                        PULONG pulFlags,
                        PSZ pszClientClass,
                        PSZ pszTitle,
                        ULONG ulClientStyle,
                        HMODULE hmResources,
                        ULONG ulIdResources,
                        PHWND phwndClient);

Figure 1) Some components of a "standard window".
hwndParent
Specifies the parent of the standard window. If HWND_DESKTOP is specified, a top level window is created.
ulFrameStyle
Specifies a set of WS_* (Window Style) constants for the frame window.
pulFlags
Specifies a set of FCF_* (Frame Creation Flag) constants that indicate the desired components (see Figure 1 and the description below) and properties of the standard window:
pszClientClass
"Points" to the name of the class of the window to be used as the client area in Figure 1. "Points" is used in quotes because this can also be a WC_* (Window Class) constant indicating one of the predefined window classes (known as public window classes).
pszTitle
Points to the window text of the frame window. This text is also displayed on the titlebar. This cannot be NULL. To specify nothing, use an empty string ("").
ulClientStyle
Specifies a set of WS_* (Window Style) constants for the client window. This is typically specified as zero, but could be used if a public window class is used as the client.
hmResources
Specifies the module handle where any required resourced are located. If NULLHANDLE is used, the resources are assumed to be appended to the .EXE file.
ulIdResources
The identifier of any required resources.
phwndClient
Points to the variable to receive the window handle of the client.

(See the next sections for detailed explanations of the umpteen million new things mentioned above)

After the standard window is created, the message loop is typically entered. The message loop is comprised of a call to WinGetMsg() followed by a call to WinDispatchMsg(). WinGetMsg() consistently returns TRUE until a WM_QUIT message is received which causes the loop to end.

Following the loop is a call to WinDestroyWindow() which destroys the frame window, and thus all of its children.

I intentionally wanted this to sound mechanical, because it is. The main() function of all PM applications are very similar, if not identical.

INT main(VOID)
{
  HAB habAnchor;
  HMQ hmqQueue;
  ULONG ulCreate;
  HWND hwndFrame;
  HWND hwndClient;
  BOOL bLoop;
  QMSG qmMsg;

  habAnchor=WinInitialize(0);
  hmqQueue=WinCreateMsgQueue(habAnchor,0);

  WinRegisterClass(habAnchor,CLS_CLIENT,windowProc,CS_SIZEREDRAW,0);

  ulCreate=FCF_SYSMENU | FCF_TITLEBAR | FCF_MINMAX | FCF_SIZEBORDER |
              FCF_SHELLPOSITION | FCF_TASKLIST | FCF_ICON;

  hwndFrame=WinCreateStdWindow(HWND_DESKTOP,
                               WS_VISIBLE,
                               &ulCreate,
                               CLS_CLIENT,
                               "Hello World Sample",
                               0,
                               NULLHANDLE,
                               RES_CLIENT,
                               &hwndClient);
  if (hwndFrame!=NULLHANDLE) {
     bLoop=WinGetMsg(habAnchor,&qmMsg,NULLHANDLE,0,0);
     while (bLoop) {
        WinDispatchMsg(habAnchor,&qmMsg);
        bLoop=WinGetMsg(habAnchor,&qmMsg,NULLHANDLE,0,0);
     } /* endwhile */

     WinDestroyWindow(hwndFrame);
  } /* endif */

  WinDestroyMsgQueue(hmqQueue);
  WinTerminate(habAnchor);
  return 0;
}

Figure 2) Sample main() function for a PM application coded in C.

If you thought that was a lot of typing, here's an incentive to learn C++ using IBM's User Interface class library: the entire main() function can be coded in 8 lines, and 3 of those are required by the language!

INT main(VOID)
{
  new IFrameWindow("Hello World Sample",
                   RES_CLIENT,
                   IFrameWindow::defaultStyle() |
                      IFrameWindow::minimizedIcon);
  (IApplication::current()).run();
  return 0;
}

Figure 3) Sample main() function for a PM application coded in C++.

New Concepts

There are many things mentioned in the last section, which are described in more detail below.

Frame Windows

Frame windows are windows that serve as the focal point for a standard window construct. It usually receives messages from other windows, of which it sends the important ones to the client. Physically, it encompases all of the standard window components.

Top Level Windows

Since it is possible to have a standard window within a standard window, the standard window that is the parent of the other standard windows is known as the top level window (actually, a top level window is defined as one whose parent is the desktop). The top level frame has special significance because enumerations of the windows present only iterate through the top level windows.

When a top level window contains other standard windows, the application is termed an MDI - or multiple document interface - application.

Window Text

All windows have a common set of characteristics, which was alluded to last issue when class styles versus window styles was discussed. Besides window styles, many other characteristics are found in all windows; window text is one of them. Actually, text is a misnomer because it is not required that this be text, only that this points to accessable memory. Another useful characteristic queryable by all windows is the window identifier.

There are many different WinQuery*() functions to retrieve these common characteristics.

Resources

This is something which we will describe in more detail next issue. Suffice it to say for now that a resource is a definable item that would be too much trouble to write code to construct, so a resource language and corresponding compiler was written to allow you to describe things like menus, string tables (for easy translation), accelerator tables, etc.

Window Hierarchies

You've already seen the terms parent, children, siblings, and owner used. What exactly do they mean? The first three have meaning analagous to "Real Life" (tm). A parent window can have 0 or more child windows each of whom are siblings of each other.

Figure 4) Parent-child relationships

This parent-child relationship is exploited in various ways, most notably in clipping. If a child window lies completely or partially outside the rectangular area defined by the parent window, those portions are clipped, i.e. not shown.

Figure 5) Children clipped to their parent

Other ways that this relationship is used will be discussed in later issues.

Unlike parent-child one-to-many relationships, owner-ownee relationships are one-to-one, but the same window can own many other windows. The distinction is that there is no concept of a sibling in this regard. An owner window is notified by the owned window whenever something happens that the owner might be interested in. Notification occurs via a message named WM_CONTROL. We will also see more of this message in later issues.

Figure 6) Owner-ownee relationship

New Constants

Window Style Constants

There are many window style constants which are common to all windows and even more which are specific to a particular window class. Listed below are the common window styles and their meanings.

WS_VISIBLE
Specifies that the window is to be visible.
WS_DISABLED
Specifies that the window is to be disabled.
WS_CLIPCHILDREN
Specifies that, when painting, children should be prevented from being painted over.
WS_CLIPSIBLINGS
Specifies that, when painting, the parent should be prevented from being painted over.
WS_PARENTCLIP
Specifies that, when painting, the parent should be prevented from being painted over.
WS_SAVEBITS
Specifies that a bitmap area of any windows covered by this window should be saved, and restored as the window is moved, sized, hidden, or destroyed.
WS_SYNCPAINT
Specifies that paint notification should be synchronous, i.e. the window should be notified immediately following any invalidation.
WS_MINIMIZED
Specifies that the window is minimized. This flag is ignored in the WinCreateStdWindow() function.
WS_MAXIMIZED
Specifies that the window is maximized. This flag is ignored in the WinCreateStdWindow() function.
WS_ANIMATE
Specifies that the creation and destruction of the window should be accompanied by zoom-out/zoom-in animation.
WS_GROUP
WS_TABSTOP
WS_MULTISELECT
Used for dialogs only and will not be discussed here.

Frame Creation Flag Constants

In addition to window styles, standard windows also have many flags which determine the behavior of the WinCreateStdWindow() call. These flags, and their meanings are described below.

FCF_TITLEBAR
Specifies that a titlebar should be included.
FCF_SYSMENU
Specifies that the system menu should be included.
FCF_MENU
Specifies that an action bar (not shown) should be included.
FCF_SIZEBORDER
Specifies that a sizing border should be used as the window border.
FCF_MINBUTTON
Specifies that a min(imize) button should be included.
FCF_MAXBUTTON
Specifies that a max(imize) button should be included.
FCF_MINMAX
Specifies that both min and max buttons should be included.
FCF_VERTSCROLL
Specifies that a vertical scrollbar (not shown) should be included.
FCF_HORZSCROLL
Specifies that a horizontal scrollbar (not shown) should be included.
FCF_DLGBORDER
Specifies that a dialog border (not shown) should be used as the window border.
FCF_BORDER
Specifies that a normal border (not shown) should be used as the window border.
FCF_SHELLPOSITION
Specifies that the Workplace Shell should automatically move, size, and then show the window after it is created.
FCF_TASKLIST
Specifies that the Workplace Shell should automatically add an entry in the Window List for the standard window.
FCF_NOBYTEALIGN
Specifies that the window position should not snap to an 8-pel grid when it is moved. This can result in less than optimal performance.
FCF_NOMOVEWITHOWNER
Specifies that the window position relative to its owner should not be adjusted when the owner is moved.
FCF_ICON
Specifies that an icon with the identifier specified in ulIdResources should be associated with the standard window.
FCF_ACCELTABLE
Specifies that an accelerator with the identifier specified in ulIdResources should be associated with the standard window.
FCF_SYSMODAL
Specifies that the window should be system modal.
FCF_SCREENALIGN / FCF_MOUSEALIGN
Used for dialogs only and will not be discussed here.
FCF_HIDEBUTTON
Specifies that a hide button should be included.
FCF_HIDEMAX
Specifies that both hide button and max buttons should be included.
FCF_DBE_APPSTAT
This flag is undocumented.
FCF_AUTOICON
Specifies that, when minimized, the system should redraw the icon when necessary instead of notifying the window.

Note the following sets of mutually exclusive flags. If more than one of each group is specified, the first is the default.

  • FCF_MINBUTTON and FCF_HIDEBUTTON
  • FCF_SCREENALIGN and FCF_MOUSEALIGN
  • FCF_SIZEBORDER, FCF_DLGBORDER, and FCF_BORDER

If all of this seems to be too much, don't fret; this function is one of the most complicated in terms of the myriad of flags that can be used (not surprisingly, another one is the WinCreateWindow() function).

More About the Message Loop

Let's take a closer look at the message loop.

bLoop=WinGetMsg(habAnchor,&qmMsg,NULLHANDLE,0,0);
while (bLoop) {
   WinDispatchMsg(habAnchor,&qmMsg);
   bLoop=WinGetMsg(habAnchor,&qmMsg,NULLHANDLE,0,0);
} /* endwhile */

Figure 7) Message loop

In PM, the only way you receive messages is via the WinGetMsg() call. The message construct (QMSG) is then translated to a call to the window class procedure (introduced last time) via the WinDispatchMsg() call.

When PM was designed, someone decided that the concept of type-ahead was important, so they decided to synchronize all input from the mouse and keyboard via a single system-wide input message queue, which gets checked whenever the application with the focus calls WinGetMsg(). This is important because the only way to switch between applications is via the keyboard or mouse; however, if the application with the focus is taking a long time to process the current message, it will never allow PM to change the input focus to another application.

To avoid what will appear to the user to be a system lockup, IBM defined the well-behaved application rule which states that a PM application should take no longer than 1/10th of a second to process a message. Of course, no one is going to time each message processing section to insure this, but it does indicate to the PM programmer that you should do what you have to quickly and "get back to the loop". Longer tasks, like loading a file, must be done in another thread. Multithreading from within PM applications was discussed in the article Threads in PM Applications in volume 1, issue 6 of EDM/2.

The Window Class Procedure

As discussed last time, the window class procedure (often abbreviated to window procedure or even window proc) is not much more than a giant switch statement which tests for particular message identifiers (abbeviated to just messages) and processing those only. The default case calls and returns the value from the function WinDefWindowProc().

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 8) Simple window procedure

The sample simple window procedure shown above does just what we described. A question often asked is why the choice of FALSE on the return statement. My answer is "I don't know"; you must return something to avoid warnings by the compiler and FALSE is the more frequent return value from specific messages, so I used it instead of TRUE.

There are a multitude of messages that the system sends to an application; so many, in fact, that detailing each of them would fill a book (hey, that's an idea...). Instead, you will find that you use a few much more than the rest:

WM_CREATE
Sent whenever the window is being created to allow it to initialize itself; by returning MRFROMSHORT(TRUE) you tell the system that initialization failed, causing the function resulting in this message to also fail.
WM_DESTROY
Sent whenever the window is about to be destroyed. There is no way to halt the destruction once this message is received.
WM_CLOSE
Sent whenever the window is about to be destroyed. There is no way to halt the destruction once this message is received.
WM_CLOSE
Sent whenever the user requested that the window close itself. This allows you to confirm the action, if needed, and then call the appropriate function to destroy yourself if confirmed.
WM_SIZE
Sent after the window has been resized.
WM_CONTROL
Sent whenever an owned window has something to tell you.
WM_COMMAND
Sent whenever the user selected a menuitem or a pushbutton, which indicates that a command is to be performed.
WM_MOUSEMOVE
Sent whenever the mouse moves over your window.
WM_BUTTONxDOWN
Sent whenever button x is depressed.
WM_BUTTONxUP
Sent whenever button x is released
WM_BUTTONxCLICK
Sent whenever button x is clicked.
WM_BUTTONxDBLCLK
Sent whenever button x is double-clicked.
WM_CHAR
Sent whenever a key is pressed.
WM_PAINT
Sent whenever a portion of the window needs to be repainted.
WM_TIMER
Sent whenever a timer started by the application expires.
WM_BEGINDRAG
Sent whenever the user attempts to initiate a direct manipulation operation.
WM_CONTEXTMENU
Sent whenever the user attempts to display a popup context-sensitive menu.

Now, what do you do when you receive any of these (or other) messages? That is up to you, but you will find a function to do just about anything you need; all you have to do is put them together in the right order.

Summary

This month we learned quite a lot, and the begining can definately be called the hardest part of adjusting to PM programming. Next month, we will look at the processing for WM_PAINT in our "Hello World" sample application and discuss presentation spaces. We will also be introduced to some of the messages listed in the last section.