Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
576 views
in Technique[技术] by (71.8m points)

winapi - Owner-drawn button, WM_CTLCOLORBTN and WM_DRAWITEM (clearing an HDC)

I'm trying to implement a simple owner-drawn button, which will just contain an image from a brush.

Here's my code (WTL, but it's quite straightforward):

case WM_CTLCOLORBTN:
    dc.SetBkMode(TRANSPARENT);
    POINT pt = { 0 };
    button.MapWindowPoints(m_hWnd, &pt, 1);
    dc.SetBrushOrg(-pt.x, -pt.y, NULL);
    return m_brushHeader;

Everything works fine so far, but for proper keyboard support, I have to add focus rectangle. So now I'm also handling the WM_DRAWITEM message:

case WM_DRAWITEM:
    if(lpDrawItemStruct->itemAction & (ODA_DRAWENTIRE | ODA_FOCUS))
    {
        if((lpDrawItemStruct->itemState & ODS_FOCUS) && 
            !(lpDrawItemStruct->itemState & ODS_NOFOCUSRECT))
        {
            dc.DrawFocusRect(&lpDrawItemStruct->rcItem);
        }
        else
        {
            // Need to remove the rectangle here!
        }
        break;
    }
    break;

The rectangle is properly added, but when the focus is moved to a different button, and I receive the ODA_DRAWENTIRE request, I have to clear it.

How do I clear the content of the HDC? I found only methods of filling it with color, etc. I need to make it empty/transparent, like it was before using DrawFocusRect.

P.S. The application uses visual styles, i.e. ComCtl32.dll Version 6.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Update: I've been living in a time capsule for the past 15 years and initially posted an answer that doesn't address how to solve the issues revolving around Visual Styles (see below).

With Visual Styles enabled there is a change in behavior for the WM_DRAWITEM message: The DRAWITEMSTRUCTs itemAction field no longer has the ODA_FOCUS bit set on focus loss. The result is that the solution to remove the focus rectangle towards the bottom of this answer can no longer be applied.

To remove the focus rectangle with visual styles enabled requires rendering the control again. The following code snippet for the message handler shows how to do this:

switch ( message ) {
// ...
case WM_DRAWITEM: {
    const DRAWITEMSTRUCT& dis = *(DRAWITEMSTRUCT*)lParam;
    if ( dis.itemAction & ODA_DRAWENTIRE ) {
        // Render the control
        // ...

        // If the control has the input focus...
        if ( dis.itemState & ODS_FOCUS ) {
            // Render the focus rectangle
            DrawFocusRect( dis.hDC, &dis.rcItem );
        }
    }
}
// ...
}

Redrawing the entire control upon focus loss is not required. DrawFocusRect is rendered in XOR mode and can be removed by applying the same operation a second time.

The logic to render the focus rectangle consists of two parts:

  1. If itemAction contains ODA_FOCUS render the focus rectangle irrespective of any other state. This toggles the visibility.
  2. Otherwise, only render the focus rectangle, if itemState contains ODS_FOCUS. This is necessary so that the initial state is properly accounted for.

The following code demonstrates this strategy.

resource.h:

#define IDD_MAINDLG 101

DlgBasedWin32.rc (Declaring a simple dialog with just an OK and Cancel button):

#include "resource.h"
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_MAINDLG DIALOGEX 0, 0, 309, 176
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    CONTROL         "OK",IDOK,"Button",BS_OWNERDRAW | WS_TABSTOP,198,155,50,14
    CONTROL         "Cancel",IDCANCEL,"Button",BS_OWNERDRAW | WS_TABSTOP,252,155,50,14
END

DlgBasedWin32.cpp (Creating the main dialog and message loop):

#include <windows.h>
#include "resource.h"

// Forward declarations of functions included in this code module:
INT_PTR CALLBACK DlgProc( HWND, UINT, WPARAM, LPARAM );

int APIENTRY _tWinMain( HINSTANCE hInstance,
                        HINSTANCE /*hPrevInstance*/,
                        LPTSTR    /*lpCmdLine*/,
                        int       /*nCmdShow*/)
{
    HWND hDlg = CreateDialogW( hInstance, MAKEINTRESOURCEW( IDD_MAINDLG ),
                               NULL, DlgProc );
    ShowWindow( hDlg, SW_SHOW );
    UpdateWindow( hDlg );

    MSG msg = { 0 };
    // Main message loop:
    while ( GetMessageW( &msg, NULL, 0, 0 ) )
    {
        if ( !IsDialogMessageW( hDlg, &msg ) ) {
            TranslateMessage( &msg );
            DispatchMessageW( &msg );
        }
    }

    return (int) msg.wParam;
}

DlgBasedWin32.cpp (Dialog message handler):

// Message handler for IDD_MAINDLG
INT_PTR CALLBACK DlgProc( HWND hDlg,
                          UINT message,
                          WPARAM wParam,
                          LPARAM lParam )
{
    switch ( message )
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if ( LOWORD( wParam ) == IDOK || LOWORD( wParam ) == IDCANCEL ) {
            DestroyWindow( hDlg );
            return (INT_PTR)TRUE;
        }
        break;

    case WM_DESTROY:
        PostQuitMessage( 0 );
        return (INT_PTR)TRUE;

    case WM_DRAWITEM: {
        WORD wID = (WORD)wParam;
        const DRAWITEMSTRUCT& dis = *(DRAWITEMSTRUCT*)lParam;
        // Focus change?
        if ( dis.itemAction & ODA_FOCUS ) {
            // Toggle focus rectangle
            DrawFocusRect( dis.hDC, &dis.rcItem );
        }
        else if ( dis.itemAction & ODA_DRAWENTIRE ) {
            // Not a focus change -> render rectangle if requested
            if ( dis.itemState & ODS_FOCUS ) {
                DrawFocusRect( dis.hDC, &dis.rcItem );
            }
        }
        return (INT_PTR)TRUE;
    }

    }

    return (INT_PTR)FALSE;
}

The code above displays a simple dialog with just an OK and Cancel button. The buttons have the BS_OWNERDRAW style set, and the WM_DRAWITEM handler merely renders the focus rectangle; the buttons remain otherwise invisible. Full keyboard and mouse support is implemented through IsDialogMessage and the default message handler, respectively.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...