Introduction to PM Programming - Apr 1994

From EDM2
Jump to: navigation, search

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 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 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 began looking at dialog boxes and their components. This month, we'll continue by delving into the source code to HELLO which was presented last month. We'll examine the messages in our dialog procedure, and will begin looking at how controls are used within dialogs.

nameDlgProc()

Below is the dialog procedure from HELLO.C condensed a bit:

MRESULT EXPENTRY nameDlgProc(HWND hwndWnd,
                             ULONG ulMsg,
                             MPARAM mpParm1,
                             MPARAM mpParm2)
{
   switch (ulMsg) {
   case WM_INITDLG:
        :
      break;
   case WM_CONTROL:
         :
      break;
   case WM_COMMAND:
         :
      break;
   default:
      return WinDefDlgProc(hwndWnd,ulMsg,mpParm1,mpParm2);
   } /* endswitch */

   return MRFROMSHORT(FALSE);
}

We looked at the WM_INITDLG message last issue; let's take a look at the other two.

WM_COMMAND

This message occurs when a command is to be performed, or when a key combination is translated to a WM_COMMAND accelerator message.

Parameters
param1
usId (USHORT)
Identifier of the command or accelerator key.
usSource (USHORT)
A CMDSRC_* constant describing the source of the message
param2
bPointer (BOOL)
Pointer indicator
TRUE The message is the result of a pointer action.
FALSE The message is the result of a keyboard action.
Returns
reply
ulReserved (BIT32)
Reserved, and must be zero.

WM_CONTROL

This message occurs when a control has a significant event to report to its owner.

Parameters
param1
usId (USHORT)
Identifier of the control sending the notification.
usNotify (USHORT)
The notification code. This is specific to the window class of the control.
param2
pvData (PVOID)
Control data. This is specific to the notification code being sent.
Returns
reply
ulReserved (BIT32)
Reserved, and must be zero.

Okay, "big deal," you say. Just add two more entries to your list of messages that you now know about? Well, these messages are a bit more important than that...

The WM_COMMAND Message

The WM_COMMAND message is important because it is the focal point through which your application will get notified that something needs to get done. Among other things...

  • menu items send this message when they are selected
  • pushbuttons send this message when they are selected
  • the frame window sends this message whenever the user presses a key combination that is defined in the accelerator table
  • dialogs automatically receive this whenever Enter or Escape are pressed.

It doesn't look like much, but as you travel the PM developer's journey, you'll find that this message is frequently sent. It becomes your central interface to the user, since most actions are requested by the user through menus on client windows and through pushbuttons on dialogs.

Let's take a look at how this message is treated in HELLO.C.

case WM_COMMAND:
   switch (SHORT1FROMMP(mpParm1)) {
   case DID_OK:
      WinDismissDlg(hwndWnd,TRUE);
      break;
   case DID_CANCEL:
      WinDismissDlg(hwndWnd,FALSE);
      break;
   default:
      return WinDefDlgProc(hwndWnd,ulMsg,mpParm1,mpParm2);
   } /* endswitch */
   break;

It isn't obvious in the documentation, if I remember correctly, so don't feel ashamed if you don't understand the constants DID_OK and DID_CANCEL.

DID_OK Sent whenever the user presses Enter
DID_CANCEL Sent whenever the user presses Escape

It is imperative that you understand that these messages are not sent by pushbuttons. Thus, even though your dialog might not have pushbuttons, it is possible to receive these messages. (If you don't believe me, comment out the DEFPUSHBUTTON and PUSHBUTTON lines in HELLO.RC, recompile and try.)

Dismissal Revisited

When reading last month's column, I realized that I was talking about the dismissal of dialogs like we were all old pro's at it; if we were, I thought, this column wouldn't have a reason for existence.

BOOL WinDismissDlg(HWND hwndDlg,ULONG ulResult);

This function is used to dismiss a dialog. As was mentioned last time, this does not destroy the dialog by default; it only hides the dialog. What does this have to do with the WM_COMMAND message? Well, the typical action for DID_OK and DID_CANCEL is to dismiss the dialog (for DID_OK, data is usually read from the dialog also and verified), so you can see that this function is used frequently.

In the above, no data is read from the dialog. How does the program know what name to display?...

The WM_CONTROL Message

What happens whenever an item in a listbox is selected? Or whenever the text in an entryfield changes? Or when a checkbox is clicked on by the user? For all of these, and many other events, the owner of the control is sent a WM_CONTROL message, meaning that something occurred that could have an effect on the behaviour of the application.

This is a broad message, which has a specific set of parameters based on two criteria:

  1. the class of the control sending the message
  2. the event that generate the message

Thus, for the first scenario, the usNotify is LN_SELECT, and pvData is the handle of the listbox. For the second, usNotify is EN_CHANGE and pvData is the handle of the entryfield. And so on...

case WM_CONTROL:
   switch (SHORT1FROMMP(mpParm1)) {
   case DNAME_LB_NAMELIST:
      switch (SHORT2FROMMP(mpParm1)) {
      case LN_SELECT:
         {
            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:
         WinPostMsg(hwndWnd,WM_COMMAND,MPFROMSHORT(DID_OK),0);
         break;
      default:
         return WinDefDlgProc(hwndWnd,ulMsg,mpParm1,mpParm2);
      } /* endswitch */
      break;
   default:
      return WinDefDlgProc(hwndWnd,ulMsg,mpParm1,mpParm2);
   } /* endswitch */
   break;

You probably don't understand the specific functions, but you can see for yourself by running the application that, whenever an item is selected in the listbox, the name selected is placed in the entryfield.

.And the Point is...

The point of introducing the WM_COMMAND and WM_CONTROL messages is to demonstrate the usefulness of them. As we begin to look at the controls, we will examine how they interact with the application through these messages; careful exploitation will (eventually) allow you to control the execution of your application.

New Functions

In addition to the WinDismissDlg() function, we will look at three additional PM APIs.

BOOL WinSetWindowText(HWND hwndWnd,PSZ pszText);

LONG WinQueryWindowText(HWND hwndWnd,LONG lSzBuf,PSZ pszBuffer);

LONG WinQueryWindowTextLength(HWND hwndWnd);

WinSetWindowText sets the text of the specified window to the specified value. It returns a success flag.

WinQueryWindowText queries the text of the specified window and copies up to either the end of the text or lSzBuf characters into the buffer pointed to by pszBuffer. It returns the number of characters copied.

WinQueryWindowTextLength returns the length of the text for the specified window.

It is important to note that how the window text is actually used differs from control to control. For example, the window text of an entryfield is what is displayed in the entryfield itself. This is also true for a titlebar. However, a listbox has many text entries, which cannot be described by a single text item so it ignores the window text. Some controls use their window text in other ways. A frame, for example, notices when its window text changes and updates the titlebar's window text, if a titlebar exists.

In other words, your mileage may vary.  :)

Summary

This month we introduced two very important messages - WM_COMMAND and WM_CONTROL - and described their purpose in a window or dialog procedure. We also looked at four new PM APIs and looked at window text for the first time since they were mentioned in volume 2, issue 1.

Next month, we will continue with HELLO by beginning to examine the controls within it individually.