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

Search icon in edit control overlapped by input area

提问人:Marius Bancila 提问时间:8/11/2016 最后编辑:Marius Bancila 更新时间:11/22/2019 访问量:1144

问:

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

代码背后的想法:

  • 有一个派生自的类(在 OnPaint 中处理绘画)CEdit
  • 图标显示在右侧,编辑区域根据图标的大小缩小
  • 对于单行和多行编辑,调整大小的方式不同。对于单行,我调用 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_WINDOWSetRect

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 绘制 编辑控件

评论

0赞 Barmak Shemirani 8/12/2016
您的替代是与“编辑”控件的绘制例程作斗争。它用于手动绘制控件,有时调用 ,而 又调用 ,然后进行默认处理,重新绘制工作区。当“编辑”控件进入和失去焦点时,或者当收到任何绘制消息时,这将失败。OnPaintCPaintDCCEdit::OnPaintCPaintDC
0赞 Marius Bancila 8/12/2016
CEdit::OnPaint()只要求进行多行编辑,这不是我关心的问题。我只使用单行编辑控件。我提到了多行编辑,因为在这种情况下,设置边界可以正常工作。
0赞 isanae 8/30/2016
如果您覆盖并从那里调用怎么办?通过快速测试似乎可以正常工作,但没有 MFC。您可能需要再次更改剪辑以显示您自己的内容。CWnd::OnCtlColor()ExcludeClipRect()OnPaint()
0赞 isanae 9/15/2016
我的回答有帮助吗?你有一个带有赏金的“引起注意”的通知,但从那以后你什么也没说。
0赞 Marius Bancila 9/24/2016
我很抱歉忽略了这一点,最近这完全超出了我的关注范围。我会检查你发布的内容,如果它有效,我会给你赏金点(必须创建一个新的,希望有效)。

答:

0赞 isanae 8/30/2016 #1

我认为正在发生的事情是调用,它会将 发送到编辑框。我无法忽略它,所以我想它正在被发送到一个内部窗口?不确定。CPaintDCBeginPaint()WM_ERASEBKGNDSTATIC

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

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

我在 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);
}

然后,我将窗口子类化以绘制自己的图标,然后转发消息,这样我就不必自己绘制其他所有内容。EDITWM_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);
}

请注意,我无法调用 and(相当于构造 ),因为边界不会被绘制。我猜这与调用两次(一次手动,一次)和处理有关。YMMV,尤其是 MFC。BeginPaint()EndPaint()CPaintDCWM_PAINTBeginPaint()EDITWM_ERASEBKGND

最后,我在创建后立即设置边距:EDIT

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

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

0赞 Karthik Krish 11/22/2019 #2

看看这个教程...从 www.catch22.net。它清楚地说明了如何将按钮插入编辑控件。虽然这是一个 Win32 示例,但可以即兴使用 MFC,因为 MFC 的基本结构是使用 win32 api。

http://www.catch22.net/tuts/win32/2001-05-20-insert-buttons-into-an-edit-control/#

它使用 WM_NCCALCSIZE 来限制文本控件。