1

My goal is to replace a background for the common-control's edit control. My current code does this:

HBITMAP hBmp = ::LoadBitmap(hInstance, MAKEINTRESOURCE(BKGND_ID));
HBRUSH hBkgndBrush = ::CreatePatternBrush(hBmp);
::DeleteObject(hBmp);


HBRUSH CDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

    // TODO:  Change any attributes of the DC here

    if(pWnd->GetDlgCtrlID() == MY_CTRL_ID && hBkgndBrush)
    {
        hbr = hBkgndBrush;

        //Do I need to select it?
        //pDC->SelectObject(hbr);   //This line?

        pDC->SetBkMode(TRANSPARENT);
    }

    // TODO:  Return a different brush if the default is not desired
    return hbr;
}

The question is, do I need to select hbr before returning it? (See commented out line above.) I seem to see it done both ways in different examples online.

EDIT: Also forgot to mention, I override WM_ERASEBKGND as such:

HDC hDc = ::GetDC(hWnd);
if(hDc)
{
    RECT rc = {0};
    ::GetClientRect(hWnd, &rc);

    ::FillRect(hDc, &rc, hBkgndBrush);

    ::ReleaseDC(hWnd, hDc);
}

EDIT2: I made a small sample MFC project to illustrate the issue. Basically, when I move the app quickly off the screen and then back, it creates this visual "glitch" but only if control doesn't have ES_MULTILINE style:

enter image description here

c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • You don't have to select brush. The way you have it is correct. What problems are you having right now? Is this one large bitmap which covers the whole dialog and edit controls? – Barmak Shemirani Jun 13 '16 at 07:08
  • @BarmakShemirani: When I move my window very quickly off the screen and back, the background seems to have weird repeating artifacts. Do I need this brush/bitmap to be the same size as the edit control? Or larger? – c00000fd Jun 13 '16 at 07:09
  • I see what you mean. I don't know how to deal with that. I think you have to force the child control to repaint itself. It won't help to change the brush size. --- In an unrelated issue, you might want to put this on second line: `if (nCtlColor == CTLCOLOR_DLG) return hbr;` or if the bitmapBrush covers the whole dialog, use `SetBrushOrg` to align child bitmap to be lined up with the background bitmap. – Barmak Shemirani Jun 13 '16 at 07:30
  • The artifact illustrated stems from inconsistent brush origin. You can read about it here: https://msdn.microsoft.com/en-us/library/windows/desktop/dd183396(v=vs.85).aspx – Adrian McCarthy Jun 14 '16 at 22:35
  • @AdrianMcCarthy: Yes, I agree. Although how would it help me solve this issue? – c00000fd Jun 15 '16 at 02:05
  • That's why I posted a comment rather than an answer. Having a thorough understanding of the problem may lead to an answer. In general, trying to modify the edit control's painting is fraught with pitfalls, since it doesn't follow all the best practices (e.g., it does incremental updates from places other than the paint handler) and it doesn't provide enough hooks to get everything right in every case. – Adrian McCarthy Jun 15 '16 at 17:02
  • @AdrianMcCarthy: Yes, I agree with that. – c00000fd Jun 15 '16 at 17:45

1 Answers1

2

When background brush is created from bitmap using CreatePatternBrush, some "repeating artifacts" may occur during dialog resizing or moving.

To remove these artifacts, force the child controls to repaint in response to ON_WM_WINDOWPOSCHANGED message:

void CMyDialog::OnWindowPosChanged(WINDOWPOS *wndpos)
{
    CDialog::OnWindowPosChanged(wndpos);

    CWnd *wnd = GetWindow(GW_CHILD);
    while (wnd)
    {
        wnd->Invalidate(TRUE);
        wnd = wnd->GetWindow(GW_HWNDNEXT);
    }
}

or

void CMyDialog::OnWindowPosChanged(WINDOWPOS *wndpos)
{
    CDialog::OnWindowPosChanged(wndpos);
    edit1.Invalidate(FALSE);
    edit2.Invalidate(FALSE);
    ...
}

OnCtlColor override will be as follows:

HBRUSH CMyDialog::OnCtlColor(CDC* pDC, CWnd* wnd, UINT nCtlColor)
{
    if (nCtlColor == CTLCOLOR_DLG)
        return CDialogEx::OnCtlColor(pDC, wnd, nCtlColor); 
    pDC->SetBkMode(TRANSPARENT);
    return hBkgndBrush;
}

You can add other conditions based on wnd or nCtlColor to change the background of edit control only.

Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • Thanks. I updated my answer with more details. So going back to your suggestions, unfortunately calling `SetBrushOrgEx(hDC, 0, 0, NULL);` did not change anything, and invalidating my control on every window move would probably solve it, except that it also adds a nasty visible flicker. As I come to think about it now, the "artifact" issue seems to be coming from my override of WM_ERASEBKGND. Could that be it? Does FillRect do something that I'm not aware of? – c00000fd Jun 13 '16 at 20:10
  • What operating system are you using? I tested my code in Win10 only. Don't override `WM_ERASEBKGND`. Also `OnEraseBkgnd(CDC* pDC)` already has dc handle. – Barmak Shemirani Jun 13 '16 at 21:27
  • Thanks. You are correct. Unfortunately that still doesn't do it. I updated my answer with a small sample project. Can you check if it does the same on your end? Also somehow if I add `ES_MULTILINE` style the issue goes away. – c00000fd Jun 14 '16 at 19:10
  • You didn't add `OnWindowPosChanged`. Also you didn't say what Windows version you are using (I don't see flicker in Windows 10 and 7) Try it with `Invalidate(FALSE)` See updated answer. – Barmak Shemirani Jun 14 '16 at 22:14
  • Yes, calling `Invalidate(FALSE)` on my control every time the parent window is moved will patch this bug. Still, I'm not sure that it's a good solution because: 1) It wastes CPU cycles for no reason, and 2) it doesn't address the problem. It simply "patches it." That is why I didn't use it. Although yes, I'll consider it as a last resort... – c00000fd Jun 15 '16 at 02:02
  • As for which OS'es are affected, I did some tests. Interestingly enough, it works fine on XP, and then fails since Vista and up to the latest OS. So I'm more inclined to call it a Windows bug. (Unless you guys show me what am I doing wrong here.) As obviously multilined control seems to work just fine. – c00000fd Jun 15 '16 at 02:04
  • If Multi-line edit control has vertical/horizontal scroll, or auto-scroll, then the problem is worse (use another solution, for example HTML control). If it is just a single-line edit control then use the above solution. It doesn't waste CPU cycles. – Barmak Shemirani Jun 15 '16 at 06:40
  • In your `OnWindowPosChanged` I would probably limit it to `if(!(lpWndpos->flags & SWP_NOMOVE) && !(::GetWindowLongPtr(hWnd, GWL_STYLE) & ES_MULTILINE)){::InvalidateRect(hWnd, NULL, FALSE);}` – c00000fd Jun 16 '16 at 20:50
  • Make sure multi-line edit control doesn't have scroll bar or auto-scroll feature. It seems scroll-able edit controls supports solid brush colors only. – Barmak Shemirani Jun 18 '16 at 23:52