Defining Your Own Controls

Although Windows assumes much of the responsibility for maintaining the dialog box and child window controls, various methods let you slip some of your own code into this process. We've already seen a method that allows you to paint on the surface of a dialog box. You can also use window subclassing (discussed in Chapter 6) to alter the operation of child window controls.

You can also define your own child window controls and use them in a dialog box. For example, suppose you don't particularly care for the normal rectangular push buttons and would prefer to create elliptical push buttons. You can do this by registering a window class and using your own window procedure to process messages for your customized child window. You then specify this window class in a CONTROL statement in the dialog box template. The ABOUT3 program, shown in Figure 10-5, does exactly that.

ABOUT3.MAK

#----------------------

# ABOUT3.MAK make file

#----------------------

about3.exe : about3.obj about3.def about3.res

link about3, /align:16, NUL, /nod slibcew libw, about3

rc about3.res

about3.obj : about3.c about3.h

cl -c -Gsw -Ow -W2 -Zp about3.c

about3.res : about3.rc about3.h about3.ico

rc -r about3.rc

ABOUT3.C

/*------------------------------------------

ABOUT3.C -- About Box Demo Program No. 3

(c) Charles Petzold, 1990

------------------------------------------*/

#include <windows.h>

#include "about3.h"

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

long FAR PASCAL EllipPushWndProc (HWND, WORD, WORD, LONG) ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,

LPSTR lpszCmdLine, int nCmdShow)

{

static char szAppName [] = "About3" ;

MSG msg ;

HWND hwnd ;

WNDCLASS wndclass ;

if (!hPrevInstance)

{

wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc = WndProc ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = LoadIcon (hInstance, szAppName) ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = szAppName ;

wndclass.lpszClassName = szAppName ;

RegisterClass (&wndclass) ;

wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc = EllipPushWndProc ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = NULL ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = COLOR_WINDOW + 1 ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = "EllipPush" ;

RegisterClass (&wndclass) ;

}

hwnd = CreateWindow (szAppName, "About Box Demo Program",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, nCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

BOOL FAR PASCAL AboutDlgProc (HWND hDlg, WORD message, WORD wParam, LONG lParam)

{

switch (message)

{

case WM_INITDIALOG :

return TRUE ;

case WM_COMMAND :

switch (wParam)

{

case IDOK :

EndDialog (hDlg, 0) ;

return TRUE ;

}

break ;

}

return FALSE ;

}

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)

{

static FARPROC lpfnAboutDlgProc ;

static HANDLE hInstance ;

switch (message)

{

case WM_CREATE :

hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;

lpfnAboutDlgProc = MakeProcInstance (AboutDlgProc, hInstance) ;

return 0 ;

case WM_COMMAND :

switch (wParam)

{

case IDM_ABOUT :

DialogBox (hInstance, "AboutBox", hwnd,

lpfnAboutDlgProc) ;

return 0 ;

}

break ;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, message, wParam, lParam) ;

}

long FAR PASCAL EllipPushwndProc (HWND hwnd, WORD message,

WORD wParam, LONG lParam)

{

char szText [40] ;

HBRUSH hBrush ;

HDC hdc ;

PAINTSTRUCT ps ;

RECT rect ;

switch (message)

{

case WM_PAINT :

GetClientRect (hwnd, &rect) ;

GetWindowText (hwnd, szText, sizeof szText) ;

hdc = BeginPaint (hwnd, &ps) ;

hBrush = CreateSolidBrush (GetSysColor (COLOR_WINDOW)) ;

hBrush = SelectObject (hdc, hBrush) ;

SetBkColor (hdc, GetSysColor (COLOR_WINDOW)) ;

SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ;

Ellipse (hdc, rect.left, rect.top, rect.right, rect.bottom) ;

DrawText (hdc, szText, -1, &rect,

DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;

DeleteObject (SelectObject (hdc, hBrush)) ;

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_KEYUP :

if (wParam != VK_SPACE)

break ;

// fall through

case WM_LBUTTONUP :

SendMessage (GetParent (hwnd), WM_COMMAND,

GetWindowWord (hwnd, GWW_ID), (LONG) hwnd) ;

return 0 ;

}

return DefWindowProc (hwnd, message, wParam, lParam) ;

}

ABOUT3.RC

/*---------------------------

ABOUT3.RC resource script

---------------------------*/

#include <windows.h>

#include "about3.h"

about3 ICON about3.ico

About3 MENU

{

POPUP "&Help"

{

MENUITEM "&About About3...", IDM_ABOUT

}

}

#define TABGRP (WS_TABSTOP | WS_GROUP)

AboutBox DIALOG 20, 20, 160, 80

STYLE WS_POPUP | WS_DLGFRAME

{

CTEXT "About3" -1, 0, 12, 160, 8

ICON "About3" -1, 8, 8, 0, 0

CTEXT "About Box Demo Program" -1, 0, 36, 160, 8

CTEXT "(c) Charles Petzold, 1990" -1, 0, 48, 160, 8

CONTROL "OK" IDOK, "EllipPush", TABGRP, 64, 60, 32, 14

}

ABOUT3.H

/*----------------------

ABOUT3.H header file

----------------------*/

#define IDM_ABOUT 1

ABOUT3.ICO

FIG 10-05.epb

ABOUT3.DEF

;-----------------------------------

; ABOUT3.DEF module definition file

;-----------------------------------

NAME ABOUT3

DESCRIPTION 'About Box Demo No. 3 (c) Charles Petzold, 1990'

EXETYPE WINDOWS

STUB 'WINSTUB.EXE'

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD MOVEABLE MULTIPLE

HEAPSIZE 1024

STACKSIZE 8192

EXPORTS WndProc

AboutDlgProc

EllipPushWndProc

The window class we'll be registering is called ”EllipPush“ (”elliptical push button“). Rather than use a DEFPUSHBUTTON statement in the dialog box template, we use a CONTROL statement that specifies this window class:

CONTROL "OK" IDOK, "EllipPush", TABGRP, 64, 60, 32, 14

The dialog box manager uses this window class in a CreateWindow call when creating the child window control in the dialog box.

The ABOUT3.C program registers the ”EllipPush“ window class in WinMain:

wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc = EllipPushWndProc ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = NULL ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = COLOR_WINDOW + 1 ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = "EllipPush" ;

RegisterClass (&wndclass) ;

The window class specifies that the window procedure is EllipPushWndProc, which is also in ABOUT3.C.

The EllipPushWndProc window procedure processes only three messages: WM_PAINT, WM_KEYUP, and WM_LBUTTONUP. During the WM_PAINT message, it obtains the size of its window from GetClientRect and obtains the text that appears in the push button from GetWindowText. It uses the Windows functions Ellipse and DrawText to draw the ellipse and the text.

The processing of the WM_KEYUP and WM_LBUTTONUP messages is very simple:

case WM_KEYUP :

if (wParam != VK_SPACE)

break ;

// fall through

case WM_LBUTTONUP :

SendMessage (GetParent (hwnd), WM_COMMAND,

GetWindowWord (hwnd, GWW_ID), (LONG) hwnd) ;

return 0 ;

The window procedure obtains the handle of its parent window (the dialog box) using GetParent and sends a WM_COMMAND message with wParam equal to the control's ID. The ID is obtained using GetWindowWord. The dialog box window procedure then passes this message on to the dialog box procedure within ABOUT3. The result is a customized push button, as shown in Figure 10-6. You can use this same method to create other customized controls for dialog boxes.

Is that all there is to it? Well, not really. EllipPushWndProc is a bare-bones version of the logic generally involved in maintaining a child window control. For instance, the button doesn't flash like normal push buttons. To invert the colors on the interior of the push button, the window procedure would have to process WM_KEYDOWN (from the Spacebar) and WM_LBUTTONDOWN messages. The window procedure should also capture the mouse on a WM_LBUTTONDOWN message and release the mouse capture (and return the button's interior color to normal) if the mouse is moved outside the child window's client area while the button is still depressed. Only if the button is released while the mouse is captured should the child window send a WM_COMMAND message back to its parent.

EllipPushWndProc also does not process WM_ENABLE messages. As mentioned above, a dialog box procedure can disable a window using the EnableWindow function. The child window would then display gray rather than black text to indicate that it has been disabled and cannot receive messages.

If the window procedure for a child window control needs to store data that are different for each created window, then it can do so by using a positive value of cbWndExtra in the window class structure. This reserves space in the internal window structure that can be accessed by using SetWindowWord, SetWindowLong, GetWindowWord, and GetWindowLong.