1

I'm creating Win32 control:

m_progress = CreateWindowExW(0, PROGRESS_CLASSW, L"ProgressBar", WS_VISIBLE | WS_CHILD | WS_TABSTOP, 153, 339, 135, 33, m_window, (HMENU)0, m_instance, 0);
SendMessageW(m_progress, WM_SETFONT, (WPARAM)m_fontBold, TRUE);
SendMessageW(m_progress, PBM_SETRANGE, 0, MAKELPARAM(0, 100));

It's working, but I also want to draw text with percentage on it So I've subclassed progress control like this:

m_progressPrevProc = (WNDPROC)SetWindowLongPtrW(m_progress, GWLP_WNDPROC, (LONG_PTR)ProgressMsgProcessor);
...
static LRESULT CALLBACK ProgressMsgProcessor(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    if (msg == WM_PAINT)
    {
        PAINTSTRUCT ps;
        RECT rc = { 5, 5, 135, 33 };
        //HDC hdc = BeginPaint(hwnd, &ps);
        //SelectObject(hdc, g_App.m_fontBold);
        //DrawTextA(hdc, "100 %", -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
        //EndPaint(hwnd, &ps);
    }

    return CallWindowProcW((WNDPROC)PrevWndProcProzess, hwnd, msg, wparam, lparam);
}

But if uncomment atleast "HDC hdc = BeginPaint(hwnd, &ps);" then text appears, but default control absolutely disappears (like it's not drawn) How can I fix it to show default windows control with text on it, because I don't need to draw custom control, only add overlay text? Thank you

  • One someone's called `BeginPaint()/EndPaint()` the dirty region is validated and calling it again will return an empty region. The proper way to do this is to call `CallWindowProc` first, to allow the control to perform it's default painting, and then call `GetDC() / ReleaseDC()` to get a DC to draw into over the top. – Jonathan Potter May 31 '18 at 12:05
  • @JonathanPotter Ah, beat me to the punch, just noticed. – Paul Sanders May 31 '18 at 12:07

1 Answers1

2

The problem here is that you cleared the update region with your BeginPaint and EndPaint calls, so the progress bar doesn't think it has to draw anything. It's a weakness of the way WM_PAINT works that you can't paint over an existing control in this way. Instead, you have to do something like this:

static LRESULT CALLBACK ProgressMsgProcessor(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    if (msg == WM_PAINT)
    {
        // Paint the control first
        CallWindowProcW ((WNDPROC)PrevWndProcProzess, hwnd, msg, wparam, lparam);

        // Then draw over it
        HDC hDC = GetDC (hwnd);
        HFONT hOldFont = (HFONT) SelectObject(hDC, g_App.m_fontBold);

        // Draw your own stuff into hDC

        SelectObject (hDC, hOldFont);
        ReleaseDC (hwnd, hDC);
        return 0;
    }

    return CallWindowProcW ((WNDPROC)PrevWndProcProgress, hwnd, msg, wparam, lparam);
}

Other notes:

  • Your code as posted is drawing under the control, not over it (!). My code fixes that.
  • If you select and object into a DC, you should select the old one back in when you are done. Again, my code shows how to do this.
Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • Just nitpicking, but in this example, you don't need to call `CallWindowProcW()` conditionally. I would move it above the `if` statement and save its return value to a local variable, and then `return` that variable after the `if` block is done, eg: `LRESULT lRes = CallWindowProcW((WNDPROC)PrevWndProcProzess, hwnd, msg, wparam, lparam); if (msg == WM_PAINT) { ... } return lRes;` – Remy Lebeau May 31 '18 at 16:49
  • 1
    @RemyLebeau Yes, that would be neater here but (a) I was striving for clarity primarily, and (b) in the more general case calling `CallWindowProc` before doing anything else might not work for all type of message that `ProgressMsgProcessor` wants to handle (or might, in the future, want to handle). – Paul Sanders May 31 '18 at 16:58
  • This is a great solution, many thanks. I've been stumbled on this for quite a while. Now the only issue remains the text being overdrawn each time you draw, for some reason the previous text always remains there. – Mecanik Apr 20 '21 at 09:03