所有者绘制的按钮,WM_CTLCOLORBTN和WM_DRAWITEM(清除HDC)

Pau*_*aul 2 winapi

我正在尝试实现一个简单的所有者绘制按钮,它只包含画笔中的图像.

这是我的代码(WTL,但它非常简单):

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;
Run Code Online (Sandbox Code Playgroud)

到目前为止一切正常,但为了正确的键盘支持,我必须添加焦点矩形.所以现在我也在处理这个WM_DRAWITEM消息:

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;
Run Code Online (Sandbox Code Playgroud)

矩形已正确添加,但当焦点移动到另一个按钮,并且我收到ODA_DRAWENTIRE请求时,我必须清除它.

如何清除HDC的内容?我发现只有用颜色等填充它的方法.我需要将它变成空的/透明的,就像使用之前一样DrawFocusRect.

PS该应用程序使用视觉样式,即ComCtl32.dll版本6.

IIn*_*ble 5

更新: 过去15年来,我一直生活在一个时间囊中,最初发布的答案并没有解决如何解决围绕视觉样式的问题(见下文).

启用视觉样式后,WM_DRAWITEM消息的行为会发生变化:DRAWITEMSTRUCTs itemAction字段不再ODA_FOCUS设置焦点丢失位.结果是无法再应用将焦点矩形移到此答案底部的解决方案.

要在启用视觉样式的情况下删除焦点矩形,需要再次渲染控件.消息处理程序的以下代码段显示了如何执行此操作:

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 );
        }
    }
}
// ...
}
Run Code Online (Sandbox Code Playgroud)

不需要在焦点丢失时重绘整个控件.DrawFocusRect在XOR模式下渲染,可以通过第二次应用相同的操作来删除.

渲染焦点矩形的逻辑由两部分组成:

  1. 如果itemAction包含ODA_FOCUS渲染焦点矩形而不管其他任何状态.这会切换可见性.
  2. 否则,仅渲染焦点矩形(如果itemState包含)ODS_FOCUS.这是必要的,以便适当地考虑初始状态.

以下代码演示了此策略.

RESOURCE.H:

#define IDD_MAINDLG 101
Run Code Online (Sandbox Code Playgroud)

DlgBasedWin32.rc(只用一个OK和Cancel按钮声明一个简单的对话框):

#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
Run Code Online (Sandbox Code Playgroud)

DlgBasedWin32.cpp(创建主对话框和消息循环):

#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;
}
Run Code Online (Sandbox Code Playgroud)

DlgBasedWin32.cpp(Dialog消息处理程序):

// 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;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码显示了一个简单的对话框,只有一个OK和Cancel按钮.按钮具有BS_OWNERDRAW样式集,WM_DRAWITEM处理程序仅呈现焦点矩形; 按钮保持不可见.全键盘和鼠标支持IsDialogMessage分别通过和默认消息处理程序实现.