【问题标题】:Owner-drawn button, WM_CTLCOLORBTN and WM_DRAWITEM (clearing an HDC)所有者绘制的按钮,WM_CTLCOLORBTN 和 WM_DRAWITEM(清除 HDC)
【发布时间】:2013-12-28 13:43:02
【问题描述】:

我正在尝试实现一个简单的自绘按钮,它只包含来自画笔的图像。

这是我的代码(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之前一样。

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

【问题讨论】:

  • @HansPassant 我试过了,但问题是我对按钮有一个静态文本控件。如果我在WM_DRAWITEM 内而不是WM_CTLCOLORBTN 内绘制背景,则按钮与静态控件重叠。
  • 您忘记了 WS_CLIPCHILDREN 吗?您的代码已损坏,itemAction 是 位掩码,而不是值。
  • 查看DrawFocusRect 的文档:“因为DrawFocusRect 是一个异或函数,所以用同一个矩形再次调用它会从屏幕上删除矩形。”
  • @HansPassant WS_CLIPCHILDREN 已设置。无论 z 顺序如何,静态控件都是不可见的。感谢您的修复,编辑了问题和代码,但没有任何区别。有没有什么办法可以让HDC恢复到原来的状态?
  • 您通过重绘其他所有内容来移除焦点矩形。所以通过画笔重绘位图。

标签: winapi


【解决方案1】:

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

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

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

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 以 XOR 模式呈现,可以通过再次应用相同的操作来移除。

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

  1. 如果itemAction 包含ODA_FOCUS,则无论其他状态如何,都会渲染焦点矩形。这会切换可见性。
  2. 否则,如果itemState 包含ODS_FOCUS,则仅渲染焦点矩形。这是必要的,以便正确考虑初始状态。

以下代码演示了这种策略。

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(对话消息处理程序):

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

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

【讨论】:

  • 非常感谢您的努力。我已经在一个新项目上尝试了你的代码,它确实有效,直到我添加了一个视觉样式清单。出于某种原因,会根据通用控件版本发送不同的消息。所以我想这里唯一的解决方案是在WM_DRAWITEM 上手动绘制所有内容,正如雷蒙德建议的那样。
  • @Paul 我必须看看视觉样式如何/为什么会改变行为,以及是否有一种不那么乏味的方法来解决这个问题。然而,由于雷蒙德从不错,他的评论让我有点担心。也许视觉风格确实增加了这一切的复杂性。您也应该在问题中明确说明视觉样式的使用。
  • “您也应该在问题中明确说明视觉样式的使用。” - 我认为现在这是规则而不是例外。另外,我认为它不会改变发送的消息。无论如何,我更新了问题。
  • @Paul 任何有自尊的开发者都应该在今天实现视觉风格。为了谋生,多年来我一直在维护一个在 Windows XP Embedded 上运行的系统,并且关闭了视觉样式,在我的小宇宙中不存在视觉样式。我很抱歉。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-13
  • 1970-01-01
  • 2020-05-28
  • 2011-08-10
  • 1970-01-01
相关资源
最近更新 更多