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

时间:2013-12-28 13:43:02

标签: 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;

到目前为止一切正常,但为了获得适当的键盘支持,我必须添加焦点矩形。 所以现在我也在处理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;

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

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

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

1 个答案:

答案 0 :(得分:5)

<强>更新 在过去的15年里,我一直生活在一个时间胶囊中,并且最初发布的答案并没有解决如何解决围绕Visual Styles的问题(见下文)。

启用视觉样式后,WM_DRAWITEM message的行为发生了变化: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 );
        }
    }
}
// ...
}

不需要在焦点丢失时重绘整个控件。 DrawFocusRect以异或模式呈现,可以通过第二次应用相同的操作来删除。

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

  1. 如果itemAction包含ODA_FOCUS呈现焦点矩形,则无论其他状态如何。这会切换可见性。
  2. 否则,如果itemState包含ODS_FOCUS,则仅渲染焦点矩形。这是必要的,以便正确地考虑初始状态。
  3. 以下代码演示了此策略。

    resource.h中:

    #define IDD_MAINDLG 101
    

    DlgBasedWin32.rc(仅使用“确定”和“取消”按钮声明一个简单的对话框):

    #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(创建主对话框和消息循环):

    #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;
    }
    

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