编辑控件中的搜索图标与输入区域重叠

问题描述 投票:0回答:2

我正在尝试在MFC中制作一个搜索编辑控件,该控件始终在控件窗口中显示一个图标(无论控件的状态和文本如何)。很多年前我写过类似的东西并且运行得很好,但是代码不再适用于 Windows 7 和更新版本(甚至可能是 Vista,但没有尝试过)。发生的情况是控件中显示的图像与输入区域重叠(见下图)。

代码背后的想法:

  • 有一个派生自
    CEdit
    的类(处理 OnPaint 中的绘画)
  • 右侧显示图标,编辑区域根据图标大小缩小
  • 单行和多行编辑的大小调整方式不同。对于单行,我调用 SetMargins,对于多行编辑,我调用 SetRect
  • 此编辑调整大小适用于
    PreSubclassWindow()
    OnSize()
    OnSetFont()

这就是编辑输入尺寸的应用方式:

void CSymbolEdit::RecalcLayout()
{
    int width = GetSystemMetrics( SM_CXSMICON );

    if(m_hSymbolIcon)
    {
      if (GetStyle() & ES_MULTILINE)
      {
         CRect editRect;
         GetRect(&editRect);

         editRect.right -= (width + 6);

         SetRect(&editRect);
      }
      else
      {
         DWORD dwMargins = GetMargins();
         SetMargins(LOWORD(dwMargins), width + 6);
      }
    }
}

下图显示了单行编辑的问题(为了更好地查看图像已放大)。黄色背景仅用于突出显示目的,在实际代码中我使用

COLOR_WINDOW
系统颜色。您可以看到,当单行编辑有文本并有输入时,左侧图像会被覆盖。在多行编辑中不会发生这种情况,其中
SetRect
正确设置了格式矩形。

enter image description here

我尝试使用 ExcludeClipRect 删除显示图像的编辑区域。

CRect rc;
GetClientRect(rc);

CPaintDC dc(this);
ExcludeClipRect(dc.m_hDC, rc.right - width - 6, rc.top, rc.right, rc.bottom);

DWORD dwMargins = GetMargins();
SetMargins(LOWORD(dwMargins), width + 6);

这似乎对结果没有任何影响。

供参考,这是多年前写的绘画方法,曾经在 Windows XP 上运行良好,但现在不再正确了。

void CSymbolEdit::OnPaint()
{
    CPaintDC dc(this);

    CRect rect;
    GetClientRect( &rect );

    // Clearing the background
    dc.FillSolidRect( rect, GetSysColor(COLOR_WINDOW) );

    DWORD dwMargins = GetMargins();

    if( m_hSymbolIcon )
    {
        // Drawing the icon
        int width = GetSystemMetrics( SM_CXSMICON );
        int height = GetSystemMetrics( SM_CYSMICON );

        ::DrawIconEx( 
            dc.m_hDC, 
            rect.right - width - 1, 
            1,
            m_hSymbolIcon, 
            width, 
            height, 
            0, 
            NULL, 
            DI_NORMAL);

        rect.left += LOWORD(dwMargins) + 1;
        rect.right -= (width + 7);
    }
    else
    {
        rect.left += (LOWORD(dwMargins) + 1);
        rect.right -= (HIWORD(dwMargins) + 1);
    }

    CString text;
    GetWindowText(text);
    CFont* oldFont = NULL;

   rect.top += 1;

    if(text.GetLength() == 0)
    {       
        if(this != GetFocus() && m_strPromptText.GetLength() > 0)
        {
            oldFont = dc.SelectObject(&m_fontPrompt);
            COLORREF color = dc.GetTextColor();
            dc.SetTextColor(m_colorPromptText);
            dc.DrawText(m_strPromptText, rect, DT_LEFT|DT_SINGLELINE|DT_EDITCONTROL);
            dc.SetTextColor(color);
            dc.SelectObject(oldFont);
        }
    }
    else
    {
      if(GetStyle() & ES_MULTILINE)
         CEdit::OnPaint();
      else
      {
         oldFont = dc.SelectObject(GetFont());
         dc.DrawText(text, rect, DT_SINGLELINE | DT_INTERNAL | DT_EDITCONTROL);
         dc.SelectObject(oldFont);
      }
    }
}

我查看了类似编辑控件的其他实现,它们现在都有相同的错误。

显然,问题是如何从控件的输入区域中排除图像区域?

c++ visual-c++ mfc paint editcontrol
2个回答
0
投票

认为发生的事情是

CPaintDC
调用
BeginPaint()
,后者将
WM_ERASEBKGND
发送到编辑框。我无法忽略它,所以我猜它可能被发送到内部
STATIC
窗口?不确定。

ExcludeClipRect()
处理程序中调用
OnPaint()
不会执行任何操作,因为
EDIT
会将剪辑区域重置为
BeginPaint()
或其自己的
WM_PAINT
处理程序中的整个客户区域。

但是,

EDIT
在绘制自身之前向其父级发送
WM_CTRCOLOREDIT
,但似乎是在设置剪切区域之后。所以你可以在那里打电话
ExcludeClipRect()
。听起来像是一个实现细节可能会随着公共控件的未来版本而改变。事实上,它似乎已经这样做了。

我在 Windows 7 上做了一个没有 MFC 的快速测试,这是我的窗口过程:

LRESULT CALLBACK wnd_proc(HWND h, UINT m, WPARAM wp, LPARAM lp)
{
    switch (m)
    {
        case WM_CTLCOLOREDIT:
        {
            const auto dc = (HDC)wp;
            const auto hwnd = (HWND)lp;

            RECT r;
            GetClientRect(hwnd, &r);

            // excluding the margin, but not the border; this assumes
            // a one pixel wide border
            r.left = r.right - some_margin;
            --r.right;
            ++r.top;
            --r.bottom;

            ExcludeClipRect(dc, r.left, r.top, r.right, r.bottom);

            return (LRESULT)GetStockObject(DC_BRUSH);
        }
    }

    return ::DefWindowProc(h, m, wp, lp);
}

然后我对

EDIT
窗口进行子类化,在
WM_PAINT
中绘制我自己的图标,然后转发消息,这样我就不必自己绘制其他所有内容。

LRESULT CALLBACK edit_wnd_proc(
    HWND h, UINT m, WPARAM wp, LPARAM lp,
    UINT_PTR  id, DWORD_PTR data)
{
    switch (m)
    {
        case WM_PAINT:
        {
            const auto dc = GetDC(h);

            // draw an icon

            ReleaseDC(h, dc);
            break;
        }
    }

    return DefSubclassProc(h, m, wp, lp);
}

请注意,我无法在

BeginPaint()
中调用
EndPaint()
CPaintDC
(相当于构造
WM_PAINT
),因为不会绘制边框。我猜这与调用
BeginPaint()
两次(一次手动,一次通过
EDIT
)和处理
WM_ERASEBKGND
有关。 YMMV,尤其是 MFC。

最后,我在创建

EDIT
后立即设置边距:

SendMessage(
    e, EM_SETMARGINS,
    EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELPARAM(0, margin));

如果系统字体发生变化,您可能还需要再次更新边距。


0
投票

看看本教程。它清楚地说明了如何将按钮插入编辑控件。虽然这是一个 Win32 示例,但可以将其临时修改为 MFC,因为 MFC 的基本结构是使用 win32 api。

它使用

WM_NCCALCSIZE
来限制文本控制。

© www.soinside.com 2019 - 2024. All rights reserved.