Perhaps the epitome of lazy programming is the HEXCALC program, shown in Figure 10-11 beginning on the following page. This program doesn't call CreateWindow at all, never processes WM_PAINT messages, never obtains a device context, and never processes mouse messages. Yet it manages to incorporate a 10-function hexadecimal calculator with a full keyboard and mouse interface in fewer than 150 lines of source code. The calculator is shown in Figure 10-12 on page 485.
HEXCALC.MAK
#-----------------------
# HEXCALC.MAK make file
#-----------------------
hexcalc.exe: hexcalc.obj hexcalc.def hexcalc.res
link hexcalc, /align:16, NUL, /nod slibcew libw, hexcalc
rc hexcalc.res
hexcalc.obj: hexcalc.c
cl -c -Gsw -Ow -W2 -Zp hexcalc.c
hexcalc.res : hexcalc.rc hexcalc.ico
rc -r hexcalc.rc
HEXCALC.C
/*----------------------------------------
HEXCALC.C -- Hexadecimal Calculator
(c) Charles Petzold, 1990
----------------------------------------*/
#include <windows.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;
int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
static char szAppName [] = "HexCalc" ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
if (!hPrevInstance)
{
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = DLGWINDOWEXTRA ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (hInstance, szAppName) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = COLOR_WINDOW + 1 ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
RegisterClass (&wndclass) ;
}
hwnd = CreateDialog (hInstance, szAppName, 0, NULL) ;
ShowWindow (hwnd, nCmdShow) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
void ShowNumber (HWND hwnd, DWORD dwNumber)
{
char szBuffer [20] ;
SetDlgItemText (hwnd, VK_ESCAPE, strupr (ltoa (dwNumber, szBuffer, 16))) ;
}
DWORD CalcIt (DWORD dwFirstNum, short nOperation, DWORD dwNum)
{
switch (nOperation)
{
case '=' : return dwNum ;
case '+' : return dwFirstNum + dwNum ;
case '-' : return dwFirstNum - dwNum ;
case '*' : return dwFirstNum * dwNum ;
case '&' : return dwFirstNum & dwNum ;
case '|' : return dwFirstNum | dwNum ;
case '^' : return dwFirstNum ^ dwNum ;
case '<' : return dwFirstNum << dwNum ;
case '>' : return dwFirstNum >> dwNum ;
case '/' : return dwNum ? dwFirstNum / dwNum : ULONG_MAX ;
case '%' : return dwNum ? dwFirstNum % dwNum : ULONG_MAX ;
default : return 0L ;
}
}
long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
{
static BOOL bNewNumber = TRUE ;
static DWORD dwNumber, dwFirstNum ;
static short nOperation = '=' ;
HWND hButton ;
switch (message)
{
case WM_KEYDOWN : // left arrow --> backspace
if (wParam != VK_LEFT)
break ;
wParam = VK_BACK ;
// fall through
case WM_CHAR :
if ((wParam = toupper (wParam)) == VK_RETURN)
wParam = '=' ;
if (hButton = GetDlgItem (hwnd, wParam))
{
SendMessage (hButton, BM_SETSTATE, 1, 0L) ;
SendMessage (hButton, BM_SETSTATE, 0, 0L) ;
}
else
{
MessageBeep (0) ;
break ;
}
// fall through
case WM_COMMAND :
SetFocus (hwnd) ;
if (wParam == VK_BACK) // backspace
ShowNumber (hwnd, dwNumber /= 16) ;
else if (wParam == VK_ESCAPE) // escape
ShowNumber (hwnd, dwNumber = 0L) ;
else if (isxdigit (wParam)) // hex digit
{
if (bNewNumber)
{
dwFirstNum = dwNumber ;
dwNumber = 0L ;
}
bNewNumber = FALSE ;
if (dwNumber <= ULONG_MAX >> 4)
ShowNumber (hwnd, dwNumber = 16 * dwNumber + wParam -
(isdigit (wParam) ? '0' : 'A' - 10)) ;
else
MessageBeep (0) ;
}
else // operation
{
if (!bNewNumber)
ShowNumber (hwnd, dwNumber =
CalcIt (dwFirstNum, nOperation, dwNumber)) ;
bNewNumber = TRUE ;
nOperation = wParam ;
}
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
HEXCALC.RC
/*----------------------------
HEXCALC.RC resource script
----------------------------*/
#include <windows.h>
HexCalc ICON hexcalc.ico
HexCalc DIALOG 32768, 0, 102, 122
STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
CLASS "HexCalc"
CAPTION "Hex Calculator"
{
PUSHBUTTON "D", 68, 8, 24, 14, 14
PUSHBUTTON "A", 65, 8, 40, 14, 14
PUSHBUTTON "7", 55, 8, 56, 14, 14
PUSHBUTTON "4", 52, 8, 72, 14, 14
PUSHBUTTON "1", 49, 8, 88, 14, 14
PUSHBUTTON "0", 48, 8, 104, 14, 14
PUSHBUTTON "0", 27, 26, 4, 50, 14
PUSHBUTTON "E", 69, 26, 24, 14, 14
PUSHBUTTON "B", 66, 26, 40, 14, 14
PUSHBUTTON "8", 56, 26, 56, 14, 14
PUSHBUTTON "5", 53, 26, 72, 14, 14
PUSHBUTTON "2", 50, 26, 88, 14, 14
PUSHBUTTON "Back", 8, 26, 104, 32, 14
PUSHBUTTON "C", 67, 44, 40, 14, 14
PUSHBUTTON "F", 70, 44, 24, 14, 14
PUSHBUTTON "9", 57, 44, 56, 14, 14
PUSHBUTTON "6", 54, 44, 72, 14, 14
PUSHBUTTON "3", 51, 44, 88, 14, 14
PUSHBUTTON "+", 43, 62, 24, 14, 14
PUSHBUTTON "-", 45, 62, 40, 14, 14
PUSHBUTTON "*", 42, 62, 56, 14, 14
PUSHBUTTON "/", 47, 62, 72, 14, 14
PUSHBUTTON "%", 37, 62, 88, 14, 14
PUSHBUTTON "Equals", 61, 62, 104, 32, 14
PUSHBUTTON "&&", 38, 80, 24, 14, 14
PUSHBUTTON "|", 124, 80, 40, 14, 14
PUSHBUTTON "^", 94, 80, 56, 14, 14
PUSHBUTTON "<", 60, 80, 72, 14, 14
PUSHBUTTON ">", 62, 80, 88, 14, 14
}
HEXCALC.ICO
FIG 1011.epb
HEXCALC.DEF
;------------------------------------
; HEXCALC.DEF module definition file
;------------------------------------
NAME HEXCALC
DESCRIPTION 'Hexadecimal Calculator (c) Charles Petzold, 1990'
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 8192
EXPORTS WndProc
HEXCALC is a normal infix notation calculator that uses C notation for the operations. It works with unsigned 32-bit integers and does addition, subtraction, multiplication, division, and remainders; bitwise AND, OR, and exclusive OR operations; and left and right bit shifts. Division by 0 causes the result to be set to FFFFFFFF.
You can use either the mouse or keyboard with HEXCALC. You begin by ”clicking in“ or typing the first number (up to eight hexadecimal digits), then the operation, and then the second number. You can then show the result by clicking the Equals button or by pressing either the Equals key or Enter key. To correct your entries, you use the Back button or the Backspace or Left Arrow key. Click the ”display“ box or press the Esc key to clear the current entry.
What's so strange about HEXCALC is that the window displayed on the screen seems to be a hybrid of a normal overlapped window and a modeless dialog box. On the one hand, all the messages to HEXCALC are processed in a function called WndProc that appears to be a normal window procedure. The function returns a long, it processes the WM_DESTROY message, and it calls DefWindowProc just like a normal window procedure. On the other hand, the window is created in WinMain with a call to CreateDialog using a dialog box template from HEXCALC.RC. So is HEXCALC a normal overlapped window or a modeless dialog box?
The simple answer is that a dialog box is a window. Normally, Windows uses its own internal window procedure to process messages to a dialog box popup window. Windows then passes these messages to a dialog box procedure within the program that creates the dialog box. In HEXCALC we are forcing Windows to use the dialog box template to create a popup window, but we're processing messages to that window ourselves.
A closer look at HEXCALC.RC will reveal how this is done. The top of the dialog box template looks like this:
HexCalc DIALOG 32768, 0, 102, 122
STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
CLASS "HexCalc"
CAPTION "Hex Calculator"
Notice the identifiers such as WS_OVERLAPPED and WS_MINIMIZEBOX, which we might use to create a normal window using a CreateWindow call. The CLASS statement is the crucial difference between this dialog box and the others we've created so far. When we omitted this statement in previous dialog box templates, Windows registered a window class for the dialog box and used its own window procedure to process the dialog box messages. The inclusion of a CLASS statement here tells Windows to send the messages else- where—specifically, to the window procedure specified in the ”HexCalc“ window class.
The ”HexCalc“ window class is registered in the WinMain function of HEXCALC, just like a window class for a normal window. However, note this very important difference: The cbWndExtra field of the WNDCLASS structure is set to DLGWINDOWEXTRA. This is essential for dialog procedures that you register yourself.
After registering the window class, WinMain calls CreateDialog:
hwnd = CreateDialog (hInstance, szAppName, 0, NULL) ;
The second parameter (the string ”HexCalc“) is the name of the dialog box template. The third parameter, which is normally the window handle of the parent window, is set to 0 because the window has no parent. The last parameter, which is normally the address of the dialog procedure, isn't required because Windows won't be processing the messages and hence can't send them to a dialog procedure.
This CreateDialog call in conjunction with the dialog box template is effectively translated by Windows into a CreateWindow call that does the equivalent of this:
hwnd = CreateWindow ("HexCalc", "Hex Calculator",
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
CW_USEDEFAULT, CW_USEDEFAULT,
102 * 4 / cxChar, 122 * 8 / cyChar,
NULL, NULL, hInstance, NULL) ;
The xChar and yChar variables are the width and height of a system font character.
We reap an enormous benefit from letting Windows make this CreateWindow call: Windows will not stop at creating the 1 popup window but will also call CreateWindow for all 29 child window push-button controls defined in the dialog box template. All these controls send WM_COMMAND messages to the window procedure of the parent window, which is none other than WndProc. This is an excellent technique for creating a window that must contain a collection of child windows.