您的位置:首页 > 其它

Win32 Series - The Button Class

2013-12-29 09:24 239 查看
http://www-user.tu-chemnitz.de/~heha/petzold/

The Button Class

We'll begin our exploration of the button window class with a program named BTNLOOK ("button look"), which is shown in Figure 9-1. BTNLOOK creates 10 child window button controls, one for each of the 10 standard styles of buttons.

Figure 9-1. The BTNLOOK program.

BTNLOOK.C

/*----------------------------------------
BTNLOOK.C -- Button Look Program
(c) Charles Petzold, 1998
----------------------------------------*/

#include <windows.h>

struct
{
int     iStyle ;
TCHAR * szText ;
}
button[] =
{
BS_PUSHBUTTON,      TEXT ("PUSHBUTTON"),
BS_DEFPUSHBUTTON,   TEXT ("DEFPUSHBUTTON"),
BS_CHECKBOX,        TEXT ("CHECKBOX"),
BS_AUTOCHECKBOX,    TEXT ("AUTOCHECKBOX"),
BS_RADIOBUTTON,     TEXT ("RADIOBUTTON"),
BS_3STATE,          TEXT ("3STATE"),
BS_AUTO3STATE,      TEXT ("AUTO3STATE"),
BS_GROUPBOX,        TEXT ("GROUPBOX"),
BS_AUTORADIOBUTTON, TEXT ("AUTORADIO"),
BS_OWNERDRAW,       TEXT ("OWNERDRAW")
} ;

#define NUM (sizeof button / sizeof button[0])

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("BtnLook") ;
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 = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
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 ("Button Look"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND  hwndButton[NUM] ;
static RECT  rect ;
static TCHAR szTop[]    = TEXT ("message            wParam       lParam"),
szUnd[]    = TEXT ("_______            ______       ______"),
szFormat[] = TEXT ("%-16s%04X-%04X    %04X-%04X"),
szBuffer[50] ;
static int   cxChar, cyChar ;
HDC          hdc ;
PAINTSTRUCT  ps ;
int          i ;

switch (message)
{
case WM_CREATE :
cxChar = LOWORD (GetDialogBaseUnits ()) ;
cyChar = HIWORD (GetDialogBaseUnits ()) ;

for (i = 0 ; i < NUM ; i++)
hwndButton[i] = CreateWindow ( TEXT("button"),
button[i].szText,
WS_CHILD | WS_VISIBLE | button[i].iStyle,
cxChar, cyChar * (1 + 2 * i),
20 * cxChar, 7 * cyChar / 4,
hwnd, (HMENU) i,
((LPCREATESTRUCT) lParam)->hInstance, NULL) ;
return 0 ;

case WM_SIZE :
rect.left   = 24 * cxChar ;
rect.top    =  2 * cyChar ;
rect.right  = LOWORD (lParam) ;
rect.bottom = HIWORD (lParam) ;
return 0 ;

case WM_PAINT :
InvalidateRect (hwnd, &rect, TRUE) ;

hdc = BeginPaint (hwnd, &ps) ;
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
SetBkMode (hdc, TRANSPARENT) ;

TextOut (hdc, 24 * cxChar, cyChar, szTop, lstrlen (szTop)) ;
TextOut (hdc, 24 * cxChar, cyChar, szUnd, lstrlen (szUnd)) ;

EndPaint (hwnd, &ps) ;
return 0 ;

case WM_DRAWITEM :
case WM_COMMAND :
ScrollWindow (hwnd, 0, -cyChar, &rect, &rect) ;

hdc = GetDC (hwnd) ;
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

TextOut (hdc, 24 * cxChar, cyChar * (rect.bottom / cyChar - 1),
szBuffer,
wsprintf (szBuffer, szFormat,
message == WM_DRAWITEM ? TEXT ("WM_DRAWITEM") :
TEXT ("WM_COMMAND"),
HIWORD (wParam), LOWORD (wParam),
HIWORD (lParam), LOWORD (lParam))) ;

ReleaseDC (hwnd, hdc) ;
ValidateRect (hwnd, &rect) ;
break ;

case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}


As you click on each button, the button sends a WM_COMMAND message to the parent window procedure, which is the familiarWndProc. BTNLOOK's
WndProc displays the wParam and lParam parameters of this message in the right half of the client area, as shown in Figure 9-2.

The button with the style BS_OWNERDRAW is displayed on this window only with a background shading because this is a style of button that the program is responsible for drawing. The button indicates it needs drawing by WM_DRAWITEM messages containing anlParam
message parameter that is a pointer to a structure of type DRAWITEMSTRUCT. These messages are also displayed in BTNLOOK. I'll discuss owner-draw buttons in more detail later in this chapter.



Figure 9-2. The BTNLOOK display.

Creating the Child Windows

BTNLOOK defines a structure called button that contains button window styles and descriptive text strings for each of the 10 types of buttons. The button window styles all begin with the letters BS, which stand for "button style." The 10 button
child windows are created in a for loop during WM_CREATE message processing inWndProc. The
CreateWindow call uses the following parameters:

Class name
TEXT ("button")

Window text
button[i].szText

Window style
WS_CHILD ¦ WS_VISIBLE ¦ button[i].iStyle

x position
cxChar

y position
cyChar * (1 + 2 * i)

Width
20 * xChar

Height
7 * yChar / 4

Parent window
hwnd

Child window ID
(HMENU) i

Instance handle
((LPCREATESTRUCT) lParam) -> hInstance

Extra parameters
NULL

The class name parameter is the predefined name. The window style uses WS_CHILD, WS_VISIBLE, and one of the 10 button styles (BS_PUSHBUTTON, BS_DEFPUSHBUTTON, and so forth) that are defined in the button structure. The window text parameter (which for a
normal window is the text that appears in the caption bar) is text that will be displayed with each button. I've simply used text that identifies the button style.

The x position and y position parameters indicate the placement of the upper left corner of the child window relative to the upper left corner of the parent window's client area. The width and height parameters specify the width and height
of each child window. Notice that I'm using a function named GetDialogBaseUnits to obtain the width and height of the characters in the default font. This is the function that dialog boxes use to obtain text dimensions. The function returns a 32-bit
value comprising a width in the low word and a height in the high word. While
GetDialogBaseUnits returns roughly the same values as can be obtained from theGetTextMetrics function, it's somewhat easier to use and will ensure more consistency with controls in dialog boxes.

The child window ID parameter should be unique for each child window. This ID helps your window procedure identify the child window when processing WM_COMMAND messages from it. Notice that the child window ID is passed in theCreateWindow parameter
normally used to specify the program's menu, so it must be cast to an HMENU.

The instance handle parameter of the CreateWindow call looks a little strange, but we're taking advantage of the fact that during a WM_CREATE messagelParam is actually a pointer to a structure of type CREATESTRUCT ("creation structure")
that has a memberhInstance. So we cast lParam into a pointer to a CREATESTRUCT structure and gethInstance out.

(Some Windows programs use a global variable named hInst to give window procedures access to the instance handle available inWinMain. In
WinMain, you need to simply set

hInst = hInstance ;


before creating the main window. In the CHECKER3 program in
Chapter 7, we used GetWindowLong to obtain this instance handle:

GetWindowLong (hwnd, GWL_HINSTANCE)


Any of these methods is fine.)

After the CreateWindow call, we needn't do anything more with these child windows. The button window procedure within Windows maintains the buttons for us and handles all repainting jobs. (The exception is the button with the BS_OWNERDRAW style;
as I'll discuss later, this button style requires the program to draw the button.) At the program's termination, Windows destroys these child windows when the parent window is destroyed.

The Child Talks to Its Parent

When you run BTNLOOK, you see the different button types displayed on the left side of the client area. As I mentioned earlier, when you click a button with the mouse, the child window control sends a WM_COMMAND message to its parent window. BTNLOOK traps
the WM_COMMAND message and displays the values of wParam and lParam. Here's what they mean:

LOWORD (wParam)Child window ID
HIWORD (wParam)Notification code
lParamChild window handle
If you're converting programs written for the 16-bit versions of Windows, be aware that these message parameters have been altered to accommodate 32-bit handles.

The child window ID is the value passed to CreateWindow when the child window is created. In BTNLOOK, these IDs are 0 through 9 for the 10 buttons displayed in the client area. The child window handle is the value that Windows returns from theCreateWindow
call.

The notification code indicates in more detail what the message means. The possible values of button notification codes are defined in the Windows header files:

Button Notification Code IdentifierValue
BN_CLICKED0
BN_PAINT1
BN_HILITE or BN_PUSHED2
BN_UNHILITE or BN_UNPUSHED3
BN_DISABLE4
BN_DOUBLECLICKED or BN_DBLCLK5
BN_SETFOCUS6
BN_KILLFOCUS7
In reality, you'll never see most of these button values. The notification codes 1 through 4 are for an obsolete button style called BS_USERBUTTON. (It's been replaced with BS_OWNERDRAW and a different notification mechanism.) The notification codes 6 and
7 are sent only if the button style includes the flag BS_NOTIFY. The notification code 5 is sent only for BS_RADIOBUTTON, BS_AUTORADIOBUTTON, and BS_OWNERDRAW buttons, or for other buttons if the button style includes BS_NOTIFY.

You'll notice that when you click a button with the mouse, a dashed line surrounds the text of the button. This indicates that the button has the input focus. All keyboard input now goes to the child window button control rather than to the main window.
However, when the button control has the input focus, it ignores all keystrokes except the Spacebar, which now has the same effect as a mouse click.

The Parent Talks to Its Child

Although BTNLOOK does not demonstrate this fact, a window procedure can also send messages to the child window control. These messages include many of the window messages beginning with the prefix WM. In addition, eight button-specific messages are defined
in WINUSER.H; each begins with the letters BM, which stand for "button message." These button messages are shown in the following table:

Button MessageValue
BM_GETCHECK0x00F0
BM_SETCHECK0x00F1
BM_GETSTATE0x00F2
BM_SETSTATE0x00F3
BM_SETSTYLE0x00F4
BM_CLICK0x00F5
BM_GETIMAGE0x00F6
BM_SETIMAGE0x00F7
The BM_GETCHECK and BM_SETCHECK messages are sent by a parent window to a child window control to get and set the check mark of check boxes and radio buttons. The BM_GETSTATE and BM_SETSTATE messages refer to the normal, or pushed, state of a window when
you click it with the mouse or press it with the Spacebar. We'll see how these messages work when we look at each type of button. The BM_SETSTYLE message lets you change the button style after the button is created.

Each child window has a window handle and an ID that is unique among its siblings. Knowing one of these items allows you to get the other. If you know the window handle of the child, you can obtain the ID using

id = GetWindowLong (hwndChild, GWL_ID) ;


This function (along with SetWindowLong) was used in the CHECKER3 program inChapter 7 to maintain data in a special area reserved when the window
class was registered. The area accessed with the GWL_ID identifier is reserved by Windows when the child window is created. You can also use

id = GetDlgCtrlID (hwndChild) ;


Even though the "Dlg" part of the function name refers to a dialog box, this is really a general-purpose function.

Knowing the ID and the parent window handle, you can get the child window handle:

hwndChild = GetDlgItem (hwndParent, id) ;


Push Buttons

The first two buttons shown in BTNLOOK are "push" buttons. A push button is a rectangle enclosing text specified in the window text parameter of theCreateWindow call. The rectangle takes up the full height and width of the dimensions given in theCreateWindow
or MoveWindow call. The text is centered within the rectangle.

Push-button controls are used mostly to trigger an immediate action without retaining any type of on/off indication. The two types of push-button controls have window styles called BS_PUSHBUTTON and BS_DEFPUSHBUTTON. The "DEF" in BS_DEFPUSHBUTTON stands
for "default." When used to design dialog boxes, BS_PUSHBUTTON controls and BS_DEFPUSHBUTTON controls function differently from one another. When used as child window controls, however, the two types of push buttons function the same way, although BS_DEFPUSHBUTTON
has a heavier outline.

A push button looks best when its height is 7/4 times the height of a text character, which is what BTNLOOK uses. The push button's width must accommodate at least the width of the text, plus two additional characters.

When the mouse cursor is inside the push button, pressing the mouse button causes the button to repaint itself using 3D-style shading to appear as if it's been depressed. Releasing the mouse button restores the original appearance and sends a WM_COMMAND
message to the parent window with the notification code BN_CLICKED. As with the other button types, when a push button has the input focus, a dashed line surrounds the text and pressing and releasing the Spacebar has the same effect as pressing and releasing
the mouse button.

You can simulate a push-button flash by sending the window a BM_SETSTATE message. This causes the button to be depressed:

SendMessage (hwndButton, BM_SETSTATE, 1, 0) ;


This call causes the button to return to normal:

SendMessage (hwndButton, BM_SETSTATE, 0, 0) ;


The hwndButton window handle is the value returned from the CreateWindow call.

You can also send a BM_GETSTATE message to a push button. The child window control returns the current state of the button: TRUE if the button is depressed and FALSE if it isn't depressed. Most applications do not require this information, however. And because
push buttons do not retain any on/off information, the BM_SETCHECK and BM_GETCHECK messages are not used.

Check Boxes

A check box is a square box with text; the text usually appears to the right of the check box. (If you include the BS_LEFTTEXT style when creating the button, the text appears to the left; you'll probably want to combine this style with BS_RIGHT to right-justify
the text.) Check boxes are usually incorporated in an application to allow a user to select options. The check box commonly functions as a toggle switch: clicking the box once causes a check mark to appear; clicking again toggles the check mark off.

The two most common styles for a check box are BS_CHECKBOX and BS_AUTOCHECKBOX. When you use the BS_CHECKBOX style, you must set the check mark yourself by sending the control a BM_SETCHECK message. ThewParam parameter is set to 1 to create a check
mark and to 0 to remove it. You can obtain the current check state of the box by sending the control a BM_GETCHECK message. You might use code like this to toggle the X mark when processing a WM_COMMAND message from the control:

SendMessage ((HWND) lParam, BM_SETCHECK, (WPARAM)
!SendMessage ((HWND) lParam, BM_GETCHECK, 0, 0), 0) ;


Notice the ! operator in front of the second SendMessage call. The
lParam value is the child window handle that is passed to your window procedure in the WM_COMMAND message. When you later need to know the state of the button, send it another BM_GETCHECK message. Or you can retain the current check state in a static variable
in your window procedure. You can also initialize a BS_CHECKBOX check box with a check mark by sending it a BM_SETCHECK message:

SendMessage (hwndButton, BM_SETCHECK, 1, 0) ;


For the BS_AUTOCHECKBOX style, the button control itself toggles the check mark on and off. Your window procedure can ignore WM_COMMAND messages. When you need the current state of the button, send the control a BM_GETCHECK message:

iCheck = (int) SendMessage (hwndButton, BM_GETCHECK, 0, 0) ;


The value of iCheck is TRUE or nonzero if the button is checked and FALSE or 0 if not.

The other two check box styles are BS_3STATE and BS_AUTO3STATE. As their names indicate, these styles can display a third state as well—a gray color within the check box—which occurs when you send the control a WM_SETCHECK message withwParam equal
to 2. The gray color indicates to the user that the selection is indeterminate or irrelevant.

The check box is aligned with the rectangle's left edge and is centered within the top and bottom dimensions of the rectangle that were specified during theCreateWindow call. Clicking anywhere within the rectangle causes a WM_COMMAND message to
be sent to the parent. The minimum height for a check box is one character height. The minimum width is the number of characters in the text, plus two.

Radio Buttons

A radio button is named after the row of buttons that were once quite common on car radios. Each button on a car radio is set for a different radio station, and only one button can be pressed at a time. In dialog boxes, groups of radio buttons are conventionally
used to indicate mutually exclusive options. Unlike check boxes, radio buttons do not work as toggles—that is, when you click a radio button a second time, its state remains unchanged.

The radio button looks very much like a check box except that it contains a little circle rather than a box. A heavy dot within the circle indicates that the radio button has been checked. The radio button has the window style BS_RADIOBUTTON or BS_AUTORADIOBUTTON,
but the latter is used only in dialog boxes.

When you receive a WM_COMMAND message from a radio button, you should display its check by sending it a BM_SETCHECK message withwParam equal to 1:

SendMessage (hwndButton, BM_SETCHECK, 1, 0) ;


For all other radio buttons in the same group, you can turn off the checks by sending them BM_SETCHECK messages withwParam equal to 0:

SendMessage (hwndButton, BM_SETCHECK, 0, 0) ;


Group Boxes

The group box, which has the BS_GROUPBOX style, is an oddity in the button class. It neither processes mouse or keyboard input nor sends WM_COMMAND messages to its parent. The group box is a rectangular outline with its window text at the top. Group boxes
are often used to enclose other button controls.

Changing the Button Text

You can change the text in a button (or in any other window) by calling SetWindowText:

SetWindowText (hwnd, pszString) ;


where hwnd is a handle to the window whose text is being changed and
pszString is a pointer to a null-terminated string. For a normal window, this text is the text of the caption bar. For a button control, it's the text displayed with the button.

You can also obtain the current text of a window:

iLength = GetWindowText (hwnd, pszBuffer, iMaxLength) ;


The iMaxLength parameter specifies the maximum number of characters to copy into the buffer pointed to bypszBuffer. The function returns the string length copied. You can prepare your program for a particular text length by first calling

iLength = GetWindowTextLength (hwnd) ;


Visible and Enabled Buttons

To receive mouse and keyboard input, a child window must be both visible (displayed) and enabled. When a child window is visible but not enabled, Windows displays the text in gray rather than black.

If you don't include WS_VISIBLE in the window class when creating the child window, the child window will not be displayed until you make a call toShowWindow:

ShowWindow (hwndChild, SW_SHOWNORMAL) ;


But if you include WS_VISIBLE in the window class, you don't need to call
ShowWindow. However, you can hide the child window by this call to ShowWindow:

ShowWindow (hwndChild, SW_HIDE) ;


You can determine if a child window is visible by a call to

IsWindowVisible (hwndChild) ;


You can also enable and disable a child window. By default, a window is enabled. You can disable it by calling

EnableWindow (hwndChild, FALSE) ;


For button controls, this call has the effect of graying the button text string. The button no longer responds to mouse or keyboard input. This is the best method for indicating that a button option is currently unavailable.

You can reenable a child window by calling

EnableWindow (hwndChild, TRUE) ;


You can determine whether a child window is enabled by calling

IsWindowEnabled (hwndChild) ;


Buttons and Input Focus

As I noted earlier in this chapter, push buttons, check boxes, radio buttons, and owner-draw buttons receive the input focus when they are clicked with the mouse. The control indicates it has the input focus with a dashed line that surrounds the text. When
the child window control gets the input focus, the parent window loses it; all keyboard input then goes to the control rather than to the parent window. However, the child window control responds only to the Spacebar, which now functions like the mouse. This
situation presents an obvious problem: your program has lost control of keyboard processing. Let's see what we can do about it.

As I discussed in
Chapter 6, when Windows switches the input focus from one window (such as a parent) to another (such as a child window control), it first sends a WM_KILLFOCUS message to the window losing the input focus. ThewParam parameter is the handle of the
window that is to receive the input focus. Windows then sends a WM_SETFOCUS message to the window receiving the input focus, withwParam specifying the handle of the window losing the input focus. (In both cases,wParam might be NULL, which
indicates that no window has or is receiving the input focus.)

A parent window can prevent a child window control from getting the input focus by processing WM_KILLFOCUS messages. Assume that the arrayhwndChild contains the window handles of all child windows. (These were saved in the array during theCreateWindow
calls that created the windows.) NUM is the number of child windows.

case WM_KILLFOCUS :

for (i = 0 ; i < NUM ; i++)
if (hwndChild [i] == (HWND) wParam)
{
SetFocus (hwnd) ;
break ;
}
return 0 ;


In this code, when the parent window detects that it's losing the input focus to one of its child window controls, it callsSetFocus to restore the input focus to itself.

Here's a simpler (but less obvious) way of doing it:

case WM_KILLFOCUS :
if (hwnd == GetParent ((HWND) wParam))
SetFocus (hwnd) ;
return 0 ;


Both these methods have a shortcoming, however: they prevent the button from responding to the Spacebar, because the button never gets the input focus. A better approach would be to let the button get the input focus but also to include the facility for
the user to move from button to button using the Tab key. At first this sounds impossible, but I'll show you how to accomplish it with a technique called "window subclassing" in the COLORS1 program shown later in this chapter.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: