Introduction to PM Programming - Nov 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 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 looked at the WC_BUTTON class and the five different "subclasses" contained therein. This month, we will look at a new application - BUTTON - which creates types of all of these controls. We will also look at the ownerdraw concept and how is specifically applies to button controls.

Good Guys Come Last

And easier concepts come first. Before we delve into the application, we should understand what ownerdraw controls are. The idea behind the ownerdraw control is that the application garners the responsibility for painting the control. This is useful in many situations; you might want to change the appearance of the control (as in ownerdraw buttons), or you might want to add functionality via usability enhancements (as in ownerdraw listboxes which contain icons). As more controls are developed with ownerdraw capability, more uses will be found for this newly created functionality (or is it the second that drives the first...?).

The last sentence has a double-edged blade, unfortunately. Since there are more than a few controls with this capability, there can be no standard way of implementing ownerdrawing. The specifics are tightly bound to the type of the control which is being "ownerdrawn." For buttons, the owner receives a WM_CONTROL message with the notification BN_PAINT when the button needs to be drawn, as an example.

Heeeeere's Johnny

Now that we aren't complete idiots <grin> with regard to ownerdraw windows, we can look at the sample code to see what secrets it holds. The application isn't complicated - there is a main() function which displays a dialog box. That's it. But the dialog box procedure is more complicated than what we have seen up to this point in the column.

MRESULT EXPENTRY buttonDlgProc(HWND hwndWnd,
                               ULONG ulMsg,
                               MPARAM mpParm1,
                               MPARAM mpParm2)
{
   PBTNDLGINFO pbdiInfo;

   pbdiInfo=(PBTNDLGINFO)WinQueryWindowPtr(hwndWnd,0);

   switch (ulMsg) {
   case WM_INITDLG:
      {
         HPS hpsWnd;

         //----------------------------------------------------------------
         // Get the dialog's instance data and set it in the window words
         //----------------------------------------------------------------
         pbdiInfo=(PBTNDLGINFO)PVOIDFROMMP(mpParm2);
         WinSetWindowPtr(hwndWnd,0,pbdiInfo);

         //----------------------------------------------------------------
         // Load the bitmaps
         //----------------------------------------------------------------
         hpsWnd=WinGetPS(hwndWnd);

         pbdiInfo->hbmOkUp=GpiLoadBitmap(hpsWnd,
                                         NULLHANDLE,
                                         BMP_OKUP,
                                         0,
                                         0);
         pbdiInfo->hbmOkDown=GpiLoadBitmap(hpsWnd,
                                           NULLHANDLE,
                                           BMP_OKDOWN,
                                           0,
                                           0);
         pbdiInfo->hbmCancelUp=GpiLoadBitmap(hpsWnd,
                                             NULLHANDLE,
                                             BMP_CANCELUP,
                                             0,
                                             0);
         pbdiInfo->hbmCancelDn=GpiLoadBitmap(hpsWnd,
                                             NULLHANDLE,
                                             BMP_CANCELDN,
                                             0,
                                             0);

         WinReleasePS(hpsWnd);

         //----------------------------------------------------------------
         // Set the default state of the dialog
         //----------------------------------------------------------------
/* @1 */ WinSendDlgItemMsg(hwndWnd,
                           DBT_CB_ENABLEGROUP,
                           BM_SETCHECK,
                           MPFROMSHORT(TRUE),
                           0);
      }
      break;
   case WM_DESTROY:
      //-------------------------------------------------------------------
      // Cleanup
      //-------------------------------------------------------------------
      GpiDeleteBitmap(pbdiInfo->hbmOkUp);
      GpiDeleteBitmap(pbdiInfo->hbmOkDown);
      GpiDeleteBitmap(pbdiInfo->hbmCancelUp);
      GpiDeleteBitmap(pbdiInfo->hbmCancelDn);
      break;
   case WM_CONTROL:
      switch (SHORT1FROMMP(mpParm1)) {
      case DBT_CB_ENABLEGROUP:
         switch (SHORT2FROMMP(mpParm1)) {
         case BN_CLICKED:
         case BN_DBLCLICKED:
            {
               BOOL bChecked;

               //----------------------------------------------------------
               // Get the check state of the window and enable or disable
               // the radio button group appropriately.
               //----------------------------------------------------------
/* @2 */       bChecked=SHORT1FROMMP(WinSendDlgItemMsg(hwndWnd,
                                                       DBT_CB_ENABLEGROUP,
                                                       BM_QUERYCHECK,
                                                       0,
                                                       0));
               WinEnableWindow(WinWindowFromID(hwndWnd,DBT_RB_CHOICE1),
                               bChecked);
               WinEnableWindow(WinWindowFromID(hwndWnd,DBT_RB_CHOICE2),
                               bChecked);
            }
            break;
         default:
            return WinDefDlgProc(hwndWnd,ulMsg,mpParm1,mpParm2);
         } /* endswitch */
         break;
      case DBT_UB_OK:
      case DBT_UB_CANCEL:
         switch (SHORT2FROMMP(mpParm1)) {
         case BN_PAINT:
            {
               PUSERBUTTON pubButton;
               HBITMAP hbmDraw;
               RECTL rclWnd;
               BITMAPINFOHEADER2 bmihInfo;
               POINTL ptlPoint;

               //----------------------------------------------------------
               // Get the USERBUTTON structure so that we can paint
               // ourselves properly.
               //----------------------------------------------------------
/* @3 */       pubButton=(PUSERBUTTON)PVOIDFROMMP(mpParm2);

               if (pubButton->fsState==BDS_HILITED) {
                  if (SHORT1FROMMP(mpParm1)==DBT_UB_OK) {
                     hbmDraw=pbdiInfo->hbmOkDown;
                  } else {
                     hbmDraw=pbdiInfo->hbmCancelDn;
                  } /* endif */
               } else {
                  if (SHORT1FROMMP(mpParm1)==DBT_UB_OK) {
                     hbmDraw=pbdiInfo->hbmOkUp;
                  } else {
                     hbmDraw=pbdiInfo->hbmCancelUp;
                  } /* endif */
               } /* endif */

               //----------------------------------------------------------
               // Center the bitmap within the window
               //----------------------------------------------------------
/* @4 */       WinQueryWindowRect(pubButton->hwnd,&rclWnd);

               bmihInfo.cbFix=16;
               GpiQueryBitmapInfoHeader(hbmDraw,&bmihInfo);

               ptlPoint.x=rclWnd.xRight/2-bmihInfo.cx/2;
               ptlPoint.y=rclWnd.yTop/2-bmihInfo.cy/2;

               //----------------------------------------------------------
               // Draw the bitmap
               //----------------------------------------------------------
               WinDrawBitmap(pubButton->hps,
                             hbmDraw,
                             NULL,
                             &ptlPoint,
                             0,
                             0,
                             DBM_NORMAL);
            }
            break;
         default:
            return WinDefDlgProc(hwndWnd,ulMsg,mpParm1,mpParm2);
         } /* endswitch */
         break;
      } /* endswitch */
      break;
   case WM_COMMAND:
      switch (SHORT1FROMMP(mpParm1)) {
      case DBT_UB_OK:
      case DID_OK:
         {
            SHORT sChecked;

            //-------------------------------------------------------------
            // Query the tri-state checkbox to see if we should beep.
            // If in "halftoned" state, assume that means "don't care"
            // and beep anyway.
            //-------------------------------------------------------------
/* @5 */    sChecked=SHORT1FROMMP(WinSendDlgItemMsg(hwndWnd,
                                                    DBT_3B_WANTBEEP,
                                                    BM_QUERYCHECK,
                                                    0,
                                                    0));

            switch (sChecked) {
            case 0:
               break;
            case 1:
               WinAlarm(HWND_DESKTOP,WA_NOTE);
               break;
            case 2:
               WinAlarm(HWND_DESKTOP,WA_ERROR);
               break;
            default:
               break;
            } /* endswitch */

            WinDismissDlg(hwndWnd,TRUE);
         }
         break;
      case DBT_UB_CANCEL:
      case DID_CANCEL:
         WinDismissDlg(hwndWnd,FALSE);
         break;
      case DBT_PB_BEEPNOW:
         WinAlarm(HWND_DESKTOP,WA_NOTE);
         break;
      default:
         return WinDefDlgProc(hwndWnd,ulMsg,mpParm1,mpParm2);
      } /* endswitch */
      break;
   default:
      return WinDefDlgProc(hwndWnd,ulMsg,mpParm1,mpParm2);
   } /* endswitch */

   return MRFROMSHORT(FALSE);
}

When you stop to look at it, there are only four messages that are processed: WM_INITDLG and WM_DESTROY for initialization and termination of the dialog box, WM_CONTROL to control the execution path of the dialog box, and WM_COMMAND to process the final result of the dialog box.

Landmarks Again

Look at landmark @1; here, we manually set the check state of the button. Why? If you try the application, you'll see that this checkbox controls whether or not the radio buttons are enabled. If we do not send the BM_SETCHECK message, the checkbox will not be checked (by default) but the radio buttons will be enabled and this doesn't make sense semantically.

At landmark @2, you find that we have to query the button ourselves to see if it is checked before we can proceed. This has always been one of my pet-peeves since it should make sense to provide this information to the application in this message.

Skip to landmark @5 where we find the same BM_QUERYCHECK message as in the WM_CONTROL notification. The reason for pointing it out here is to demonstrate that tri-state buttons return a 0, 1, or 2 to indicate unchecked, checked, or half-toned state.

Back at landmark @3 we see the ownerdrawing code. Button controls pass a pointer to a USERBUTTON structure in mpParm2 for the BN_PAINT notification.

typedef struct _USERBUTTON {
   HWND hwnd;
   HPS hps;
   ULONG fsState;
   ULONG fsStateOld;
} USERBUTTON, *PUSERBUTTON;

hwnd is the handle to the button, hps is the handle to the presentation space in which you are to draw, fsState and fsStateOld describe the current and previous state of the button as a BDS_* constant:

BDS_HILITED
The button is depressed.
BDS_DEFAULT
The button is the default pushbutton.
BDS_DISABLED
The button is disabled.

Note that the explanation for BDS_DEFAULT implies that an ownerdraw button is really a pushbutton incognito.

During the notification, we obtain the pointer to this structure, check the value of fsState, and draw ourselves appropriately at landmark @4. This final one, I will ask you to save for a later time.

Conclusion

Our conclusion is simple: the button control isn't fooling anyone by looking complex. In fact, it is a rather unintimidating control. We also looked at what an ownerdraw control is and how is applies specifically to buttons. As we dissect other controls with this capability, we will learn how the functionality can be utilized.

Next month, we will begin looking at our next window class - the listbox.