Feedback Search Top Backward Forward
EDM/2

Introduction to PM Programming

Written by Larry Salomon Jr.

 

Introduction

The 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 Month

Last 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 APIs

PM 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 Boards

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

hwndWnd
the window to receive the message to be posted or sent
ulMsg
the message identifier
mpParm1, mpParm2
message-specific parameters. See the Programming Reference for detailed information about the parameters required for each message.

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() Revisited

Let'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 Class

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

ES_ANY
relates to DBCS ("double byte character set") support and will not be discussed here.
ES_AUTOSCROLL
specifies that, when the user moves the cursor outside of the visible area, the entryfield should automatically scroll so that the cursor is in a visible area.
ES_AUTOSIZE
specifies that the entryfield should automatically size itself to insure that its contents are completely visible.
ES_AUTOTAB
specifies that, when the user enters the number of characters equal to the current text limit (see EM_SETTEXTLIMIT), the entryfield should automatically give the input focus to the next control on the dialog.
ES_CENTER
specifies that the text should be displayed centered.
ES_COMMAND
relates to online help and will not be discussed here.
ES_DBCS
relates to DBCS ("double byte character set") support and will not be discussed here.
ES_LEFT
specifies that the text should be displayed flush-left.
ES_MARGIN
ES_MIXED
relates to DBCS ("double byte character set") support and will not be discussed here.
ES_READONLY
specifies that the user should not be able to change the contents.
ES_RIGHT
specifies that the text should be displayed flush-right.
ES_SBCS
relates to DBCS ("double byte character set") support and will not be discussed here.
ES_UNREADABLE
specifies that each character in the entryfield should be displayed as an asterisk ('*').

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.

  • EM_CLEAR
  • EM_COPY
  • EM_CUT
  • EM_PASTE
  • EM_QUERYCHANGED
  • EM_QUERYFIRSTCHAR
  • EM_QUERYREADONLY
  • EM_QUERYSEL
  • EM_SETFIRSTCHAR
  • EM_SETINSERTMODE
  • EM_SETREADONLY
  • EM_SETSEL
  • EM_SETTEXTLIMIT
You've probably noticed that there are no messages for setting and retrieving the contents of an entryfield. This is done using the WinSetWindowText() and WinQueryWindowText() (and through the "dialogish" APIs discussed earlier this issue).

Let's look at our first four messages - EM_QUERYCHANGED, EM_QUERYFIRSTCHAR, EM_QUERYREADONLY, and EM_QUERYSEL.

EM_QUERYCHANGED

This message is sent to determine if the text has changed since this message was last sent (or since the control was created).

Parameters

param1

ulReserved (ULONG)
Reserved, 0.

param2

ulReserved (ULONG)
Reserved, 0.

Returns

bChanged (BOOL)
changed indicator
TRUE - the contents have changed since the last time this message was sent.
FALSE - the contents have not changed since the last time this message was sent.

EM_QUERYFIRSTCHAR

This message is sent to determine the zero-based index of the first character visible in the entryfield.

Parameters

param1

ulReserved (ULONG)
Reserved, 0.

param2

ulReserved (ULONG)
Reserved, 0.

Returns

sIndex (SHORT)
the zero-based index of the first visible character.

EM_QUERYREADONLY

This message is sent to determine if the entryfield is read-only or not.

Parameters

param1

ulReserved (ULONG)
Reserved, 0.

param2

ulReserved (ULONG)
Reserved, 0.

Returns

bReadOnly (BOOL)
the read-only state of the entryfield.
TRUE - the entryfield is read-only.
FALSE - the entryfield is not read-only.

EM_QUERYSEL

This message is sent to determine the current selection, if any exists.

Parameters

param1

ulReserved (ULONG)
Reserved, 0.

param2

ulReserved (ULONG)
Reserved, 0.

Returns

sFirst (SHORT)
the zero-based index of the anchor point.
sLast (SHORT)
the zero-based index of the cursor point.

Summary

This month we learned a lot of new things:
  • We looked at a slew of new APIs, many of which are attuned to dialogs
  • We looked at nameDlgProc() is more detail
  • We began looking at the WC_ENTRYFIELD class in detail
Next month, we will continue with the WC_ENTRYFIELD discussion, but will hold off from discussing nameDlgProc() until we look at the WC_LISTBOX window class.