The Design and Implementation of VIOWIN - Part 6

From EDM2
Jump to: navigation, search
The Design and Implementation of VIOWIN
Part: 1 2 3 4 5 6 7 8

Written by Larry Salomon Jr.

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 begin our look at the windows classes that are implemented in VIOWIN.

Scaffolding

First, we must look at how and where the classes are registered. In issue 2-9, we briefly looked at how ordinal 1 was loaded by vwInitialize() and called to give the DLL an opportunity to perform any initialization it needed. We could have just as easily used the DLL initialization routine, which IBM's C-Set++ product allows you to redefine.

BOOL EXPENTRY vwInitDll(VOID)
//-------------------------------------------------------------------------
// This function registers the classes used by VIOWIN.
//
// Returns:  TRUE if successful, FALSE otherwise
//-------------------------------------------------------------------------
{
   vwRegisterClass(VWWC_BUTTON,VwButtonClassProc);
   vwRegisterClass(VWWC_ENTRYFIELD,VwEntryfieldClassProc);
   vwRegisterClass(VWWC_LISTBOX,VwListboxClassProc);
   vwRegisterClass(VWWC_SCROLLBAR,VwScrollbarClassProc);
   vwRegisterClass(VWWC_STATIC,VwStaticClassProc);

   return TRUE;
}

It needs to be mentioned that the various window procedures are exported in the .DEF file, but this is probably unnecessary since the vwSendMsg() and vwPostMsg() routines simply call the procedure directly using the function address that was passed to vwRegisterClass().

Entryfields

Let us now begin to look at the VWWC_ENTRYFIELD class. Since we should all be familiar with its PM counterpart, we will skip the introduction to the class and will instead dive into the code itself. The first thing that should be discovered is the instance data used by the instantiations of the class.

typedef struct _INSTDATA {
   ULONG ulSzStruct;
   BOOL bChanged;
   BOOL bDirty;
   BOOL bSetParms;
   SHORT sAnchor;
   SHORT sCursor;
   SHORT sFirstChar;
   PCHAR pchBuf;
   USHORT usSzBuf;
} INSTDATA, *PINSTDATA;

ulSzStruct is the size of the structure.
bChanged is TRUE when the entryfield's contents have changed since the last EM_QUERYCHANGED message.
bDirty is TRUE when a call to vwSetWindowText() exceeded the text limit and, thus, the text the VIOWIN system thinks the entryfield has is different from what the entryfield really has.
bSetParms is used in conjunction with bDirty to synchronize the differences in window text.
sAnchor is the anchor point.
sCursor is the cursor point.
sFirstChar is the first visible character index.
pchBuf points to the buffer containing the text.
usSzBuf is the size of the buffer.

Utility Functions

As you can well imagine, there are a number of tasks which are used frequently or are complex enough to warrant a separate function. These functions are listed below:

scrollText() - scrolls the text of the window by one-half of the window's width either left or right.
deltaCursor() - moves the cursor one position to the left or right.
textChanged() - sets bChanged and bDirty and sends an EN_CHANGED notification to the owner.
handleVkey() - processes virtual keystrokes.
handleChr() - processes non-virtual keystrokes.

The scrollText() Function

The code for the function is presented below.

BOOL scrollText(HVWWND hwndWnd,BOOL bLeft)
//-------------------------------------------------------------------------
// This function scrolls the text left or right by half the window's
// width.
//
// Input:  hwndWnd - handle to the window
//	    bLeft - TRUE if scroll goes to the left, FALSE if to the right
// Returns:  TRUE if successful, FALSE otherwise
//-------------------------------------------------------------------------
{
   PINSTDATA pidData;
   RECTL rclWnd;

   //----------------------------------------------------------------------
   // Get the instance data and the window rectangle
   //----------------------------------------------------------------------
   pidData=vwQueryWindowPtr(hwndWnd,1);

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

   //----------------------------------------------------------------------
   // Calculate the new first character
   //----------------------------------------------------------------------
   if (bLeft) {
	pidData->sFirstChar-=(rclWnd.xRight-rclWnd.xLeft)/2;
   } else {
	pidData->sFirstChar+=(rclWnd.xRight-rclWnd.xLeft)/2;
   } /* endif */

   //----------------------------------------------------------------------
   // Do boundary checks
   //----------------------------------------------------------------------
   if (pidData->sFirstChar<0) {
	pidData->sFirstChar=0;
   } /* endif */

   if (pidData->sFirstChar>strlen(pidData->pchBuf)-1) {
	pidData-gt;sFirstChar=strlen(pidData->pchBuf)-1;
   } /* endif */

   //----------------------------------------------------------------------
   // Paint and notify the owner
   //----------------------------------------------------------------------
   vwSendMsg(hwndWnd,WM_PAINT,0,0);

   vwSendMsg(VWHWND_DESKTOP,
		 WM_CONTROL,
		 MPFROM2SHORT(vwQueryWindowUShort(hwndWnd,QWS_ID),
				  EN_SCROLL),
		 MPFROMHWND(hwndWnd));

   return TRUE;
}

This function is fairly straightforward; it calculates the new "first visible character", performs a bounds check, sends the EN_SCROLL notification, and repaints the window.

The deltaCursor() Function

The code for the function is presented below.

BOOL deltaCursor(HVWWND hwndWnd,BOOL bLeft)
//-------------------------------------------------------------------------
// This function moves the cursor one character to the left or right
//
// Input:  hwndWnd - handle to the window
//	     bLeft - TRUE if cursor goes to the left, FALSE if to the right
// Returns:  TRUE if successful, FALSE otherwise
//-------------------------------------------------------------------------
{
   PINSTDATA pidData;
   RECTL rclWnd;

   //----------------------------------------------------------------------
   // Get the instance data and the window rectangle
   //----------------------------------------------------------------------
   pidData=vwQueryWindowPtr(hwndWnd,1);

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

   if (bLeft) {
	//-------------------------------------------------------------------
	// Check to see if we're already at the beginning
	//-------------------------------------------------------------------
	if (pidData->sCursor>0) {
	   pidData->sCursor--;

	   //----------------------------------------------------------------
	   // If the cursor went beyond the left edge, scroll the window
	   //----------------------------------------------------------------
	   if (pidData->sCursor<pidData->sFirstChar) {
		scrollText(hwndWnd,TRUE);
	   } /* endif */

	   //----------------------------------------------------------------
	   // Update the cursor position
	   //----------------------------------------------------------------
	   vwCreateCursor(hwndWnd,
				pidData->sCursor-pidData->sFirstChar+1,
				0,
				1,
				1,
				CURSOR_SETPOS);

	   return TRUE;
	} else {
	   vwAlarm(WA_ERROR);
	   return FALSE;
	} /* endif */
   } else {
	//-------------------------------------------------------------------
	// Check to see if we're already at the end
	//-------------------------------------------------------------------
	if (pidData->sCursor<strlen(pidData->pchBuf)) {
	   pidData->sCursor++;

	   //----------------------------------------------------------------
	   // If the cursor went beyond the right edge, scroll the window
	   //----------------------------------------------------------------
	   if (pidData->sCursor>pidData->sFirstChar+rclWnd.xRight-2) {
		scrollText(hwndWnd,FALSE);
	   } /* endif */

	   //----------------------------------------------------------------
	   // Update the cursor position
	   //----------------------------------------------------------------
	   vwCreateCursor(hwndWnd,
				pidData->sCursor-pidData->sFirstChar+1,
				0,
				1,
				1,
				CURSOR_SETPOS);

	   return TRUE;
	} else {
	   vwAlarm(WA_ERROR);
	   return FALSE;
	} /* endif */
   } /* endif */
}

This function is a bit more complication, but should still be easy to understand. It calculates the new cursor position and calls scrollText() if the cursor goes off-screen.

The textChanged() Function

The code for the function is presented below.

VOID textChanged(HWND hwndWnd)
//-------------------------------------------------------------------------
// This function simply performs a common task of setting the changed
// and dirty flags and sending the owner a changed notification.
//
// Input:  hwndWnd - handle to the window
//-------------------------------------------------------------------------
{
   PINSTDATA pidData;

   pidData=vwQueryWindowPtr(hwndWnd,1);

   pidData->bChanged=TRUE;
   pidData->bDirty=TRUE;

   vwSendMsg(VWHWND_DESKTOP,
		 WM_CONTROL,
		 MPFROM2SHORT(vwQueryWindowUShort(hwndWnd,QWS_ID),EN_CHANGE),
		 MPFROMHWND(hwndWnd));
}

This code is trivial.

The handleVkey() Function

The code for the function is presented below.

BOOL handleVkey(struct _CHARMSG *pcmMsg,HVWWND hwndWnd)
//-------------------------------------------------------------------------
// This function processes the virtual keystrokes.
//
// Input:  pcmMsg - points to the CHARMSG structure
//	     hwndWnd - handle to the window
//
// Returns:  TRUE if successful, FALSE otherwise
//-------------------------------------------------------------------------
{
   PINSTDATA pidData;
   RECTL rclWnd;
   ULONG ulMods;
   ULONG ulIndex;

   //----------------------------------------------------------------------
   // Get the instance data and the window rectangle
   //----------------------------------------------------------------------
   pidData=vwQueryWindowPtr(hwndWnd,1);

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

   ulMods=KC_CTRL | KC_ALT | KC_SHIFT;

   //----------------------------------------------------------------------
   // Check for Control or Alt or Shift key down
   //----------------------------------------------------------------------
   switch (pcmMsg->fs & ulMods) {
   case 0:
	//-------------------------------------------------------------------
	// None of them were down
	//-------------------------------------------------------------------
	switch (pcmMsg->vkey) {
	case VK_LEFT:
	   //----------------------------------------------------------------
	   // Move the cursor
	   //----------------------------------------------------------------
	   if (!deltaCursor(hwndWnd,TRUE)) {
		return FALSE;
	   } /* endif */
	   break;
	case VK_RIGHT:
	   //----------------------------------------------------------------
	   // Move the cursor
	   //----------------------------------------------------------------
	   if (!deltaCursor(hwndWnd,FALSE)) {
		return FALSE;
	   } /* endif */
	   break;
	case VK_BACKSPACE:
	   {
		PCHAR pchFirst;

		//-------------------------------------------------------------
		// Move the cursor and move the text in the buffer to the
		// left one character, effectively erasing the current
		// character
		//-------------------------------------------------------------
		if (deltaCursor(hwndWnd,TRUE)) {
		   pchFirst=&pidData->pchBuf[pidData->sCursor];
		   strcpy(pchFirst,pchFirst+1);

		   vwSendMsg(hwndWnd,WM_PAINT,0,0);
		   textChanged(hwndWnd);
		} else {
		   return FALSE;
		} /* endif */
	   }
	   break;
	case VK_INSERT:
	   //----------------------------------------------------------------
	   // Toggle the insertion mode
	   //----------------------------------------------------------------
	   vwSendMsg(hwndWnd,
			 EM_SETINSERTMODE,
			 MPFROMLONG(!vwQuerySysValue(VWSV_INSERTMODE)),
			 0);
	   break;
	case VK_DELETE:
	   {
		PCHAR pchFirst;

		//-------------------------------------------------------------
		// Move the cursor and move the text in the buffer to the
		// left one character, effectively erasing the character
		// to the right of the cursor
		//-------------------------------------------------------------
		if (pidData->sCursor<strlen(pidData->pchBuf)) {
		   pchFirst=&pidData->pchBuf[pidData->sCursor];
		   strcpy(pchFirst,pchFirst+1);

		   vwSendMsg(hwndWnd,WM_PAINT,0,0);
		   textChanged(hwndWnd);
		} else {
		   vwAlarm(WA_ERROR);
		   return FALSE;
		} /* endif */
	   }
	   break;
	case VK_SPACE:
	   if (pidData->sCursor<pidData->usSzBuf) {
		//-------------------------------------------------------------
		// Insert a space or overwrite the current character with a
		// space
		//-------------------------------------------------------------
		if (vwQuerySysValue(VWSV_INSERTMODE)) {
		   if (strlen(pidData->pchBuf)<pidData->usSzBuf) {
			for (ulIndex=strlen(pidData->pchBuf)+1;
			     ulIndex>pidData->sCursor;
			     ulIndex--) {
			    pidData->pchBuf[ulIndex]=pidData->pchBuf[ulIndex-1];
			} /* endfor */
		   } else {
			vwAlarm(WA_ERROR);
			return FALSE;
		   } /* endif */
		} /* endif */

		pidData->pchBuf[pidData->sCursor]=' ';
		deltaCursor(hwndWnd,FALSE);

		vwSendMsg(hwndWnd,WM_PAINT,0,0);
		textChanged(hwndWnd);
	   } else {
		vwAlarm(WA_ERROR);
		return FALSE;
	   } /* endif */
	   break;
	case VK_HOME:
	   //----------------------------------------------------------------
	   // Move the cursor to the first position
	   //----------------------------------------------------------------
	   pidData->sFirstChar=0;
	   pidData->sCursor=pidData->sFirstChar;

	   vwSendMsg(hwndWnd,WM_PAINT,0,0);

	   vwCreateCursor(hwndWnd,
				pidData->sCursor-pidData->sFirstChar+1,
				0,
				1,
				1,
				CURSOR_SETPOS);
	   break;
	case VK_END:
	   //----------------------------------------------------------------
	   // Move the cursor to the last position
	   //----------------------------------------------------------------
	   pidData->sCursor=strlen(pidData->pchBuf)-1;


pidData->sFirstChar=pidData->sCursor-(rclWnd.xRight-rclWnd.xLeft)/2;

	   if (pidData->sFirstChar<0) {
		pidData->sFirstChar=0;
	   } /* endif */

	   vwSendMsg(hwndWnd,WM_PAINT,0,0);

	   vwCreateCursor(hwndWnd,
				pidData->sCursor-pidData->sFirstChar+1,
				0,
				1,
				1,
				CURSOR_SETPOS);
	   break;
	default:
	   return FALSE;
	} /* endswitch */
	break;
   case KC_CTRL:
	//-------------------------------------------------------------------
	// Control key was down
	//-------------------------------------------------------------------
	switch (pcmMsg->vkey) {
	case VK_LEFT:
	   //----------------------------------------------------------------
	   // Ctrl-Left is the same as "home"
	   //----------------------------------------------------------------
	   pidData->sCursor=0;
	   pidData->sFirstChar=pidData->sCursor;

	   vwSendMsg(hwndWnd,WM_PAINT,0,0);

	   vwCreateCursor(hwndWnd,
				pidData->sCursor-pidData->sFirstChar+1,
				0,
				1,
				1,
				CURSOR_SETPOS);
	   break;
	case VK_RIGHT:
	   //----------------------------------------------------------------
	   // Ctrl-Right is the same as "end"
	   //----------------------------------------------------------------
	   pidData->sFirstChar=strlen(pidData->pchBuf)-1;
	   pidData->sCursor=pidData->sFirstChar;

	   vwSendMsg(hwndWnd,WM_PAINT,0,0);

	   vwCreateCursor(hwndWnd,
				pidData->sCursor-pidData->sFirstChar+1,
				0,
				1,
				1,
				CURSOR_SETPOS);
	   break;
	default:
	   return FALSE;
	} /* endswitch */
	break;
   default:
	return FALSE;
   } /* endswitch */

   return TRUE;
}

This function is one of the workhorses (the other being the handleChr() function). It classifies the keystroke according to the modifiers (Ctrl, Alt, or Shift) and processes the keys individually. It relies heavily on deltaCursor().

The handleChr() Function

The code for the function is presented below.

BOOL handleChr(struct _CHARMSG *pcmMsg,HVWWND hwndWnd)
//-------------------------------------------------------------------------
// This function processes the non-virtual keystrokes.
//
// Input:  pcmMsg - points to the CHARMSG structure
//	     hwndWnd - handle to the window
//
// Returns:  TRUE if successful, FALSE otherwise
//-------------------------------------------------------------------------
{
   PINSTDATA pidData;
   ULONG ulMods;
   ULONG ulIndex;

   pidData=vwQueryWindowPtr(hwndWnd,1);
   ulMods=KC_CTRL | KC_ALT | KC_SHIFT;

   switch (pcmMsg->fs & ulMods) {
   case 0:
   case KC_SHIFT:
	if (pidData->sCursor<pidData->usSzBuf) {
	   //----------------------------------------------------------------
	   // Insert the character or overwrite the current character with
	   // the character
	   //----------------------------------------------------------------
	   if (vwQuerySysValue(VWSV_INSERTMODE)) {
		if (strlen(pidData->pchBuf)<pidData->usSzBuf) {
		   for (ulIndex=strlen(pidData->pchBuf)+1;
			  ulIndex>pidData->sCursor;
			  ulIndex--) {
			 pidData->pchBuf[ulIndex]=pidData->pchBuf[ulIndex-1];
		   } /* endfor */
		} else {
		   vwAlarm(WA_ERROR);
		   return FALSE;
		} /* endif */
	   } /* endif */

	   pidData->pchBuf[pidData->sCursor]=pcmMsg->chr;
	   deltaCursor(hwndWnd,FALSE);
	   vwSendMsg(hwndWnd,WM_PAINT,0,0);
	   textChanged(hwndWnd);

	   vwCreateCursor(hwndWnd,
				pidData->sCursor-pidData->sFirstChar+1,
				0,
				1,
				1,
				CURSOR_SETPOS);
	} else {
	   vwAlarm(WA_ERROR);
	   return FALSE;
	} /* endif */
	break;
   default:
	return FALSE;
   } /* endswitch */

   return TRUE;
}

This function inserts the character pressed into the buffer or overwrites the character at the current cursor position with the character pressed.

The Window Procedure

The window procedure is shown below.

MRESULT EXPENTRY VwEntryfieldClassProc(HVWWND hwndWnd,
						   ULONG ulMsg,
						   MPARAM mpParm1,
						   MPARAM mpParm2)
{
   PINSTDATA pidData;

   pidData=vwQueryWindowPtr(hwndWnd,1);

   switch (ulMsg) {
   case WM_CREATE:
	//-------------------------------------------------------------------
	// Allocate and initialize the instance data
	//-------------------------------------------------------------------
	pidData=calloc(1,sizeof(INSTDATA));
	if (pidData==NULL) {
	   return MRFROMSHORT(TRUE);
	} /* endif */

	vwSetWindowPtr(hwndWnd,1,pidData);

	pidData->ulSzStruct=sizeof(pidData);

	pidData->bChanged=FALSE;
	pidData->bDirty=FALSE;
	pidData->bSetParms=FALSE;
	pidData->sAnchor=0;
	pidData->sCursor=0;
	pidData->sFirstChar=0;
	pidData->pchBuf=NULL;
	pidData->usSzBuf=0;

	vwSendMsg(hwndWnd,EM_SETTEXTLIMIT,MPFROMSHORT(32),0);
	break;
   case WM_DESTROY:
	free(pidData);
	break;
   case WM_PAINT:
	{
	   RECTL rclWnd;
	   CHAR achText[256];
	   ULONG ulFore;
	   ULONG ulBack;
	   PCHAR pchFirst;
	   USHORT usLenVisible;
	   ULONG ulStyle;

	   //----------------------------------------------------------------
	   // Get the window rectangle
	   //----------------------------------------------------------------
	   vwQueryWindowRect(hwndWnd,&rclWnd);
	   rclWnd.xRight--;
	   rclWnd.yTop--;

	   //----------------------------------------------------------------
	   // Initialize the buffer that we will draw to "show" ourselves
	   //----------------------------------------------------------------
	   memset(achText,' ',sizeof(achText));
	   achText[sizeof(achText)-1]=0;

	   //----------------------------------------------------------------
	   // Get the colors and toggle them if we have the focus
	   //----------------------------------------------------------------
	   ulFore=vwQueryForeColor(hwndWnd);
	   ulBack=vwQueryBackColor(hwndWnd);

	   if (vwQueryFocus()==hwndWnd) {
		ulFore^=0x000000FF;
		ulBack^=0x000000FF;
	   } /* endif */

	   //----------------------------------------------------------------
	   // Put the brackets in the buffer
	   //----------------------------------------------------------------
	   achText[0]='[';
	   achText[rclWnd.xRight]=']';

	   //----------------------------------------------------------------
	   // Copy the text to the buffer.	If unreadable, set the
	   // text in the buffer to '*'.
	   //----------------------------------------------------------------
	   pchFirst=&pidData->pchBuf[pidData->sFirstChar];
	   usLenVisible=min(rclWnd.xRight-2,strlen(pchFirst));

	   ulStyle=vwQueryWindowULong(hwndWnd,QWL_STYLE);

	   if ((ulStyle & ES_UNREADABLE)!=0) {
		memset(&achText[1],'*',usLenVisible);
	   } else {
		memcpy(&achText[1],pchFirst,usLenVisible);
	   } /* endif */

	   vwDrawText(hwndWnd,
			  -1,
			  achText,
			  NULL,
			  ulFore,
			  ulBack,
			  DT_LEFT|DT_TOP|DT_ERASERECT);
	}
	break;
   case WM_CHAR:
	{
	   ULONG ulFlags;

	   ulFlags=KC_KEYUP;

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

	   if ((CHARMSG(&ulMsg)->fs & KC_VIRTUALKEY)!=0) {
		if (!handleVkey(CHARMSG(&ulMsg),hwndWnd)) {
		   return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
		} /* endif */
	   } else {
		if (!handleChr(CHARMSG(&ulMsg),hwndWnd)) {
		   return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
		} /* endif */
	   } /* endif */
	}
	return MRFROMSHORT(TRUE);
   case WM_QUERYDLGCODE:
	return MRFROMLONG(DLGC_ENTRYFIELD);
   case WM_SETFOCUS:
	if (SHORT1FROMMP(mpParm2)) {
	   vwSendMsg(VWHWND_DESKTOP,
			 WM_CONTROL,
			 MPFROM2SHORT(vwQueryWindowUShort(hwndWnd,QWS_ID),
					  EN_SETFOCUS),
			 MPFROMHWND(hwndWnd));

	   vwCreateCursor(hwndWnd,
				pidData->sCursor-pidData->sFirstChar+1,
				0,
				1,
				1,
				0);
	   vwShowCursor(hwndWnd,TRUE);
	} else {
	   vwSendMsg(VWHWND_DESKTOP,
			 WM_CONTROL,
			 MPFROM2SHORT(vwQueryWindowUShort(hwndWnd,QWS_ID),
					  EN_KILLFOCUS),
			 MPFROMHWND(hwndWnd));

	   vwShowCursor(hwndWnd,FALSE);
	   vwDestroyCursor(hwndWnd);
	} /* endif */
	break;
   case WM_SETWINDOWPARAMS:
	{
	   PWNDPARAMS pwpParms;
	   MRESULT mrRc;

	   pwpParms=(PWNDPARAMS)PVOIDFROMMP(mpParm1);

	   mrRc=vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);

	   //----------------------------------------------------------------
	   // This is the tricky part:
	   //
	   // If the application calls vwSetWindowText(), then we want to
	   // execute normally.  However, WM_QUERYWINDOWPARAMS calls
	   // vwSetWindowText() whenever the "dirty" flag is set, so
	   // we don't want to do this processing in that case.
	   //----------------------------------------------------------------
	   if ((pwpParms->fsStatus==WPM_TEXT) && (!pidData->bSetParms)) {
		*pidData->pchBuf=0;
		strncat(pidData->pchBuf,pwpParms->pszText,pidData->usSzBuf-1);

		if (pidData->usSzBuf<pwpParms->cchText) {
		   pidData->bDirty=TRUE;
		} else {
		   pidData->bDirty=FALSE;
		} /* endif */

		pidData->bChanged=TRUE;
		pidData->sCursor=0;
		pidData->sFirstChar=0;

		vwSendMsg(hwndWnd,WM_PAINT,0,0);
	   } /* endif */

	   return mrRc;
	}
   case WM_QUERYWINDOWPARAMS:
	{
	   PWNDPARAMS pwpParms;

	   pwpParms=(PWNDPARAMS)PVOIDFROMMP(mpParm1);

	   switch (pwpParms->fsStatus) {
	   case WPM_CCHTEXT:
	   case WPM_TEXT:
		//-------------------------------------------------------------
		// If we're "dirty", set bSetParms to TRUE and call
		// vwSetWindowText() to synch VIOWIN's version of our
		// text with our own copy.
		//-------------------------------------------------------------
		if (pidData->bDirty) {
		   pidData->bSetParms=TRUE;
		   vwSetWindowText(hwndWnd,pidData->pchBuf);
		   pidData->bSetParms=FALSE;

		   pidData->bDirty=FALSE;
		} /* endif */
		break;
	   default:
		break;
	   } /* endswitch */

	   return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
	}
   case EM_QUERYCHANGED:
	{
	   BOOL bChanged;

	   bChanged=pidData->bChanged;
	   pidData->bChanged=FALSE;
	   return MRFROMSHORT(bChanged);
	}
   case EM_QUERYSEL:
	break;
   case EM_SETSEL:
	break;
   case EM_SETTEXTLIMIT:
	{
	   PCHAR pchBuf;

	   //----------------------------------------------------------------
	   // Allocate a new buffer, free the current one, and set
	   // pidData->pchBuf to the new buffer
	   //----------------------------------------------------------------
	   pchBuf=calloc(1,SHORT1FROMMP(mpParm1)+1);
	   if (pchBuf==NULL) {
		return MRFROMSHORT(FALSE);
	   } /* endif */

	   if (pidData->pchBuf!=NULL) {
		*pchBuf=0;
		strncat(pchBuf,pidData->pchBuf,SHORT1FROMMP(mpParm1));

		free(pidData->pchBuf);
	   } /* endif */

	   pidData->pchBuf=pchBuf;
	   pidData->usSzBuf=SHORT1FROMMP(mpParm1);
	   return MRFROMSHORT(TRUE);
	}
   case EM_CUT:
	break;
   case EM_COPY:
	break;
   case EM_PASTE:
	break;
   case EM_QUERYFIRSTCHAR:
	return MRFROMSHORT(pidData->sFirstChar);
   case EM_SETFIRSTCHAR:
	{
	   SHORT sFirstChar;

	   sFirstChar=SHORT1FROMMP(mpParm1);
	   if ((sFirstChar>0) && (sFirstChar<strlen(pidData->pchBuf))) {
		pidData->sFirstChar=sFirstChar;
		return MRFROMSHORT(TRUE);
	   } else {
		return MRFROMSHORT(FALSE);
	   } /* endif */
	}
   case EM_QUERYREADONLY:
	{
	   ULONG ulStyle;

	   ulStyle=vwQueryWindowULong(hwndWnd,QWL_STYLE);
	   return MRFROMSHORT((ulStyle & ES_READONLY)!=0);
	}
   case EM_SETREADONLY:
	{
	   ULONG ulStyle;

	   ulStyle=vwQueryWindowULong(hwndWnd,QWL_STYLE);
	   ulStyle=ulStyle & (~ES_READONLY) |
			  (SHORT1FROMMP(mpParm1) ? ES_READONLY : 0);
	   vwSetWindowULong(hwndWnd,QWL_STYLE,ulStyle);
	   return MRFROMSHORT(TRUE);
	}
   case EM_SETINSERTMODE:
	{
	   BOOL bInsert;

	   bInsert=(SHORT1FROMMP(mpParm1)!=0);
	   vwSetSysValue(VWSV_INSERTMODE,bInsert);
	}
	break;
   default:
	return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
   } /* endswitch */

   return MRFROMLONG(FALSE);
}

The window procedure, too, is fairly trivial, but there are two noteworthy pieces: the processing for the WM_SETWINDOWPARAMS and WM_QUERYWINDOWPARAMS messages. Here is where the bDirty and bSetParms flags are used. Since it would be costly, in terms of performance, to call vwSetWindowText() everytime a character is pressed, the entryfield maintains its own copy of the text and only updates the system's version of the text when someone requests it via vwQueryWindowText(). When this function is called, the system sends us a WM_QUERYWINDOWPARAMS message, during which we check bDirty. If it is TRUE, we set bSetParms and call vwSetWindowText(). This results in us receiving a WM_SETWINDOWPARAMS message, but we don't want to update ourselves because of the annoying flicker that will take place. So, only if bSetParms is not set do we update the internal buffer.

Conclusion

This month, we looked at the code for the entryfield. Next month, we will look at the next window class, VWWC_STATIC. Please send me your thoughts on the approach I've taken this month, so that I can modify it as necessary.

Please note that the source is not included in any of the .ZIP files that accompany this issue. When all of the window classes have been discussed, the source for the entire class library will be given as well as the source for the VIOWIN library.

The Design and Implementation of VIOWIN
Part: 1 2 3 4 5 6 7 8