At the beginning of this chapter, I explained that dialog boxes can be either "modal" or "modeless." So far we've been looking at modal dialog boxes, the more common of the two types. Modal dialog boxes (except system modal dialog boxes) allow the user to switch between the dialog box and other programs. However, the
Modeless dialog boxes are preferred when the user would find it convenient to keep the dialog box displayed for a while. For instance, word processors often use modeless dialog boxes for the text Find and Change dialogs. If the Find dialog box were modal, the user would have to choose Find from the menu, enter the string to be found, end the dialog box to return to the document, and then repeat the entire process to search for another occurrence of the same string. Allowing the user to switch between the document and the dialog box is much more
As you've seen, modal dialog boxes are created using DialogBox . The function returns a value only after the dialog box is destroyed. It returns the value specified in the second parameter of the EndDialog call that was used within the dialog box procedure to terminate the dialog box. Modeless dialog boxes are created using CreateDialog . This function takes the same parameters as DialogBox :
hDlgModeless = CreateDialog (hInstance, szTemplate,
hwndParent, DialogProc) ;
The difference is that the CreateDialog function returns immediately with the window handle of the dialog box. Normally, you store this window handle in a global variable.
Although the use of the
Working with modeless dialog boxes is similar to working with modal dialog boxes, but there are several important differences.
First, modeless dialog boxes usually include a caption bar and a system menu box. These are actually the default options when you create a dialog box in Developer Studio. The STYLE statement in the dialog box template for a modeless dialog box will look something like this:
STYLE WS_POPUP ¦ WS_CAPTION ¦ WS_SYSMENU ¦ WS_VISIBLE
The caption bar and system menu allow the user to move the modeless dialog box to another area of the display using either the mouse or the keyboard. You don't normally provide a caption bar and system menu with a modal dialog box, because the user can't do anything in the underlying window anyway.
The second big difference: Notice that the WS_VISIBLE style is included in our sample STYLE statement. In Developer Studio, select this option from the More Styles tab of the Dialog Properties dialog. If you omit WS_VISIBLE, you must call ShowWindow after the CreateDialog call:
hDlgModeless = CreateDialog ( . . . ) ;
ShowWindow (hDlgModeless, SW_SHOW) ;
If you
The third difference: Unlike messages to modal dialog boxes and message boxes, messages to modeless dialog boxes come through your program's message queue. The message queue must be
while (GetMessage (&msg, NULL, 0, 0))
{
if (hDlgModeless == 0 ¦¦ !IsDialogMessage (hDlgModeless, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}
If the message is intended for the modeless dialog box, then
IsDialogMessage
sends it to the dialog box window procedure and returns TRUE (nonzero);
while (GetMessage (&msg, NULL, 0, 0))
{
if (hDlgModeless == 0 ¦¦ !IsDialogMessage (hDlgModeless, &msg))
{
if (!TranslateAccelerator (hwnd, hAccel, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}
}
Because global
The
hDlgModeless
variable can also be used by other
The final big difference: Use DestroyWindow rather than EndDialog to end a modeless dialog box. When you call DestroyWindow , set the hDlgModeless global variable to NULL.
The user customarily terminates a modeless dialog box by choosing Close from the system menu. Although the Close option is enabled, the dialog box window procedure within Windows does not process the WM_CLOSE message. You must do this yourself in the dialog box procedure:
case WM_CLOSE :
DestroyWindow (hDlg) ;
hDlgModeless = NULL ;
break ;
Note the difference between these two window handles: the hDlg parameter to DestroyWindow is the parameter passed to the dialog box procedure; hDlgModeless is the global variable returned from CreateDialog that you test within the message loop.
You can also allow a user to close a modeless dialog box using push
The COLORS1 program described in Chapter 9 created nine child windows to display three scroll bars and six text items. At that time, the program was one of the more complex we had developed. Converting COLORS1 to use a modeless dialog box makes the program—and particularly its WndProc function—almost ridiculously simple. The revised COLORS2 program is shown in Figure 11-7.
Figure 11-7. The COLORS2 program.
COLORS2.C
/*------------------------------------------------
COLORS2.C -- Version using Modeless Dialog Box
(c) Charles Petzold, 1998
------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK ColorScrDlg (HWND, UINT, WPARAM, LPARAM) ;
HWND hDlgModeless ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("Colors2") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = CreateSolidBrush (0L) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Color Scroll"),
WS_OVERLAPPEDWINDOW WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
hDlgModeless = CreateDialog (hInstance, TEXT ("ColorScrDlg"),
hwnd, ColorScrDlg) ;
while (GetMessage (&msg, NULL, 0, 0))
{
if (hDlgModeless == 0 !IsDialogMessage (hDlgModeless, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY :
DeleteObject ((HGDIOBJ) SetClassLong (hwnd, GCL_HBRBACKGROUND,
(LONG) GetStockObject (WHITE_BRUSH))) ;
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
BOOL CALLBACK ColorScrDlg (HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
{
static int iColor[3] ;
HWND hwndParent, hCtrl ;
int iCtrlID, iIndex ;
switch (message)
{
case WM_INITDIALOG :
for (iCtrlID = 10 ; iCtrlID < 13 ; iCtrlID++)
{
hCtrl = GetDlgItem (hDlg, iCtrlID) ;
SetScrollRange (hCtrl, SB_CTL, 0, 255, FALSE) ;
SetScrollPos (hCtrl, SB_CTL, 0, FALSE) ;
}
return TRUE ;
case WM_VSCROLL :
hCtrl = (HWND) lParam ;
iCtrlID = GetWindowLong (hCtrl, GWL_ID) ;
iIndex = iCtrlID - 10 ;
hwndParent = GetParent (hDlg) ;
switch (LOWORD (wParam))
{
case SB_PAGEDOWN :
iColor[iIndex] += 15 ; // fall through
case SB_LINEDOWN :
iColor[iIndex] = min (255, iColor[iIndex] + 1) ;
break ;
case SB_PAGEUP :
iColor[iIndex] -= 15 ; // fall through
case SB_LINEUP :
iColor[iIndex] = max (0, iColor[iIndex] - 1) ;
break ;
case SB_TOP :
iColor[iIndex] = 0 ;
break ;
case SB_BOTTOM :
iColor[iIndex] = 255 ;
break ;
case SB_THUMBPOSITION :
case SB_THUMBTRACK :
iColor[iIndex] = HIWORD (wParam) ;
break ;
default :
return FALSE ;
}
SetScrollPos (hCtrl, SB_CTL, iColor[iIndex], TRUE) ;
SetDlgItemInt (hDlg, iCtrlID + 3, iColor[iIndex], FALSE) ;
DeleteObject ((HGDIOBJ) SetClassLong (hwndParent, GCL_HBRBACKGROUND,
(LONG) CreateSolidBrush (
RGB (iColor[0], iColor[1], iColor[2])))) ;
InvalidateRect (hwndParent, NULL, TRUE) ;
return TRUE ;
}
return FALSE ;
}
|
COLORS2.RC (excerpts)
//Microsoft Developer Studio generated resource script.
#include "resource.h"
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// Dialog
COLORSCRDLG DIALOG DISCARDABLE 16, 16, 120, 141
STYLE DS_MODALFRAME WS_POPUP WS_VISIBLE WS_CAPTION
CAPTION "Color Scroll Scrollbars"
FONT 8, "MS Sans Serif"
BEGIN
CTEXT "&Red",IDC_STATIC,8,8,24,8,NOT WS_GROUP
SCROLLBAR 10,8,20,24,100,SBS_VERT WS_TABSTOP
CTEXT "0",13,8,124,24,8,NOT WS_GROUP
CTEXT "&Green",IDC_STATIC,48,8,24,8,NOT WS_GROUP
SCROLLBAR 11,48,20,24,100,SBS_VERT WS_TABSTOP
CTEXT "0",14,48,124,24,8,NOT WS_GROUP
CTEXT "&Blue",IDC_STATIC,89,8,24,8,NOT WS_GROUP
SCROLLBAR 12,89,20,24,100,SBS_VERT WS_TABSTOP
CTEXT "0",15,89,124,24,8,NOT WS_GROUP
END
|
RESOURCE.H (excerpts)// Microsoft Developer Studio generated include file. // Used by Colors2.rc #define IDC_STATIC -1 |
Although the original COLORS1 program displayed scroll bars that were based on the size of the window, the new version keeps them at a constant
When you create the dialog box template, use explicit ID
Figure 11-8. The COLORS2 display.
The modeless dialog box is created in COLORS2's WinMain function following the ShowWindow call for the program's main window. Note that the window style for the main window includes WS_CLIPCHILDREN, which allows the program to repaint the main window without erasing the dialog box.
The dialog box window handle returned from
CreateDialog
is stored in the global variable
hDlgModeless
and
while (GetMessage (&msg, NULL, 0, 0))
{
if (!IsDialogMessage (hDlgModeless, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}
Because the dialog box is created before the program enters the message loop and is not destroyed until the program terminates, the value of hDlgModeless will always be valid. I included the logic in case you want to add some code to the dialog box window procedure to destroy the dialog box:
case WM_CLOSE :
DestroyWindow (hDlg) ;
hDlgModeless = NULL ;
break ;
In the original COLORS1 program,
SetWindowText
set the values of the three numeric labels after converting the integers to text with
wsprintf
. The code
wsprintf (szBuffer, TEXT ("%i"), color[i]) ;
SetWindowText (hwndValue[i], szBuffer) ;
The value of
i
was the ID number of the current scroll bar being
The new version uses SetDlgItemInt to set each text field of each child window to a number:
SetDlgItemInt (hDlg, iCtrlID + 3, color [iCtrlID], FALSE) ;
Although SetDlgItemInt and its companion, GetDlgItemInt , are most often used with edit controls, they can also be used to set the text field of other controls, such as static text controls. The iCtrlID variable is the ID number of the scroll bar; adding 3 to the number converts it to the ID for the corresponding numeric label. The third argument is the color value. The fourth argument indicates whether the value in the third argument is to be treated as signed (if the fourth argument is TRUE) or unsigned (if the fourth argument is FALSE). For this program, however, the values range from 0 to 255, so the fourth argument has no effect.
In the process of converting COLORS1 to COLORS2, we passed more and more of the work to Windows. The earlier version called
CreateWindow
10 times; the new version calls
CreateWindow
once and
CreateDialog
once. But if you think that we've reduced our
CreateWindow
calls to a minimum, get a load of this
Perhaps the epitome of lazy programming is the HEXCALC program, shown in Figure 11-9. 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
Figure 11-9. The HEXCALC program.
HEXCALC.C
/*----------------------------------------
HEXCALC.C -- Hexadecimal Calculator
(c) Charles Petzold, 1998
----------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HexCalc") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW CS_VREDRAW;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = DLGWINDOWEXTRA ; // Note!
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (hInstance, szAppName) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateDialog (hInstance, szAppName, 0, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
void ShowNumber (HWND hwnd, UINT iNumber)
{
TCHAR szBuffer[20] ;
wsprintf (szBuffer, TEXT ("%X"), iNumber) ;
SetDlgItemText (hwnd, VK_ESCAPE, szBuffer) ;
}
DWORD CalcIt (UINT iFirstNum, int iOperation, UINT iNum)
{
switch (iOperation)
{
case `=`: return iNum ;
case `+': return iFirstNum + iNum ;
case `-': return iFirstNum - iNum ;
case `*': return iFirstNum * iNum ;
case `&': return iFirstNum & iNum ;
case `': return iFirstNum iNum ;
case `^': return iFirstNum ^ iNum ;
case `<`: return iFirstNum << iNum ;
case `>`: return iFirstNum >> iNum ;
case `/': return iNum ? iFirstNum / iNum: MAXDWORD ;
case `%': return iNum ? iFirstNum % iNum: MAXDWORD ;
default : return 0 ;
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static BOOL bNewNumber = TRUE ;
static int iOperation = `=` ;
static UINT iNumber, iFirstNum ;
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 = (WPARAM) CharUpper ((TCHAR *) wParam)) == VK_RETURN)
wParam = `=` ;
if (hButton = GetDlgItem (hwnd, wParam))
{
SendMessage (hButton, BM_SETSTATE, 1, 0) ;
Sleep (100) ;
SendMessage (hButton, BM_SETSTATE, 0, 0) ;
}
else
{
MessageBeep (0) ;
break ;
}
// fall through
case WM_COMMAND:
SetFocus (hwnd) ;
if (LOWORD (wParam) == VK_BACK) // backspace
ShowNumber (hwnd, iNumber /= 16) ;
else if (LOWORD (wParam) == VK_ESCAPE) // escape
ShowNumber (hwnd, iNumber = 0) ;
else if (isxdigit (LOWORD (wParam))) // hex digit
{
if (bNewNumber)
{
iFirstNum = iNumber ;
iNumber = 0 ;
}
bNewNumber = FALSE ;
if (iNumber <= MAXDWORD >> 4)
ShowNumber (hwnd, iNumber = 16 * iNumber + wParam -
(isdigit (wParam) ? `0': `A' - 10)) ;
else
MessageBeep (0) ;
}
else // operation
{
if (!bNewNumber)
ShowNumber (hwnd, iNumber =
CalcIt (iFirstNum, iOperation, iNumber)) ;
bNewNumber = TRUE ;
iOperation = LOWORD (wParam) ;
}
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
|
HEXCALC.RC (excerpts)//Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Icon HEXCALC ICON DISCARDABLE "HexCalc.ico" ///////////////////////////////////////////////////////////////////////////// #include "hexcalc.dlg" |
HEXCALC.DLG
/*---------------------------
HEXCALC.DLG dialog script
---------------------------*/
HexCalc DIALOG -1, -1, 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
Figure 11-10. The HEXCALC display.
HEXCALC is a normal
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 the Enter key. To correct your entries, 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
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 window. Windows then
Unfortunately, there's something that the dialog box template needs that you can't add in the Dialog Editor in Developer Studio. For this reason, the dialog box template is contained in the HEXCALC.DLG file, which you might guess (correctly) was typed in manually. You can add a text file to any project by picking New from the File menu, picking the Files tab, and selecting Text File from the list of file types. A file such as this, containing additional resource definitions, needs to be included in the resource script. From the View menu, select Resource Includes. This displays a dialog box. In the Compile-time Directives edit field, type
#include "hexcalc.dlg"
This line will then be inserted into the HEXCALC.RC resource script, as shown above.
A close look at the dialog box template in the HEXCALC.DLG file will reveal how HEXCALC uses its own window procedure for the dialog box. The top of the dialog box template looks like
HexCalc DIALOG -1, -1, 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 by using a
CreateWindow
call. The CLASS statement is the crucial difference between this dialog box and the others we've created so far (and it is what the Dialog Editor in Developer Studio doesn't allow us to specify). 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
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 argument (the string "HexCalc") is the
This CreateDialog call, in conjunction with the dialog box template, is effectively translated by Windows into a CreateWindow call that does the equivalent of
hwnd = CreateWindow (TEXT ("HexCalc"), TEXT ("Hex Calculator"),
WS_OVERLAPPED ¦ WS_CAPTION ¦ WS_SYSMENU ¦ WS_MINIMIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT,
102 * 4 / cxChar, 122 * 8 / cyChar,
NULL, NULL, hInstance, NULL) ;
where the cxChar and cyChar variables are the width and height of the dialog font character.
We reap an
Here's another way HEXCALC's code size is kept down to a minimum: You'll notice that HEXCALC contains no header file normally required to define the identifiers for all the child window controls in the dialog box template. We can dispense with this file because the ID number for each of the push-button controls is set to the ASCII code of the text that appears in the control. This means that WndProc can treat WM_COMMAND messages and WM_CHAR messages in much the same way. In each case, the low word of wParam is the ASCII code of the button.
Of course, a little massaging of the keyboard messages is necessary. WndProc traps WM_KEYDOWN messages to translate the Left Arrow key to a Backspace key. During processing of WM_CHAR messages, WndProc converts the character code to uppercase and the Enter key to the ASCII code for the Equals key.
Calling GetDlgItem checks the validity of a WM_CHAR message. If the GetDlgItem function returns 0, the keyboard character is not one of the ID numbers defined in the dialog box template. If the character is one of the IDs, however, the appropriate button is flashed by sending it a couple of BM_SETSTATE messages:
if (hButton = GetDlgItem (hwnd, wParam))
{
SendMessage (hButton, BM_SETSTATE, 1, 0) ;
Sleep (100) ;
SendMessage (hButton, BM_SETSTATE, 0, 0) ;
}
This adds a nice touch to HEXCALC's keyboard interface, and with a minimum of effort. The
Sleep
function suspends the program for 100
When WndProc processes WM_COMMAND messages, it always sets the input focus to the parent window:
case WM_COMMAND :
SetFocus (hwnd) ;
Otherwise, the input focus would be shifted to one of the buttons whenever it was clicked with the mouse.