Introduction to PM ProgrammingWritten by Larry Salomon, Jr. |
IntroductionThe 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 ForwardLast 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 installment, you should:
...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".
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 ConceptsThere are many things mentioned in the last section, which are described in more detail below. Frame WindowsFrame 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 WindowsSince 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 TextAll 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. ResourcesThis 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 HierarchiesYou'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 ConstantsWindow Style ConstantsThere 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.
Frame Creation Flag ConstantsIn 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.
Note the following sets of mutually exclusive flags. If more than one of each group is specified, the first is the default.
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 LoopLet'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 ProcedureAs 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:
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. SummaryThis 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. |