【问题标题】:MFC VC++ Custom Checkbox ImageMFC VC++ 自定义复选框图像
【发布时间】:2011-07-16 16:52:23
【问题描述】:

我如何获得一个三态复选框来为不确定状态使用不同的位图?

我想将我的三态复选框使用的图像更改为使用不同的图像;这些控件是 Win98 风格的,并且此类复选框的不确定状态很难与禁用的复选框区分开来(这可能是他们为 WinXP 风格的控件更改此设置的原因,但由于我的项目中的其他细节,我不能使用这些) .

我使用的是 Visual C++ 2010,并且我在 VS 的资源编辑器中定义了一个 8x8 位图。位图的 ID 是IDB_INDET_CHECK

我不完全确定这样的标准“技术”是什么;我才真正开始着手操作 Windows 控件和 MFC。

我的第一次尝试是创建一个派生自CButton 的类CTriButton,覆盖DrawItem 函数,并尝试自己绘制它。然后我使用SubclassDlgItem 将我窗口中的一个复选框变成了这个类(我想?)。这……有点作品?该复选框不再出现,如果我单击它应该在的位置,则会出现一个空的复选框框架,但没有其他任何反应(并且我的代码中的调试消息没有被发送)。

这里是相关代码,虽然我不确定任何是否正确。首先,来自我窗口的OnInitDialog 的代码。

BOOL CAffixFilterDlg::OnInitDialog() // CAffixFilterDlg is my CDialog-derived window
{
    CDialog::OnInitDialog(); // call basic version

    // subclass a CButton-derived control with CTriButton
    if ( CBipedHead.SubclassDlgItem(IDC_HEAD, this) ) // CBipedHead is a CTriButton member of CAffixFilterDlg, IDC_HEAD is a checkbox
        SetWindowLong(CBipedHead.m_hWnd, GWL_STYLE, CBipedHead.GetStyle() | BS_OWNERDRAW); // set the ownerdraw style
    else // subclassing didn't work
        _ERROR("Subclassing failed."); // I do not see this error message, so SubclassDlgItem worked?

    // initialization continues, but is not relevant...
    UpdateWindow();
    Invalidate();

    return TRUE;
}

接下来,我的自定义按钮 DrawItem 的代码。

void CTriButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    _DMESSAGE("Drawing TriButton"); // never see this message

    CDC dc;
    dc.Attach(lpDrawItemStruct->hDC);     //Get device context object
    int nWidth = GetSystemMetrics(SM_CXMENUCHECK);
    int nMargin = ( nWidth - 8 ) / 2;

    CRect textRt = lpDrawItemStruct->rcItem;
    textRt.right = textRt.right - nWidth - nMargin;

    CString text;
    GetWindowText(text);

    UINT textDrawState = DST_TEXT;
    if ( lpDrawItemStruct->itemState & ODS_DISABLED )
        textDrawState |= DSS_DISABLED;

    dc.DrawState(CPoint(textRt.left, textRt.top), textRt.Size(), text, textDrawState, TRUE, 0, (CBrush*)NULL);

    CRect rt = lpDrawItemStruct->rcItem;    // initial rect is for entire button
    rt.left = rt.right - nWidth;            // set left margin
    LONG center = ( rt.bottom + rt.top ) / 2;
    rt.top = center - nWidth/2;
    rt.bottom = center + nWidth/2;

    UINT checkDrawState = DFCS_BUTTONCHECK;
    if ( lpDrawItemStruct->itemState & ODS_DISABLED )
        checkDrawState |= DFCS_INACTIVE;

    if ( lpDrawItemStruct->itemState & ODS_CHECKED )
        checkDrawState |= DFCS_CHECKED;

    else if ( GetCheck() == BST_INDETERMINATE ) {
        _VMESSAGE("Indeterminate; custom draw.");

        CBitmap indet_check = CBitmap();
        indet_check.LoadBitmap(IDB_INDET_CHECK);

        CPoint pt = CPoint(rt.left + nMargin, rt.top + nMargin);
        CSize sz = CSize(8, 8);

        dc.DrawState(pt, sz, &indet_check, DST_BITMAP|DSS_NORMAL);
    }

    dc.DrawFrameControl(rt, DFC_BUTTON, checkDrawState);
}

【问题讨论】:

  • 你在哪里启用了所有者绘制窗口样式?
  • 你有什么问题?您在哪里将复选框设置为三样式或将按钮的状态设置为中间?
  • @paludarium:谢谢,这对一些人有帮助;我不知道这样做。我已经更新了我的代码,现在设置了 ownerdraw 样式;现在东西似乎根本没有被绘制,直到我点击它应该在的位置,然后我只得到一个空的复选框框。我的绘图代码似乎没有被调用(从未看到调试消息)。我的问题已经更新了。感谢您提到设置样式!
  • @Ajay:复选框是在 Visual Studio 的资源编辑器中设置的,包括它的三样式和默认状态。无论如何,感谢您的评论;我已经尝试重写我的问题以更清楚!

标签: c++ visual-studio-2010 checkbox mfc ownerdrawn


【解决方案1】:

在 OnInitDialog() 中,您需要在更改窗口样式后调用 InvalidateRect() ,否则它不知道需要重绘。在更改窗口样式后调用 UpdateWindow() 也是一个好主意。某些信息通常由公共控件缓存,并且在调用 UpdateWindow() 之前不会确认更改。

在 DrawItem() 中,您负责呈现控件的所有状态。你不应该调用 CButton::DrawItem() 因为它什么都不做。尝试以下方法:

void CTriButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    CBitmap indet_check

    _DMESSAGE("Drawing TriButton"); // I never see this message
    int checkState = GetCheck();

    if ( checkState == BST_CHECKED )
    {
        indet_check.LoadBitmap(IDB_INDET_CHECK);
    }
    else if ( checkState == BST_UNCHECKED )
    {
        indet_check.LoadBitmap(IDB_INDET_UNCHECKED);
    }
    else if ( checkState == BST_INDETERMINATE )
    {
        indet_check.LoadBitmap(IDB_INDET_INDETERMINATE);
    }

    //    ... rest of your drawing code here ...
    //    don't forget to draw focus and push states too ;)

}

附录:

我不敢相信我第一次错过了这个,但你给SubclassDlgItem 的电话可能没有达到预期的效果。此调用导致用于按钮的消息首先由控件父窗口处理。因为CWnd(CDialog 的超类)中DrawItem 的默认实现什么都不做,所以消息永远不会传递给控件。

用下面的 sn-p 替换它,一切都应该没问题:

HWND hWndButton;
GetDlgItem(IDC_HEAD, &hWndButton);
CBipedHead.SubclassWindow(hWndButton);

这里有两个旁注:

  1. 对类和类成员使用相同的命名约定通常不是一个好主意。读起来很混乱。
  2. 我猜你总是在发布模式下编译和运行。如果你是 - 不要。这可以防止断言被抛出并让您知道有问题。

【讨论】:

  • CButton::DrawItem 什么都不做?这……当然很奇怪。有没有办法为复选标记加载默认位图(一些默认 ID 或我可以与 LoadBitmap 一起使用的东西?另外,DrawItem 函数还需要处理哪些其他事情,即我的代码是否得到了所有对我来说,这里的一个主要问题是我看不到DrawItem 的实际定义,无法知道我应该模仿什么...任何帮助将不胜感激。感谢有关InvalidateRect 的注释和UpdateWindow; 有道理。
  • @DragonWraith MFC 中的常用控件表示均未在其 DrawItem() 方法中实现渲染。它们通常包含一行,这是一条 ASSERT() 语句。 DRAWITEMSTRUC 结构包含一个名为 itemState 的成员,它包含有关如何绘制控件的各种标志。对于所有控件,您至少应处理 ODS_DEFAULTODS_DISABLEDODS_FOCUSODS_SELECTED 状态。 AFAIK 通用控件通常不依赖资源位图的元素。
  • 谢谢!这很有帮助。现在来看看如何做这些事情......我假设因为我想要一个非默认的“外观”到不确定状态,我需要一个位图?
  • @DragonWraith 不客气。您可以使用位图或使用基于矢量的 GDI 调用来绘制它。要记住的一件事是,您应该尽一切努力保持对话框和窗口中显示的所有控件的外观和感觉的一致性。这可能需要一些额外的时间,但它可以为您的用户提供更流畅的演示。
  • 哦;我完全同意;这就是我这样做的全部原因(Win98 风格的复选框有一个非常不令人满意的不确定状态,所以我基本上是在重新创建 WinXP 的外观)。应用程序中的所有三态框都将使用相同的样式。由于大多数用户使用 WinXP 或更高版本,我怀疑这将比替代方案熟悉。
【解决方案2】:

不是 答案,而是一个 答案:我发现这个自定义CCheckBox 或多或少地实现了我想要的。默认情况下,它不允许 3 个状态,但我通过自己的一些调整来解决这个问题。我不能 100% 确定它开箱即用(我遇到了一些问题,似乎不是由于我的编辑,但我不能确定),但是这是我使用的解决方案。不过,我不会将其称为答案,以防有人窥探我的代码出了什么问题并想启发我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-27
    • 2011-06-15
    • 2013-04-11
    • 1970-01-01
    • 2019-07-04
    • 1970-01-01
    • 1970-01-01
    • 2012-05-03
    相关资源
    最近更新 更多