Introduction to PM ProgrammingWritten by Larry Salomon Jr. |
IntroductionThe purpose of this column is to provide 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 you 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 MonthLast month, we introduced the WM_COMMAND and WM_CONTROL messages, which are - as you will see - very important in the development of many PM applications. We also looked at the HELLO dialog procedure in a little detail, and were introduced to three new APIs.This month, we will introduce some more APIs, will continue dissecting the dialog procedure, and will begin looking at our first window class in detail.
New APIsPM has a few specific APIs which are more "helper APIs" than anything else, because they replace two APIs, the first of which is always WinWindowFromID().HWND WinWindowFromID(HWND hwndParent,ULONG ulId);WinWindowFromID searches the children of hwndParent to find a window whose identifier (remember these from the first column in this series?) matches ulId. It returns the window handle if found, or NULLHANDLE otherwise. These new functions are listed below: BOOL WinSetDlgItemText(HWND hwndDlg,ULONG ulId,PSZ pszText); ULONG WinQueryDlgItemText(HWND hwndDlg, ULONG ulId, LONG ulSzBuffer, PCHAR pchBuffer); LONG WinQueryDlgItemTextLength(HWND hwndDlg,ULONG ulId);These three functions provide the equivalent of the WinSetWindowText(), WinQueryWindowText(), and WinQueryWindowTextLength() functions that we looked at last month. The only difference is that the above three functions call WinWindowFromID() first to determine the handle of the window you are interested in. Letter carriers and Bulletin BoardsWhile thinking about the topics that I should cover this month, I realized that, although the terms sending and posting messages have been used liberally over the lifespan of this column, the functions to perform these two actions have never been discussed, nor has the difference between the two been discussed.MRESULT WinPostMsg(HWND hwndWnd, ULONG ulMsg, MPARAM mpParm1, MPARAM mpParm2); MRESULT WinSendMsg(HWND hwndWnd, ULONG ulMsg, MPARAM mpParm1, MPARAM mpParm2);Both WinSendMsg and WinPostMsg take the same parameter set:
The difference between posting and sending is that the former simply puts the message in the message queue of the recipient, while the latter is (logically speaking) a direct call to the window procedure of the recipient's window procedure. The significance of the last statement is that your window procedure will be blocked until the WinSendMsg() call returns, and that will not happen until the recipient finishes processing the message. Why is one used over the other? For one thing, since WinSendMsg() is a synchronous call, pointers that are specified are guaranteed to point to valid areas of memory (this is important if they point to local variables on the stack). Note the word valid instead of accessible; if you send a message to another process with a pointer, accessability to the pointer must be established either via shared memory or giveable/gettable memory. WinSendMsg() does have one disadvantage, however; it requires that the sender have a message queue. WinPostMsg(), on the other hand, can be sent from anywhere, regardless of whether or not WinCreateMsgQueue() was called previously (this is handy for secondary threads). Given this information, let us look at one more "dialogish" API - WinSendDlgItemMsg(). MRESULT WinSendDlgItemMsg(HWND hwndDlg, ULONG ulId, ULONG ulMsg, MPARAM mpParm1, MPARAM mpParm2);This function, as you can imagine, behaves the same as WinSendMsg(), except that - as with the first three APIs discussed in this section - it calls WinWindowFromID() to get the handle of the window to whom the message should be sent. It should be noted that there is no function WinPostDlgItemMsg(). I have never been able to get a straight answer for why this is so, unfortunately. One last API to look at - WinDefDlgProc(). MRESULT WinDefDlgProc(HWND hwndWnd, ULONG ulMsg, MPARAM mpParm1, MPARAM mpParm2);In volume 2, issue 1, we briefly noted how WinDefWindowProc() was used to handle all messages that we did not want to process in our window class procedure. WinDefDlgProc() has the same purpose except that it is used within dialog procedures. Please be aware that, using one when you should be using the other will result in very strange behavior, so be careful!
nameDlgProc() RevisitedLet's look at nameDlgProc() one more time, this time in its entirety. Following that is the resource file definition for the dialog.MRESULT EXPENTRY nameDlgProc(HWND hwndWnd, ULONG ulMsg, MPARAM mpParm1, MPARAM mpParm2) { PNAMEDLGINFO pndiInfo; pndiInfo=(PNAMEDLGINFO)WinQueryWindowPtr(hwndWnd,0); // @1 switch (ulMsg) { case WM_INITDLG: // @2 { HAB habAnchor; HWND hwndLb; CHAR achText[64]; pndiInfo=(PNAMEDLGINFO)PVOIDFROMMP(mpParm2); // @3 WinSetWindowPtr(hwndWnd,0,(PVOID)pndiInfo); // @4 WinSendDlgItemMsg(hwndWnd, // @5 DNAME_EF_NAME, EM_SETTEXTLIMIT, MPFROMSHORT(sizeof(pndiInfo->achName)), 0); habAnchor=WinQueryAnchorBlock(hwndWnd); hwndLb=WinWindowFromID(hwndWnd,DNAME_LB_NAMELIST); WinLoadString(habAnchor, // @6 NULLHANDLE, STR_TOM, sizeof(achText), achText); WinInsertLboxItem(hwndLb,LIT_END,achText); // @7 WinLoadString(habAnchor, NULLHANDLE, STR_DICK, sizeof(achText), achText); WinInsertLboxItem(hwndLb,LIT_END,achText); WinLoadString(habAnchor, NULLHANDLE, STR_HARRY, sizeof(achText), achText); WinInsertLboxItem(hwndLb,LIT_END,achText); } break; case WM_CONTROL: // @8 switch (SHORT1FROMMP(mpParm1)) { case DNAME_LB_NAMELIST: switch (SHORT2FROMMP(mpParm1)) { case LN_SELECT: // @9 { HWND hwndLb; SHORT sIndex; hwndLb=WinWindowFromID(hwndWnd,DNAME_LB_NAMELIST); sIndex=WinQueryLboxSelectedItem(hwndLb); WinQueryLboxItemText(hwndLb, sIndex, pndiInfo->achName, sizeof(pndiInfo->achName)); WinSetDlgItemText(hwndWnd, DNAME_EF_NAME, pndiInfo->achName); } break; case LN_ENTER: // @10 WinPostMsg(hwndWnd,WM_COMMAND,MPFROMSHORT(DID_OK),0); break; default: return WinDefDlgProc(hwndWnd,ulMsg,mpParm1,mpParm2); // @11 } /* endswitch */ break; default: return WinDefDlgProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endswitch */ break; case WM_COMMAND: switch (SHORT1FROMMP(mpParm1)) { case DID_OK: // @12 WinDismissDlg(hwndWnd,TRUE); break; case DID_CANCEL: // @13 WinDismissDlg(hwndWnd,FALSE); break; default: return WinDefDlgProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endswitch */ break; default: return WinDefDlgProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endswitch */ return MRFROMSHORT(FALSE); } ------------------------------------------ DLGTEMPLATE DLG_NAME LOADONCALL MOVEABLE DISCARDABLE { DIALOG "Input Required", DLG_NAME, 112, 59, 150, 100, WS_VISIBLE, FCF_SYSMENU | FCF_TITLEBAR { LTEXT "Select a name, or enter it below, and select ""Ok""" ".", -1, 10, 80, 130, 16, DT_WORDBREAK LISTBOX DNAME_LB_NAMELIST, 10, 45, 130, 35, LS_HORZSCROLL ENTRYFIELD "", DNAME_EF_NAME, 12, 30, 127, 8, ES_MARGIN DEFPUSHBUTTON "Ok", DID_OK, 10, 10, 40, 13 PUSHBUTTON "Cancel", DID_CANCEL, 55, 10, 40, 13 } }From the dialog template, we see that the dialog consists of five controls: a text control (LTEXT) whose identifier is -1 (see below), a listbox control (LISTBOX) whose identifier is DNAME_LB_NAMELIST, an entryfield control (ENTRYFIELD) whose identifier is DNAME_EF_NAME, and two pushbuttons (the first is DEFPUSHBUTTON meaning it's the default pushbutton and the second which is simply PUSHBUTTON) whose identifiers are DID_OK and DID_CANCEL, respectively. It is important to remember that, while the symbolic constants DNAME_LB_NAMELIST and DNAME_EF_NAME are defined in HELLORC.H, DID_OK and DID_CANCEL are defined in <pmwin.h> which is #include-d by <os2.h>. Looking at the easiest message first, at numbers @12 and @13 we see the processing of the WM_COMMAND message, which (if you'll remember from last month) is sent by a pushbutton whenever it is pressed ("selected" is the term typically used). You should remember that the identifier of the button is in SHORT1FROMMP(mpParm1), which we "switch" on to determine the appropriate action to take. Here, we simply dismiss the dialog with either TRUE or FALSE returned depending on whether the user completed the dialog or changed their mind and decided to select Cancel. At number @8 we see the WM_CONTROL message. Again, last month we looked at this message and you should remember that the identifier of the window sending the message is in SHORT1FROMMP(mpParm1), which we "switch" on to determine how to interpret the notification code. Since we're interested in the listbox only, we process "case DNAME_LB_NAMELIST" and let WinDefDlgProc() handle everything else (at number @11). At number @9 is the LN_SELECT notification, which is sent whenever a listbox item is selected. Number @10 is the LN_ENTER notification, which is sent whenever ENTER is pressed while the listbox has the input focus or any listbox item is double-clicked. Since we will not be looking at the listbox in any great detail until a future issue, I will not elaborate on these two notifications until that time. At number @2 is the dialog initialization code, which I will not discuss at this time due to its complexity relative to what has been covered by this column up to this point in time.
The WC_ENTRYFIELD Window ClassEntryfields belong to the class WC_ENTRYFIELD and are probably the easiest of the window classes to use, since there isn't a lot of variation involved in the control's design. An entryfield is used to get a single line of text from the user and has clipboard support built-in (the clipboard will be discussed in a later issue). In an entryfield, text can be selected with the mouse or the keyboard. A selection consists of an anchor point and a cursor point. The anchor point is the point at which the selection began, and the cursor point is the point where the selection ends, and may move depending on whether the selection is still in progress.An entryfield can have the following styles:
The entryfield has a number of messages that it understands, and they are listed below. We will discuss some of them here, and will continue in the next issue.
Let's look at our first four messages - EM_QUERYCHANGED, EM_QUERYFIRSTCHAR, EM_QUERYREADONLY, and EM_QUERYSEL. EM_QUERYCHANGEDThis message is sent to determine if the text has changed since this message was last sent (or since the control was created).Parameters
param1
param2
ReturnsEM_QUERYFIRSTCHARThis message is sent to determine the zero-based index of the first character visible in the entryfield.Parameters
param1
param2
ReturnsEM_QUERYREADONLYThis message is sent to determine if the entryfield is read-only or not.Parameters
param1
param2
ReturnsEM_QUERYSELThis message is sent to determine the current selection, if any exists.Parameters
param1
param2
ReturnsSummaryThis month we learned a lot of new things:
|