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

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



After reading this instalment, you should: ...as well as a few other things.
 * 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.

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




 * 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. 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! 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 accessible 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 analogous to "Real Life" (tm). A parent window can have 0 or more child windows each of whom are siblings of each other.



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.



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.



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. 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 lock-up, 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 (abbreviated to just messages) and processing those only. The default case calls and returns the value from the function WinDefWindowProc. 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 beginning can definitely 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.