Feedback Search Top Backward Forward
EDM/2

The Design and Implementation of VIOWIN (Part 8)

Written by Larry Salomon Jr.

 
Part1 Part2 Part3 Part4 Part5 Part6 Part7 Part8

Introduction

For my job, I once had to write an application that ran only when OS/2 booted from the floppy diskettes. Because I had no access to the functionality PM provides, I resorted to a line-oriented interface, where messages were displayed on the screen and scrolled up when necessary. It was a good interface, I thought; it was fully NLS enabled and had intelligent defaults so the user basically only had to type in the name of the application. Unfortunately, the Quality Assurance team didn't concur with my opinion. "We want a nice interface!" one exclaimed. "Yeah, one with different windows and such!" another shouted.

I was backed into a corner that I could only get out of one way.

This series describes the design and implementation of VIOWIN, a library that implements a subset of the Win APIs provided by PM for fullscreen sessions. The reasoning behind writing this series is that it provided me and will hopefully provide you with some unique insights into how a windowing system is developed; and since it is based on PM, your familiarity with the already defined interface will increase your capability to fully understand what is being described.

Obviously, this series assumes you have PM application development experience, but it isn't required.

This Month

This month, we will (finally) wrap up the series with the VWWC_BUTTON class.

Button Controls

VIOWIN implements three types of button controls: pushbuttons, checkboxes, and radiobuttons. Checkboxes and radiobuttons are assumed to be the "auto" type, meaning that they process the painting themselves. Ownerdrawn buttons are not supported. I need not describe the functionality of each, but it should be noted that even though all three types belong to the same window class, they are different enough that they can "logically" be considered of distinct classes.

There are four helper functions used by the static control (the term "subclass" is used below to refer to the different types of button classes):

pushButtonProc()
processes the messages for the push button "subclass."
checkBoxProc()
processes the messages for the check box "subclass."
radioButtonProc()
processes the messages for the radio button "subclass."
findGroupButton()
finds a specified radio button within a group (first, last, previous, or next).

The first three simply process the WM_PAINT, WM_CHAR, and BM_ messages.

The Instance Data and Other Things

The instance data is shown in the code below. The definitions of the fields are listed afterwards.


#define RB_SELECTED              'o'
#define CB_SELECTED              'x'

typedef struct _INSTDATA {
   ULONG ulSzStruct;
   ULONG ulStyle;
   BOOL bChecked;
} INSTDATA, *PINSTDATA;

Field   Definition
ulSzStruct   Size of the structure in bytes
ulStyle   Style of the window
bChecked   TRUE if this is a checkbox and it is checked.  FALSE otherwise.

The pushButtonProc() Function

The appearance of the button is simply a rectangle with the text centered within. If the button has the focus, the text colors (foreground and background) are inverted. While it would have been nice to draw a border around the button, it was determined that the 200% increase in height (since extra two rows are necessary but the button only has one row of text) wasn't worth it.


MRESULT EXPENTRY pushButtonProc(HVWWND hwndWnd,
				ULONG ulMsg,
				MPARAM mpParm1,
				MPARAM mpParm2)
//-------------------------------------------------------------------------
// This "sub-window procedure" handles the push button-specific messages
//-------------------------------------------------------------------------
{
   PINSTDATA pidData;

   pidData=vwQueryWindowPtr(hwndWnd,1);

   switch (ulMsg) {
   case WM_PAINT:
      {
	 RECTL rclWnd;
	 CHAR achText[256];
	 ULONG ulFore;
	 ULONG ulBack;

	 vwQueryWindowRect(hwndWnd,&rclWnd);
	 rclWnd.xRight--;
	 rclWnd.yTop--;

	 vwQueryWindowText(hwndWnd,sizeof(achText),achText);

	 ulFore=vwQueryForeColor(hwndWnd);
	 ulBack=vwQueryBackColor(hwndWnd);

	 //----------------------------------------------------------------
	 // Fill ourselves with the background color.
	 //----------------------------------------------------------------
	 vwFillRect(hwndWnd,&rclWnd,ulBack);

	 //----------------------------------------------------------------
	 // If we have the focus, invert the colors to indicate this
	 //----------------------------------------------------------------
	 if (vwQueryFocus()==hwndWnd) {
	    ulFore^=0x000000FF;
	    ulBack^=0x000000FF;
	 } /* endif */

	 //----------------------------------------------------------------
	 // Draw the button text
	 //----------------------------------------------------------------
	 vwDrawText(hwndWnd,
		    -1,
		    achText,
		    &rclWnd,
		    ulFore,
		    ulBack,
		    DT_CENTER|DT_VCENTER);
      }
      break;
   case WM_CHAR:
      {
	 USHORT usFlags;
	 USHORT usMods;

	 //----------------------------------------------------------------
	 // Code like this is what makes me appreciate the similarities
	 // of the library to the corresponding PM code.  Here, we
	 // check to see if we've been selected using the <SPACE> or
	 // <ENTER> keys (either one).
	 //----------------------------------------------------------------
	 usFlags=KC_VIRTUALKEY | KC_KEYUP;
	 usMods=KC_ALT | KC_CTRL | KC_SHIFT;

	 if ((CHARMSG(&ulMsg)->fs & usFlags)!=usFlags) {
	    return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
	 } /* endif */

	 if ((CHARMSG(&ulMsg)->fs & usMods)!=0) {
	    return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
	 } /* endif */

	 switch (CHARMSG(&ulMsg)->vkey) {
	 case VK_SPACE:
	 case VK_ENTER:
	 case VK_NEWLINE:
	    vwPostMsg(hwndWnd,BM_CLICK,0,0);
	    break;
	 default:
	    return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
	 } /* endswitch */
      }
      break;
   case WM_QUERYDLGCODE:
      return MRFROMLONG(DLGC_BUTTON);
   case BM_CLICK:
      vwPostMsg(VWHWND_DESKTOP,
		WM_COMMAND,
		MPFROMSHORT(vwQueryWindowUShort(hwndWnd,QWS_ID)),
		0);
      break;
   case BM_QUERYCHECKINDEX:
      return MRFROMSHORT(-1);
   case BM_QUERYHILITE:
      break;
   case BM_SETHILITE:
      break;
   case BM_QUERYCHECK:
      return MRFROMSHORT(FALSE);
   case BM_SETCHECK:
      return MRFROMSHORT(FALSE);
   case BM_SETDEFAULT:
      break;
   default:
      return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
   } /* endswitch */

   return MRFROMLONG(FALSE);
}

The checkBoxProc() Function

Note the choice (in the "Instance Data and Other Things" section) of character for the checked state of the button ("x"). While "¹" is probably a better choice, this character might not exist in other codepages, so we'll stick to one that is more likely to exist.


MRESULT EXPENTRY checkBoxProc(HVWWND hwndWnd,
			      ULONG ulMsg,
			      MPARAM mpParm1,
			      MPARAM mpParm2)
//-------------------------------------------------------------------------
// This "sub-window procedure" handles the check box-specific messages
//-------------------------------------------------------------------------
{
   PINSTDATA pidData;

   pidData=vwQueryWindowPtr(hwndWnd,1);

   switch (ulMsg) {
   case WM_PAINT:
      {
	 CHAR achFmt[256];
	 CHAR chCheck;
	 CHAR achText[256];
	 ULONG ulFore;
	 ULONG ulBack;

	 vwQueryWindowText(hwndWnd,sizeof(achFmt),achFmt);

	 //----------------------------------------------------------------
	 // See if we're checked and, if so, display the appropriate
	 // character in the brackets.
	 //----------------------------------------------------------------
	 chCheck=pidData->bChecked?CB_SELECTED:' ';

	 sprintf(achText,"[%c] %s",chCheck,achFmt);

	 ulFore=vwQueryForeColor(hwndWnd);
	 ulBack=vwQueryBackColor(hwndWnd);

	 //----------------------------------------------------------------
	 // If we have the focus, invert the colors to indicate this
	 //----------------------------------------------------------------
	 if (vwQueryFocus()==hwndWnd) {
	    ulFore^=0x000000FF;
	    ulBack^=0x000000FF;
	 } /* endif */

	 //----------------------------------------------------------------
	 // Draw the button text as we have built it above
	 //----------------------------------------------------------------
	 vwDrawText(hwndWnd,
		    -1,
		    achText,
		    NULL,
		    ulFore,
		    ulBack,
		    DT_LEFT|DT_VCENTER|DT_ERASERECT);
      }
      break;
   case WM_CHAR:
      {
	 USHORT usFlags;
	 USHORT usMods;

	 usFlags=KC_VIRTUALKEY | KC_KEYUP;
	 usMods=KC_ALT | KC_CTRL | KC_SHIFT;

	 if ((CHARMSG(&ulMsg)->fs & usFlags)!=usFlags) {
	    return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
	 } /* endif */

	 if ((CHARMSG(&ulMsg)->fs & usMods)!=0) {
	    return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
	 } /* endif */

	 //----------------------------------------------------------------
	 // If the user pressed the spacebar, toggle the checked state
	 // by sending ourselves a BM_CLICK message
	 //----------------------------------------------------------------
	 switch (CHARMSG(&ulMsg)->vkey) {
	 case VK_SPACE:
	    vwPostMsg(hwndWnd,BM_CLICK,0,0);
	    break;
	 default:
	    return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
	 } /* endswitch */
      }
      break;
   case WM_QUERYDLGCODE:
      return MRFROMLONG(DLGC_BUTTON);
   case BM_CLICK:
      //-------------------------------------------------------------------
      // Toggle the check state, repaint ourselves, and send the
      // notification to our owner, which is always the desktop.
      //-------------------------------------------------------------------
      pidData->bChecked=!pidData->bChecked;
      vwSendMsg(hwndWnd,WM_PAINT,0,0);

      vwSendMsg(VWHWND_DESKTOP,
		WM_CONTROL,
		MPFROM2SHORT(vwQueryWindowUShort(hwndWnd,QWS_ID),
			     BN_CLICKED),
		MPFROMHWND(hwndWnd));
      break;
   case BM_QUERYCHECKINDEX:
      return MRFROMSHORT(-1);
   case BM_QUERYHILITE:
      break;
   case BM_SETHILITE:
      break;
   case BM_QUERYCHECK:
      return MRFROMSHORT(pidData->bChecked);
   case BM_SETCHECK:
      pidData->bChecked=SHORT1FROMMP(mpParm1);
      vwSendMsg(hwndWnd,WM_PAINT,0,0);
      break;
   case BM_SETDEFAULT:
      break;
   default:
      return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
   } /* endswitch */

   return MRFROMLONG(FALSE);
}

The radioButtonProc() Function

This one was the most tricky to handle because of the processing of the arrow keys. In PM, pressing the up or left arrow moves the focus to the previous button in the group and the down and right arrow moves the focus to the next button in the group. After spending a couple of days on this and still not successfully accomplishing the desired behavior, I removed the code to the findGroupButton() function which greatly simplified this window procedure.


MRESULT EXPENTRY radioButtonProc(HVWWND hwndWnd,
				 ULONG ulMsg,
				 MPARAM mpParm1,
				 MPARAM mpParm2)
//-------------------------------------------------------------------------
// This "sub-window procedure" handles the radio button-specific messages
//-------------------------------------------------------------------------
{
   PINSTDATA pidData;

   pidData=vwQueryWindowPtr(hwndWnd,1);

   switch (ulMsg) {
   case WM_PAINT:
      {
	 CHAR achFmt[256];
	 CHAR chCheck;
	 CHAR achText[256];
	 ULONG ulFore;
	 ULONG ulBack;

	 vwQueryWindowText(hwndWnd,sizeof(achFmt),achFmt);

	 //----------------------------------------------------------------
	 // See if we're checked and, if so, display the appropriate
	 // character in the brackets.
	 //----------------------------------------------------------------
	 chCheck=pidData->bChecked?RB_SELECTED:' ';

	 sprintf(achText,"(%c) %s",chCheck,achFmt);

	 ulFore=vwQueryForeColor(hwndWnd);
	 ulBack=vwQueryBackColor(hwndWnd);

	 //----------------------------------------------------------------
	 // If we have the focus, invert the colors to indicate this
	 //----------------------------------------------------------------
	 if (vwQueryFocus()==hwndWnd) {
	    ulFore^=0x000000FF;
	    ulBack^=0x000000FF;
	 } /* endif */

	 //----------------------------------------------------------------
	 // Draw the button text as we have built it above
	 //----------------------------------------------------------------
	 vwDrawText(hwndWnd,
		    -1,
		    achText,
		    NULL,
		    ulFore,
		    ulBack,
		    DT_LEFT|DT_VCENTER|DT_ERASERECT);
      }
      break;
   case WM_CHAR:
      {
	 USHORT usFlags;
	 USHORT usMods;

	 usFlags=KC_VIRTUALKEY | KC_KEYUP;
	 usMods=KC_ALT | KC_CTRL | KC_SHIFT;

	 if ((CHARMSG(&ulMsg)->fs & usFlags)!=usFlags) {
	    return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
	 } /* endif */

	 if ((CHARMSG(&ulMsg)->fs & usMods)!=0) {
	    return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
	 } /* endif */

	 //----------------------------------------------------------------
	 // If the user pressed <SPACE> or either <ENTER> key, select
	 // ourselves.  If they pressed the <UP> or <LEFT> keys, click the
	 // previous button in the group.  If they pressed the <DOWN> or
	 // <RIGHT> keys, click the next button in the group.
	 //----------------------------------------------------------------
	 switch (CHARMSG(&ulMsg)->vkey) {
	 case VK_SPACE:
	 case VK_ENTER:
	 case VK_NEWLINE:
	    vwPostMsg(hwndWnd,BM_CLICK,0,0);
	    break;
	 case VK_UP:
	 case VK_LEFT:
	    {
	       HVWWND hwndPrev;

	       hwndPrev=findGroupButton(hwndWnd,QW_PREV);
	       if (hwndPrev==NULLHANDLE) {
		  hwndPrev=findGroupButton(hwndWnd,QW_BOTTOM);
	       } /* endif */

	       vwSendMsg(hwndPrev,BM_CLICK,MPFROMSHORT(TRUE),0);
	    }
	    break;
	 case VK_DOWN:
	 case VK_RIGHT:
	    {
	       HVWWND hwndNext;

	       hwndNext=findGroupButton(hwndWnd,QW_NEXT);
	       if (hwndNext==NULLHANDLE) {
		  hwndNext=findGroupButton(hwndWnd,QW_TOP);
	       } /* endif */

	       vwSendMsg(hwndNext,BM_CLICK,MPFROMSHORT(TRUE),0);
	    }
	    break;
	 default:
	    return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
	 } /* endswitch */
      }
      break;
   case WM_QUERYDLGCODE:
      return MRFROMLONG(DLGC_BUTTON);
   case BM_CLICK:
      {
	 SHORT sIndex;
	 HVWWND hwndCheck;

	 //----------------------------------------------------------------
	 // If we've been clicked, we need to find the button _within our
	 // group_ that is currently selected, unselect it, and then
	 // select ourselves.  This is not as trivial as it sounds, but
	 // the code below doesn't indicate this.  See findGroupButton().
	 // - - - - - - - - - - - - - -
	 // Send ourselves a BM_QUERYCHECKINDEX to see who currently is
	 // selected in the group.
	 //----------------------------------------------------------------
	 sIndex=SHORT1FROMMR(vwSendMsg(hwndWnd,BM_QUERYCHECKINDEX,0,0));

	 //----------------------------------------------------------------
	 // If sIndex is not -1, find the button and uncheck it.
	 //----------------------------------------------------------------
	 if (sIndex!=-1) {
	    hwndCheck=findGroupButton(hwndWnd,QW_TOP);
	    while (sIndex>0) {
	       hwndCheck=findGroupButton(hwndCheck,QW_NEXT);
	       sIndex--;
	    } /* endwhile */
	 } else {
	    hwndCheck=NULLHANDLE;
	 } /* endif */

	 if (hwndCheck!=NULLHANDLE) {
	    vwSendMsg(hwndCheck,BM_SETCHECK,MPFROMSHORT(FALSE),0);
	 } /* endif */

	 //----------------------------------------------------------------
	 // Check ourselves, set the focus to us, and send the notification
	 // to our owner (the desktop).
	 //----------------------------------------------------------------
	 vwSendMsg(hwndWnd,BM_SETCHECK,MPFROMSHORT(TRUE),0);
	 vwSetFocus(hwndWnd);

	 vwSendMsg(VWHWND_DESKTOP,
		   WM_CONTROL,
		   MPFROM2SHORT(vwQueryWindowUShort(hwndWnd,QWS_ID),
				BN_CLICKED),
		   MPFROMHWND(hwndWnd));
      }
      break;
   case BM_QUERYCHECKINDEX:
      {
	 HWND hwndGroup;
	 SHORT sIndex;

	 //----------------------------------------------------------------
	 // Moving the findGroupButton() code to a separate function
	 // made this a lot easier.  Amen to code-readability!
	 // - - - - - - - - - - - - - -
	 // Start from the top and enumerate all of the buttons in the
	 // group until we find one that is checked.
	 //----------------------------------------------------------------
	 hwndGroup=findGroupButton(hwndWnd,QW_TOP);
	 if (hwndGroup==NULLHANDLE) {
	    return MRFROMSHORT(-1);
	 } /* endif */

	 sIndex=0;

	 if (SHORT1FROMMR(vwSendMsg(hwndGroup,BM_QUERYCHECK,0,0))==TRUE) {
	    return MRFROMSHORT(sIndex);
	 } /* endif */

	 hwndGroup=findGroupButton(hwndGroup,QW_NEXT);

	 while (hwndGroup!=NULLHANDLE) {
	    sIndex++;

	    if (SHORT1FROMMR(vwSendMsg(hwndGroup,BM_QUERYCHECK,0,0))==TRUE)
{

	       return MRFROMSHORT(sIndex);
	    } /* endif */

	    hwndGroup=findGroupButton(hwndGroup,QW_NEXT);
	 } /* endwhile */

	 return MRFROMSHORT(-1);
      }
   case BM_QUERYHILITE:
      break;
   case BM_SETHILITE:
      break;
   case BM_QUERYCHECK:
      return MRFROMSHORT(pidData->bChecked);
   case BM_SETCHECK:
      pidData->bChecked=SHORT1FROMMP(mpParm1);
      vwSendMsg(hwndWnd,WM_PAINT,0,0);
      break;
   case BM_SETDEFAULT:
      break;
   default:
      return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
   } /* endswitch */

   return MRFROMLONG(FALSE);
}

The findGroupButton() Function

  • The previous button of the top button in the group does not exist.
  • The next button of the bottom button in the group does not exist.
  • The previous or next button in the group must be a radio button, but having a checkbox in the middle of the group does not delineate the group.
    
    HVWWND findGroupButton(HVWWND hwndWnd,LONG lCmd)
    //-------------------------------------------------------------------------
    // This function finds the group button in the appropriate position given
    // a button handle in the same group.
    //
    // Input:  hwndWnd - button handle in the group to check
    //         lCmd - a QW_ constant (from the vwQueryWindow() function)
    // Returns:  specified button handle if successful, NULLHANDLE otherwise
    //-------------------------------------------------------------------------
    {
       HVWWND hwndTop;
       HVWWND hwndBottom;
       HVWWND hwndReturn;
       ULONG ulStyle;
       CHAR achClass[256];
       BOOL bIsRadioButton;
       HVWWND hwndLast;
    
       //----------------------------------------------------------------------
       // Of the four QW_ constants, QW_PREV and QW_NEXT make recursive calls
       // specifying QW_TOP and QW_BOTTOM respectively.  Thus, the latter two
       // must be entirely self-contained in order to avoid an endless
       // recursive loop.  Additionally, QW_PREV cannot call QW_NEXT, nor can
       // QW_NEXT call QW_PREV.
       //----------------------------------------------------------------------
    
       switch (lCmd) {
       case QW_PREV:
          //-------------------------------------------------------------------
          // Query the top of the group and check for equality with the
          // specified window.  If they match, return NULLHANDLE.
          //-------------------------------------------------------------------
          hwndTop=findGroupButton(hwndWnd,QW_TOP);
    
          if (hwndWnd==hwndTop) {
    	 return NULLHANDLE;
          } /* endif */
    
          hwndReturn=hwndWnd;
    
          //-------------------------------------------------------------------
          // Work our way backwards in the window chain until we find the
          // first radio button.
          //-------------------------------------------------------------------
          do {
    	 hwndReturn=vwQueryWindow(hwndReturn,QW_PREV);
    	 if (hwndReturn==NULLHANDLE) {
    	    return NULLHANDLE;
    	 } /* endif */
    
    	 ulStyle=vwQueryWindowULong(hwndReturn,QWL_STYLE);
    	 vwQueryClassName(hwndReturn,sizeof(achClass),achClass);
    
    	 bIsRadioButton=((strcmp(achClass,VWWC_BUTTON)==0) &&
    			 (((ulStyle & BS_AUTORADIOBUTTON)!=0) ||
    			  ((ulStyle & BS_RADIOBUTTON)!=0)));
          } while (!bIsRadioButton); /* enddo */
    
          return hwndReturn;
       case QW_NEXT:
          //-------------------------------------------------------------------
          // Query the bottom of the group and check for equality with the
          // specified window.  If they match, return NULLHANDLE.
          //-------------------------------------------------------------------
          hwndBottom=findGroupButton(hwndWnd,QW_BOTTOM);
    
          if (hwndWnd==hwndBottom) {
    	 return NULLHANDLE;
          } /* endif */
    
          hwndReturn=hwndWnd;
    
          //-------------------------------------------------------------------
          // Work our way forewards in the window chain until we find the
          // first radio button.
          //-------------------------------------------------------------------
          do {
    	 hwndReturn=vwQueryWindow(hwndReturn,QW_NEXT);
    	 if (hwndReturn==NULLHANDLE) {
    	    return NULLHANDLE;
    	 } /* endif */
    
    	 ulStyle=vwQueryWindowULong(hwndReturn,QWL_STYLE);
    	 vwQueryClassName(hwndReturn,sizeof(achClass),achClass);
    
    	 bIsRadioButton=((strcmp(achClass,VWWC_BUTTON)==0) &&
    			 (((ulStyle & BS_AUTORADIOBUTTON)!=0) ||
    			  ((ulStyle & BS_RADIOBUTTON)!=0)));
          } while (!bIsRadioButton); /* enddo */
    
          return hwndReturn;
       case QW_TOP:
          //-------------------------------------------------------------------
          // Start with the window specified and work our way backwards until
          // we either reach the beginning
          //-------------------------------------------------------------------
          hwndReturn=hwndWnd;
          hwndLast=hwndReturn;
    
          ulStyle=vwQueryWindowULong(hwndReturn,QWL_STYLE);
    
          while ((ulStyle & WS_GROUP)==0) {
    	 hwndReturn=vwQueryWindow(hwndReturn,QW_PREV);
    
    	 //----------------------------------------------------------------
    	 // If we've reached the beginning of the window chain return the
    	 // last valid one that we saw
    	 //----------------------------------------------------------------
    	 if (hwndReturn==NULLHANDLE) {
    	    return hwndLast;
    	 } /* endif */
    
    	 ulStyle=vwQueryWindowULong(hwndReturn,QWL_STYLE);
    	 vwQueryClassName(hwndReturn,sizeof(achClass),achClass);
    
    	 bIsRadioButton=((strcmp(achClass,VWWC_BUTTON)==0) &&
    			 (((ulStyle & BS_AUTORADIOBUTTON)!=0) ||
    			  ((ulStyle & BS_RADIOBUTTON)!=0)));
    
    	 //----------------------------------------------------------------
    	 // If the current window (hwndReturn) is of the class VWWC_BUTTON
    	 // and the style indicates it's a radio button, remember this
    	 // window handle
    	 //----------------------------------------------------------------
    	 if (bIsRadioButton) {
    	    hwndLast=hwndReturn;
    	 } /* endif */
          } /* endwhile */
    
          return hwndReturn;
       case QW_BOTTOM:
          hwndReturn=hwndWnd;
          hwndLast=hwndReturn;
    
          //-------------------------------------------------------------------
          // Start here and search forward until we find either the end of
          // the window chain or the beginning of the next group.  Note that
          // we can't set ulStyle to the window style because, if this is
          // the first button in the group, we will return it instead of the
          // proper value.
          //-------------------------------------------------------------------
          ulStyle=0;
    
          while ((ulStyle & WS_GROUP)==0) {
    	 hwndReturn=vwQueryWindow(hwndReturn,QW_NEXT);
    
    	 //----------------------------------------------------------------
    	 // If we've reached the end of the window chain return the
    	 // last valid one that we saw
    	 //----------------------------------------------------------------
    	 if (hwndReturn==NULLHANDLE) {
    	    return hwndLast;
    	 } /* endif */
    
    	 ulStyle=vwQueryWindowULong(hwndReturn,QWL_STYLE);
    	 vwQueryClassName(hwndReturn,sizeof(achClass),achClass);
    
    	 bIsRadioButton=((strcmp(achClass,VWWC_BUTTON)==0) &&
    			 (((ulStyle & BS_AUTORADIOBUTTON)!=0) ||
    			  ((ulStyle & BS_RADIOBUTTON)!=0)));
    
    	 //----------------------------------------------------------------
    	 // If the current window (hwndReturn) is of the class VWWC_BUTTON
    	 // and the style indicates it's a radio button, remember this
    	 // window handle
    	 //----------------------------------------------------------------
    	 if ((bIsRadioButton) && ((ulStyle & WS_GROUP)==0)) {
    	    hwndLast=hwndReturn;
    	 } /* endif */
          } /* endwhile */
    
          return hwndLast;
       default:
          return NULLHANDLE;
       } /* endswitch */
    }
    

    The Window Procedure

    The actual window procedure simply handles the WM_CREATE and WM_DESTROY messages, to allocate and destroy the instance data. Other messages are passed to the appropriate "sub window procedure" based on the window style.

    
    MRESULT EXPENTRY VwButtonClassProc(HVWWND hwndWnd,
    				   ULONG ulMsg,
    				   MPARAM mpParm1,
    				   MPARAM mpParm2)
    //-------------------------------------------------------------------------
    // This window procedure simply processes the WM_CREATE and WM_DESTROY
    // messages and otherwise calls the appropriate "sub window procedure"
    // to handle the other messages.  This must be done this way since all
    // button types are of the same class.
    //-------------------------------------------------------------------------
    {
       PINSTDATA pidData;
    
       pidData=vwQueryWindowPtr(hwndWnd,1);
    
       switch (ulMsg) {
       case WM_CREATE:
          pidData=calloc(1,sizeof(INSTDATA));
          if (pidData==NULL) {
    	 return MRFROMSHORT(TRUE);
          } /* endif */
    
          vwSetWindowPtr(hwndWnd,1,pidData);
    
          pidData->ulSzStruct=sizeof(INSTDATA);
          pidData->ulStyle=vwQueryWindowULong(hwndWnd,QWL_STYLE);
          pidData->bChecked=FALSE;
          return MRFROMSHORT(FALSE);
       case WM_DESTROY:
          vwSetWindowPtr(hwndWnd,1,NULL);
          free(pidData);
          return MRFROMSHORT(FALSE);
       default:
          if ((pidData->ulStyle & BS_AUTOCHECKBOX)!=0) {
    	 return checkBoxProc(hwndWnd,ulMsg,mpParm1,mpParm2);
          } else
          if ((pidData->ulStyle & BS_CHECKBOX)!=0) {
    	 return checkBoxProc(hwndWnd,ulMsg,mpParm1,mpParm2);
          } else
          if ((pidData->ulStyle & BS_AUTORADIOBUTTON)!=0) {
    	 return radioButtonProc(hwndWnd,ulMsg,mpParm1,mpParm2);
          } else
          if ((pidData->ulStyle & BS_RADIOBUTTON)!=0) {
    	 return radioButtonProc(hwndWnd,ulMsg,mpParm1,mpParm2);
          } else {
    	 //----------------------------------------------------------------
    	 // Assume it is a pushbutton.
    	 //----------------------------------------------------------------
    	 return pushButtonProc(hwndWnd,ulMsg,mpParm1,mpParm2);
          } /* endif */
       } /* endswitch */
    }
    

    Conclusion

    This concludes the VIOWIN implementation. It has been demonstrated that a subset of PM can be defined for character mode applications, if the proper subset is defined by the designer. By implementing a subset of PM instead of developing a new user interface, we allow the programmer to leverage their PM experience when developing character-mode interfaces. This results in a much higher productivity, since they can "just do it" instead of having to concern themselves with learning how to program a new user interface (which includes the pitfalls and idiosyncrasies also).

    Where can we go from here? To be honest, I have thought of reimplementing VIOWIN many times to include overlapping windows, a hierarchical parent-child and owner-ownee set of relationships, more window classes (especially listboxes and menus), and then more resource types (especially dialog boxes). After this is done, it could be investigated whether or not a subset of the ICLUI ("IBM Class Libraries for User Interface," I believe) could be implemented on top of the new VIOWIN to allow C++ programmers to also gain the advantages of a familiar programming paradigm.

    Part1 Part2 Part3 Part4 Part5 Part6 Part7 Part8

  •