Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Writing Windows Custom Controls


SP93: Writing Windows Custom Controls

Dan has been a consultant in the Bay Area for the last 20 years specializing in Windows, Windows NT, OS2, and main frame interfaces.


With the overwhelming popularity of Microsoft Windows, many applications have taken on a consistent and familiar look. This consistency is one of the benefits of a graphical interface, but it can become tiresome after a while.

One of the best ways to spiff up your Windows apps is through custom controls. The standard controls provided by Windows--the list box, pushbutton, edit control, and so on--can easily become base classes for developing interesting and powerful custom controls.

Since a control is really a child window with additional parent-notification support, developing a custom control from scratch involves a considerable amount of work. Using a standard control as a base shortens development by allowing you to inherit most of the existing functionality and replace or add only those features that expand functionality. For example, you can modify a basic edit control so that it allows only numeric input. You can replace a scroll bar's paint routine and draw a circular dial instead.

In this article, I'll use the standard Windows radio button as the basis for a VCR-style button. In addition to providing a VCR button that can be used with regular C applications (see Listing One, page 66), I also take advantage of an interface that allows the control to work with Visual C++'s App Studio. Generally, a set of VCR buttons only has a single On state for the set; they exhibit the same behavior as a group of radio buttons but they're in a visually different package.

Superclassing

The technique used to inherit the radio-button behavior is usually called "superclassing." It involves registering a new window class that uses the underlying base window procedure as the default window procedure. Instead of passing messages to DefWindowProc, your code uses the CallWndProc function to call the window procedure of the base class.

The first step in this process is handled by EnhancedButtonInit(), called when the module is loaded. Because the VCR button class is implemented as a standard Windows DLL, it is available for use by any number of applications.

Several important bits of information are set up when registering the new class. The GetClassInfo function is used to retrieve a Windows window-class structure containing the class information for a button control. The window proc address is saved off in a global and replaced with the address of a new window proc. The class name is changed, the instance is adjusted to that of the DLL, and additional data bytes are added to the "window extra bytes." These extra bytes are added to the control to save instance state information for each VCR button. It is important to add the extra bytes since those allocated by the original class will still be used by the base code. The starting offset to the VCR state information is stored in a global variable. The balance of the module is the new window proc and some associated service routines.

Since we primarily want to change the look of the radio button, you'd assume the only message that would need to be modified would be the paint routine. This isn't the case, however, with radio buttons and check boxes. To improve performance on older platforms, Microsoft wrote the radio button so that certain portions of the button are painted when the state of the button changes. To further complicate matters, these states are stored as part of the extra bytes. The standard radio button has three extra bytes attached. This is rather unusual because the Windows API only provides GetWindowWord() and GetWindowLong(), to get word-size or double-word-size extra bytes. These functions address the extra bytes starting from a base of 0 (that is, the end of the original struct). The word at offset 1 holds the handle to the font for the control. The byte at offset 0 contains the control's state flags, such as focus, checked, and active. The offset to these bits is documented as part of the BM_SETSTATE message.

Style Flags

One of the parameters to CreateWindow() is a long word of style information. The high word is reserved for general window styles and the low word is used for individual control styles. If a control was written from scratch, all of the low-word style bits would be available. Since we're superclassing the button style, the reserved style bits defined by Microsoft must be preserved.

Microsoft has not yet defined every bit so some are available for use. The only danger here is compatibility with future versions of Windows. Visual C++ has improved on style bits and extra words for a control by providing a property list. Since the button flags have not been redefined since Version 1.03 of Windows, the risk of incompatibility is pretty low. Should some new flag bits be reserved (as happened with the edit control in version 3.1), you can use a different combination.

For this example, I define a new flag, BSS_VCR. When this style bit is defined, the custom button control will look like a VCR button. The conversion between property lists and style bits is discussed in the Visual C++ interface documentation.

Since we're superclassing the button class, all of the control's underlying behavior remains. By using a special BSS_VCR style, all features of the new code can be ignored if this style is not defined.

Message Handling

The following messages are superclassed by the custom control and are either handled completely or preprocessed before handing off to the base control code:

Border width. A custom message BM_SETBORDER has been defined to allow an application to set the border width of the control externally. The border width is stored in the extra reserved word of the VCR button and is used during the paint procedure to draw the three-dimensional border. The 3-D border gives the control the pushed-in or pushed-out look. For larger buttons, a wider border gives a better look. In Visual C++, the border width is a property. The Visual C++ translation layer converts the property value and assigns it to the internally stored window word. This allows the control to work transparently with C, Visual C++, and Visual Basic.

Control painting. The interior painting is done by the paint routine. The background is filled with either the button face or the button shadow color. This provides a clear indication of whether the button is selected or not. The 3-D border is reversed when the button is checked. A check is also made for a disabled button. Disabled buttons are painted with grayed-out text. Here we use system button colors to paint the button, as configured by the control-panel settings. This provides a consistent interface for both pushbuttons and VCR buttons.

State painting. State painting is done primarily by the BM_SETSTATE message. Here we paint both a border showing the control selection and manage the state bits. Picking up and managing this message relieves the custom control of a great deal of work. Otherwise the mouse and keyboard would have to be managed. Since the base control handles capturing the mouse and keyboard messages only, the state bits have to be handled at this level. Normally, we would even allow the base control to handle this message by first calling forward and then doing any extra work. This is not possible here since the base radio button paints the button portion in this message. Handling the state means setting or clearing the highlight bit in the first window extra byte. Since the minimum of a word can be retrieved, the high-order byte must be preserved. Changing the high byte can destroy the font handle for the control and cause a crash. For this reason the first window word is always read before modifying and writing back to the control.

Setting state. The current checked state of the button is also maintained in the first window extra byte. Here again, the base control handles almost all of the work, but the BM_SETCHECK message must be handled since the radio button paints the On/Off portion of the radio control on this message. The least-significant bit in the low word is toggled to indicate state. The control is then invalidated, forcing the paint routine to handle the visual change. One other operation is carried out here. Since radio buttons can be defined as auto radio buttons, the VCR button should emulate the same behavior. This keeps the VCR button functionally compatible with the radio button. Since we can't let the radio button handle the BM_SETCHECK message without messing up the screen. Therefore, to support the auto feature, other buttons in the group are managed by BtnWalk(), which is called twice: once, to find all VCR buttons before the current button, and a second time, to find all those after. The button-walk function simply turns off any buttons that are part of the same group.

The rest of the story. All other messages are handled by the base radio-button code, including notifying the parent and handling such special-purpose messages as BM_GETCHECK and BM_

GETSTATE. I've provided a test program in both C and C++ to exercise the buttons. In addition, I've coded an interface layer (in C) as a separate DLL that allows the control to be used by both Visual C++ and Visual Basic.

Using App Studio

The protocol defined by the App Studio in Visual C++ allows old-style controls to be used, but doesn't allow test drawing or setting of styles. App Studio defines a new protocol that increases the role of custom controls by allowing custom and standard properties to be set for a control both at design time and at run time. Most of this interface is documented in the Custom Control Pack that comes as part of Professional Visual Basic 2.0; you can also obtain it as an add-on SDK to Visual C++. Most controls are written in C, and a special control procedure is coded to handle the additional Visual C++/Basic messages.

By providing some bridge code, you can add a Visual C++/Basic front end to an old-style custom control. First you must add to the custom control the extra functionality expected by the Visual C++/Basic protocol, and your code must translate property requests to and from the underlying control. Done this way, a control can be used in all environments. To add flexibility, you could develop an additional DLL that allows the control to be edited by both the normal Dialog Editor as well as Borland's Resource Workshop. The only two custom properties implemented in the Visual VCR button are the current value and the bevel width. The Visual interface does not allow custom-style bits to be set directly, and the Standard-C style of custom control does not support properties. To provide the bridge, two types of translation are required: style-bit and property.

Style-bit Translation

The Visual control provides a control procedure that is called by the Visual products before a call is made to the normal window procedure. This procedure is similar to the superclassing used to add custom behavior to the VCR button. When the control procedure is called at creation time, any associated properties can be read and translated to style bits. This happens in the control procedure in VCR.C (along with other files, available electronically; see "Availability," page 3) on the WM_NCCREATE message. The custom BSS_VCR

BUTTON style is ORed into the Windows-style long word. This style data is then automatically passed down to the VCR-button window procedure when the control is created and the underlying control behaves as a VCR button.

Property Translation

Since not all information contained in a control's property can be passed via style bits, an additional mechanism is needed. This is implemented by translating property information into data held by the control as "extra window words." For instance, the bevel-size property is an example of this approach. When a bevel-size set-property command is sent to the Visual control procedure, it gets translated to a SendMessage() call (of a BM_SETBORDER message) and is sent to the underlying control. The VCR button sets the bevel width to the passed size. Any number of custom properties can be added to a custom control and handled in this fashion. Standard properties such as fonts are also handled in this manner automatically and require no additional code if the underlying control supports the WM_SETFONT message.

A Value property has been implemented to interface the control with the Visual Basic environment. When a Visual Basic code segment is called, the Value can be set to True or False. This causes a set property command to be sent to the interface layer, which in turn sends a BM_SETSTATE message to the underlying control. The accompanying code is well documented and contains further details on my implementation.

Conclusion

You can see the power and flexibility of custom controls in Windows, especially if you use a general framework for their implementation. By pushing application functionality down to the level of custom controls, Windows development becomes simpler and more consistent. Additional enhancements might include combining bitmaps with button faces, adding data-entry validation to edit controls (via "picture" masks), or creating pop-up helper windows for tasks such as selecting Zip codes from a database.

Dan Brindle


[LISTING ONE]


/* ----------------------------------------------------------------
 * VCRBUT.C -- This module implements a VCR button as a superclass of the
 * standard Windows radio button. The support provided by this file is C code
 * and provides a global class as a DLL. This code accompanies the article
 * "Writing Windows Custom Controls" by Dan Brindle and is provided courtesy
 * of Scopus Technology Inc. (Emeryville, CA). Thanks also to Bill Breedlove,
 * Evergreen Productions, for assistance with code revision and preparation.
 * The interface routines are: CustomButtonInit, BtnWalk, FocusBorder,
 * chk_btn_style and WndProcButton.
 * ----------------------------------------------------------------*/

#define STRICT
#define _WINDLL
#define NOMINMAX
#define NOCOMM
#define NOICONS
#define NOKEYSTATES
#define NOSYSCOMMANDS
#define NOOEMRESOURCE
#define NOATOM
#define NOCLIPBOARD
#define NOSOUND
#define NOWH
#define NOKANJI
#define NOHELP
#define NOPROFILER
#define NODEFERWINDOWPOS

#include <windows.h>
#include <string.h>

#include "vcrbut.h"             // Public definitions
#include "winctl.h"             // Local defintions

/*------------------global variables--------------------------------*/
HANDLE hInst;           // current instance of DLL
WNDPROC OldButtonProc;  // Address to underlying button procedure
int extra;              // Offset to custom defined window extra bytes

/* -----------------------------------------------------------------
 * CustomButtonInit( hInst ) -- registers class for VCR button control. It's
 * called from LibMain. Allows custom windows classes to be registered when
 * the module is loaded, insuring class is always available to application.
 * Entry: Handle to DLL instance
 * Returns: Result of the RegisterClass()
 * LibMain will fail if class is not registered. This means that application
 * will also fail to load. Unfortunately, it isn't possible to bring up a
 * message box while the DLL is being loaded. Generally class registration will
 * not fail. If error tracking is a requirement, then error messages can be
 * written to a file at load time.
 * ----------------------------------------------------------------*/
BOOL VCRButtonInit( HINSTANCE hInst )
{
        WNDCLASS WndClass;
        /* Get the underlying class to use.  Since the radio button is a
        ** style of button the class information is requested from Windows. */
        GetClassInfo( NULL, "button", &WndClass );

        /* Save address of button window procedure. To be used by superclassed
        ** VCR button to pass messages that don't need additional processing.*/
        OldButtonProc = WndClass.lpfnWndProc;

        /* Register the new class. The class is made global so that
        ** all applications can create VCR buttons */
        WndClass.style          |= CS_GLOBALCLASS;
        WndClass.lpfnWndProc    = (WNDPROC) WndProcButton;
        extra = WndClass.cbWndExtra;
        WndClass.cbWndExtra     += BTN_EXTRA;
        WndClass.hInstance      = hInst;
        WndClass.hbrBackground  = (HBRUSH) GetStockObject(BLACK_BRUSH);
        WndClass.lpszMenuName   = NULL;
        WndClass.lpszClassName  = (LPSTR) "VCRButton";

        return( RegisterClass( &WndClass ) );
}
/* ------------------------------------------------------------------
 * three_D_border() -- highlights a control and creates a 3-D effect. Done
 * within client area of control. This could be modified to use non-client area
 * if a lot of drawing was done on client area. Entry params: Handle to window
 * to highlight. Mode variable is true if pushed in, false if popped out.
 * ----------------------------------------------------------------*/
void three_D_border( HWND hWnd, int mode )
{
        RECT rect;
        HPEN hPen, hPenOld;
        HBRUSH hBrush, hBrushOld;
        COLORREF tmp_clr;
        int border_width;
        POINT pt[8];

        HDC hDC = GetDC( hWnd );
        GetClientRect( hWnd, &rect );

        // Get the internal border width for this instance of the control.
        border_width = GetWindowWord( hWnd, extra + BTN_BORDER ) & 0x00ff;

        if ( mode & 0x0001 )    tmp_clr = GetSysColor( COLOR_BTNFACE );
        else                    tmp_clr = GetSysColor( COLOR_BTNHIGHLIGHT );

        hPen      = CreatePen( PS_SOLID, 1, tmp_clr );
        hBrush    = CreateSolidBrush( tmp_clr );
        hPenOld   = SelectObject( hDC, hPen );
        hBrushOld = SelectObject( hDC, hBrush );

        /* Draw the top border. Here we use the polygon so that we get
        ** diagonal corners.  This is not important on a thin border
    ** but makes a difference when the border is wider than 3 pixels. */
        pt[0].x = pt[6].x = rect.left;
        pt[0].y = pt[6].y = rect.top;
        pt[1].x = border_width - 1;
        pt[1].y = pt[2].y = border_width - 1;
        pt[2].x = pt[3].x = rect.right - border_width;
        pt[3].y = rect.bottom - border_width;
        pt[4].x = pt[5].x = rect.right - 1;
        pt[4].y = rect.bottom - 1;
        pt[5].y = rect.top;

        Polygon( hDC, pt, 7 );

        SelectObject( hDC, hPenOld );
        DeleteObject( hPen );
        SelectObject( hDC, hBrushOld );
        DeleteObject( hBrush );

        if ( mode & 0x0001 )  tmp_clr = GetSysColor( COLOR_BTNHIGHLIGHT );
        else                  tmp_clr = GetSysColor( COLOR_BTNSHADOW );

        hPen      = CreatePen( PS_SOLID, 1, tmp_clr );
        hBrush    = CreateSolidBrush( tmp_clr );
        hPenOld   = SelectObject( hDC, hPen );
        hBrushOld = SelectObject( hDC, hBrush );

        /*------- Draw the underline -----*/
        pt[0].x = pt[1].x = pt[6].x = rect.left;
        pt[0].y = pt[6].y = rect.top;
        pt[1].y = pt[2].y = rect.bottom - 1;
        pt[2].x = rect.right - 2;
        pt[3].x = rect.right - border_width - 1;
        pt[3].y = rect.bottom - border_width;
        pt[4].x = pt[5].x = border_width - 1;
        pt[4].y = rect.bottom - border_width;
        pt[5].y = rect.top + border_width - 1;

        Polygon( hDC, pt, 7 );

        /*-------- Cleanup up before exit ----*/
        SelectObject( hDC, hPenOld );
        DeleteObject( hPen );
        SelectObject( hDC, hBrushOld );
        DeleteObject( hBrush );

        if ( mode & 0x0004 )
        {
                hPen = CreatePen( PS_SOLID, border_width / 3 + 1, 0 );
                hPenOld = SelectObject( hDC, hPen );
                hBrushOld = SelectObject( hDC, GetStockObject( NULL_BRUSH ));
                Rectangle( hDC, rect.left, rect.top, rect.right, rect.bottom );
                SelectObject( hDC, hPenOld );
                SelectObject( hDC, hBrushOld );
                DeleteObject( hPen );
        }
        ReleaseDC( hWnd, hDC );
        return;
}
/* -----------------------------------------------------------------
 * button_walk() -- used by auto radio button style. All VCR buttons in a group
 * are checked and state set to off. Allows automatic checking of VCR buttons.
 * Params: Starting window handle, Direction to walk the window list.
 * ----------------------------------------------------------------*/
static void button_walk( HWND hWnd, WORD wDirection )
{
    HWND hTmp;
    static char class_name[ 33 ];
    LONG lStyle;
    int tmp_chk;

    hTmp = hWnd;
    while ( hTmp = GetWindow( hTmp, wDirection ) )
    {
            /* Get the class name and check for
            ** the button class of some sort.  This
            ** is a bit dangerous as some class names may
            ** contain button and not conform to the
            ** style bits.  To limit this to VCRBUTTON
            ** then use a strcmp(). */
            GetClassName( hTmp, class_name, 32 );
            _fstrupr( class_name );
            if ( _fstrstr( class_name, "BUTTON" ) )
                    return;
            /* Get the style and look for the AutoRadio style bit.
            ** The BSS_AUTOVCRBUTTON flag automatically sets this bit. */
            lStyle = GetWindowLong( hTmp, GWL_STYLE );
            if ( lStyle & ( BS_AUTORADIOBUTTON ) )
            {
                    // stop if we reach next group
                    if ( lStyle & WS_GROUP )
                            break;
                    /* A valid radio button found, make sure it is unchecked.
                    ** Do this internally so that we don't generate
                    ** additional auto walk. */
                    if (( tmp_chk = GetWindowWord( hTmp, extra )) & 0x0001 )
                    {
                         three_D_border( hTmp, tmp_chk );
                         SetWindowWord( hTmp, extra, tmp_chk &= ~0x0001 );
                    }
            }
            else
                    break;
    }
}
/* -----------------------------------------------------------------
 * focus_border() -- displays a focus border for the VCR button.
 * The focus border consists of a dashed rectangle around the text.
 * Entry: Handle to a control.  Mode var controls adding or removing border.
 * ----------------------------------------------------------------*/
static void focus_border( HWND hWnd, int Mode )
{
    RECT rect;
    HDC hDC;
    int border_width;

    // Get the current border width
    border_width = GetWindowWord( hWnd, extra + BTN_BORDER ) & 0x00ff;

    // Always draw this on the client rectangle
    hDC = GetDC( hWnd );
    GetClientRect( hWnd, &rect );
    InflateRect( &rect,
            (GetSystemMetrics( SM_CXBORDER ) * -2) - border_width,
            (GetSystemMetrics( SM_CYBORDER ) * -2) - border_width );
    DrawFocusRect( hDC, &rect );    // Put up or take down the focus rectangle
    ReleaseDC( hWnd, hDC );
}
/* -----------------------------------------------------------------
 * chk_btn_style() -- a helper function that helps separate style bits
 * and returns the result. Entry: Handle to a control
 * ----------------------------------------------------------------*/
static int chk_btn_style( HWND hWnd )
{
    LONG style;
    style = GetWindowLong( hWnd, GWL_STYLE );
    if ( (style & ( BS_AUTORADIOBUTTON )) == BS_AUTORADIOBUTTON )
                return( BS_AUTORADIOBUTTON );
    if ( (style & ( BS_RADIOBUTTON )) == BS_RADIOBUTTON )
                return( BS_RADIOBUTTON );
    return( 0 );
}
/* -----------------------------------------------------------------
 * WndProcVCRButton() -- main window procedure for VCR button. Any messages
 * that need special handling are processed here. Rest of messages to support
 * regular button processing are passed to regular button windows procedure via
 * the address saved when the VCR button class is registered.
 * ----------------------------------------------------------------*/
LRESULT CALLBACK
WndProcButton( HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
    LONG lStyle;
    RECT rect;
    HFONT hFont;
    WORD tmp_chk;
    switch ( wMsg )
    {
        /* Do some extra processing if we are in the custom mode.
        ** In this case we set a default border width for the VCR button. */
        case WM_CREATE:

        if ( GetWindowLong( hWnd, GWL_STYLE ) & BSS_VCR )
                if (( chk_btn_style( hWnd ) == BS_AUTORADIOBUTTON ) ||
                     (chk_btn_style( hWnd ) == BS_RADIOBUTTON ))
                          SendMessage( hWnd,
                                   BM_SETBORDER, DEFAULT_BTN_BORDER, 0L );
                break;
        /* Handle the calculation of the non-client area. Normal radio button
        ** reserves no area for a border. Here we pass WM_NCCALCSIZE to default
        ** window procedure which will calc an area based on type of
        ** of border the VCR Button has. */
        case WM_NCCALCSIZE:
           if (( lStyle = GetWindowLong( hWnd, GWL_STYLE )) & BSS_VCR )
                  if (( chk_btn_style( hWnd ) == BS_AUTORADIOBUTTON ) ||
                        (chk_btn_style( hWnd ) == BS_RADIOBUTTON ))
                         return( DefWindowProc( hWnd, wMsg, wParam, lParam ));
            break;
        /* WM_USER + 1 -- Set or clear a check from radio button. In the
        ** case of VCR code, we invert--push in or pull out button. This
        ** message must be handled since underlying windows control code
        ** paints the state of the button on this message. */
        case BM_SETCHECK:
             if ( GetWindowLong( hWnd, GWL_STYLE ) & BSS_VCR )
                   if (( chk_btn_style( hWnd ) == BS_AUTORADIOBUTTON ) ||
                        (chk_btn_style( hWnd ) == BS_RADIOBUTTON ))
                        {
                            /* If auto style is set for the VCR button then
                            ** walk list of other VCR buttons. This is done for
                            ** buttons both before and after current button. */
                            if ( chk_btn_style( hWnd ) == BS_AUTORADIOBUTTON )
                            {
                                    button_walk( hWnd, GW_HWNDPREV );
                                    button_walk( hWnd, GW_HWNDNEXT );
                            }
                            /* Get previous set state from first byte of first
                            ** window word. On-off state is stored in LSB.
                            ** Toggle this bit but preserve rest of bits. */
                            tmp_chk = GetWindowWord( hWnd, 0 );
                            if ( wParam )  tmp_chk |= 0x0001;
                            else           tmp_chk &= ~0x0001;

                            SetWindowWord( hWnd, 0, tmp_chk );
                            /* Since we are generally running on a fast
                            ** system and the toggling of the visual state
                            ** involves the entire button, it is easier
                            ** to issue a re-paint for the entire button. */
                            InvalidateRect( hWnd, NULL, FALSE );

                            /* Block any further processing. This must be done
                            ** to prevent old radio button from being drawn. */
                            return( 0L );
                    }
                /* If we get here then this button style does not require
                ** special processing and default routines are called. This
                ** allows VCR button class to support other button styles. */
                break;
        /* WM_USER + 3 -- This message is sent to the control when it is first
        ** selected or de-selected. Default action is to highlight control
        ** button. Again this message must be handled since it does
        ** direct screen drawing. */
        case BM_SETSTATE:
            /* Check for special button processing */
            if ( GetWindowLong( hWnd, GWL_STYLE ) & BSS_VCR )
                    if (( chk_btn_style( hWnd ) == BS_AUTORADIOBUTTON )
                     || (chk_btn_style( hWnd ) == BS_RADIOBUTTON ))
                    {
                            /* Get current selection state without destroying
                            ** other state bits. */
                            tmp_chk = GetWindowWord( hWnd, 0 );

                            /* Put in or take out selected state bit based on
                            ** passed state. State bit is documented in SDK */
                            if ( wParam )  tmp_chk |= 0x0004;
                            else           tmp_chk &= ~0x0004;

                            SetWindowWord( hWnd, 0, tmp_chk );
                            three_D_border( hWnd, tmp_chk );

                            /* Add call to highlight button here,
                            ** to draw a black border around the control */
                                 /*-- add code here --*/
                            /* Block default processing */
                            return( 0L );
                    }
            break;
        /* Set border width for control. In visual tools, this as a property.
        ** Since underlying code must respond to a different size border, we
        ** keep it here as a an extra byte. This opens this capability up to
        ** a normal C program */

        case BM_SETBORDER:
                tmp_chk = GetWindowWord( hWnd, extra + BTN_BORDER );

                /* Put in size of border in low byte. High byte is reserved
                ** for some style flags. Make sure border width is at least
                ** 2 wide and less than 64. */
                tmp_chk &= 0xFF00;
                if ( wParam < 2 )
                        wParam = 2;
                tmp_chk |= wParam & 0x0003F;
                SetWindowWord( hWnd, extra + BTN_BORDER, tmp_chk );
                return( 0L );
        /* Since we are handling the painting of VCR button, we also have to
        ** handle situation where control is disabled. Windows will handle all
        ** other stuff related to control being disabled. */
        case WM_ENABLE:
            if ( GetWindowLong( hWnd, GWL_STYLE ) & BSS_VCR )
                    if (( chk_btn_style( hWnd ) == BS_AUTORADIOBUTTON )
                     || (chk_btn_style( hWnd ) == BS_RADIOBUTTON ))
                    {
                            /* Just let the paint routine
                            ** display the disabled state */
                            InvalidateRect( hWnd, NULL, FALSE );
                            return( 0L );
                    }
            break;
        /* On a control we also draw a focus rectangle when control has focus.
        ** This is typically done drawing a dashed rectangle around button
        ** text. In the case of the radio button, the set and kill focus
        ** message draws and removes this rectangle. */
        case WM_SETFOCUS:
        case WM_KILLFOCUS:
            if ( GetWindowLong( hWnd, GWL_STYLE ) & BSS_VCR )
                    if (( chk_btn_style( hWnd ) == BS_AUTORADIOBUTTON )
                      || (chk_btn_style( hWnd ) == BS_RADIOBUTTON ))
                    {
                            WORD tmp_chk;
                            /* Toggle the focus bit */

                            tmp_chk = GetWindowWord( hWnd, 0 );

                            if ( wMsg == WM_SETFOCUS )
                                    tmp_chk |= 0x0008;
                            else
                                    tmp_chk &= ~0x0008;

                            SetWindowWord( hWnd, 0, tmp_chk );

                            /* Draw the focus rectangle */
                            focus_border( hWnd, ( GetFocus() == hWnd ) );
                            /* Block drawing the normal radio button
                            ** focus rectangle. */
                            return( 0L );
                    }
            break;
    /* Handle any non-client Painting.  This is generally the
    ** border.  Here we use the default windows processing. */
        case WM_NCPAINT:
            /* Do radio button processing */
            if ( GetWindowLong( hWnd, GWL_STYLE ) & BSS_VCR )
                    if (( chk_btn_style( hWnd ) == BS_AUTORADIOBUTTON )
                     || (chk_btn_style( hWnd ) == BS_RADIOBUTTON ))
                    {
                            /* Nothing to do yet */
                            break;
                    }

            break;
    /* This is what makes the VCR button special.  All the
    ** display level is handled here. */
    case WM_PAINT:
        {
            char szText[ MAX_TEXT_SIZE + 1 ];
            TEXTMETRIC tm;
            int iCenter;
            int iTop;
            PAINTSTRUCT ps;
            HBRUSH hBrush;
            COLORREF tmp_clr;
                /* Check for VCR radio button special processing */

                if ( GetWindowLong( hWnd, GWL_STYLE ) & BSS_VCR )
                        if (( chk_btn_style( hWnd ) == BS_AUTORADIOBUTTON )
                     || (chk_btn_style( hWnd ) == BS_RADIOBUTTON ))
                    {

                            BeginPaint( hWnd, &ps );

                            /* Find out window size to draw the underline */
                            GetClientRect( hWnd, &rect );

                            /* Draw button text in button centered, then
                            ** a rectangle border */

                            SetTextAlign( ps.hdc, TA_CENTER );

                            /* Get any control assigned font to use from
                            ** underlying font table in control and select it
                            ** for drawing. Could get a font handle from extra
                            ** words; would be quicker. Sending a message is
                            ** more portable to future versions of Windows. */

                            if ( hFont = (HFONT) LOWORD( SendMessage( hWnd,
                             WM_GETFONT, 0, 0L )) )
                                    hFont = SelectObject( ps.hdc, hFont );
                       /* Here we take the Microsoft easy way out.  Generally
                        ** we would send a WM_CTLCOLOR message here to allow
                        ** parent to set color of button. Instead we follow
                        ** Microsoft's lead and use system defined button
                        ** colors. These can be changed for all buttons in the
                        ** WIN.INI file. This allows VCR button to match all
                        ** other push buttons in system. A nice enhancement
                        ** would be to merge brush color with Windows System
                        ** defined colors to create special-effect buttons.
                        ** Another might be to paint button with a bit map.
                        ** To make the pushed in or selected state of
                        ** the button standout we use the BTNSHADOW
                        ** as the background instead of the BTNFACE. */
                        if ( GetWindowWord( hWnd, 0 ) & 0x0001 )
                        {
                               tmp_clr = GetSysColor( COLOR_BTNSHADOW );
                               SetBkColor( ps.hdc, tmp_clr  );
                               SetTextColor( ps.hdc,
                   GetSysColor( COLOR_BTNHIGHLIGHT ) );
                        }
                        else
                        {
                                tmp_clr = GetSysColor( COLOR_BTNFACE );
                                SetBkColor( ps.hdc, tmp_clr );
                                SetTextColor( ps.hdc,
                    GetSysColor( COLOR_BTNTEXT ) );
                        }
                       /* Fill the button with the current solid color. */
                        hBrush = CreateSolidBrush( tmp_clr );
                        FillRect( ps.hdc, &rect, hBrush );
                        DeleteObject( hBrush );
                       /* Now handle the text for the button. Here we impose
                        ** a limit on text that can be put in a push button.
                        ** This can be increased by modfing the local header
                        ** for the control. Text is centered by default. Could
                        ** be modified to allow left and right text. */
                        GetWindowText( hWnd, szText, MAX_TEXT_SIZE );
                        GetTextMetrics( ps.hdc, &tm );
                        iCenter = ( rect.right - rect.left ) / 2;
                        iTop = (rect.bottom - tm.tmHeight) / 2;

                        /* Check to see if we need to gray the string */
                        if ( IsWindowEnabled( hWnd ) == FALSE )
                        {
                            if ( tmp_clr == GetSysColor( COLOR_BTNSHADOW ) )
                                    tmp_clr = GetSysColor( COLOR_GRAYTEXT );
                            else
                                    tmp_clr = GetSysColor( COLOR_BTNSHADOW );
                            SetTextColor( ps.hdc, tmp_clr );
                         }
                        TextOut( ps.hdc, iCenter, iTop, szText,
                         lstrlen( szText ) );
                       /* If we are using a special font then deselect it */
                        if ( hFont )
                             hFont = SelectObject( ps.hdc, hFont );
                       /* Redraw the focus rectangle if we have focus. */
                        three_D_border( hWnd, GetWindowWord( hWnd, 0 ));

                        if ( GetFocus() == hWnd )
                                    focus_border( hWnd, TRUE );
                EndPaint( hWnd, &ps );    /* clean up before exit */
                        return( 0L );   /* Block all other painting*/
                    }
            break;
            }
       default: break;
    }
    /* Forward all unmodified messages to the old button wndproc */
    return(  CallWindowProc( OldButtonProc, hWnd, wMsg, wParam, lParam ));
}
/* -----------------------------------------------------------------
 * WEP -- Standard function to cleanup tasks when the DLL is unloaded. WEP()
 * is called automatically by Windows when DLL is unloaded (no remaining tasks
 * still have DLL loaded). Microsoft strongly recommends DLLs have a WEP(),
 * even if it does nothing but returns success (1), as in this example.
 * ----------------------------------------------------------------*/
int FAR PASCAL WEP ( int bSystemExit )
{
    bSystemExit; // to quiet the compiler about unused parameter warning
    return(1);
}
/* -----------------------------------------------------------------
 * LibMain -- the main entry point for the Image Window Library
 * Returns: FALSE if unable to initialize
 * ----------------------------------------------------------------*/
int FAR PASCAL LibMain( HANDLE hModule, WORD wDataSeg,
 WORD cbHeapSize, LPSTR lpszCmdLine )
{
    wDataSeg;
    cbHeapSize;
    lpszCmdLine;
    hInst = hModule; // Save  module handle to use as instance later
    if ( ! VCRButtonInit( hInst ) ) //Register the enhanced button class
            return( FALSE );
    return( TRUE );
}
End Listing



Copyright © 1993, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.