Search code examples
winapigdi+gdi

Drawing (too slow) in WM_PAINT causes flicker?


I want to draw a lot of lines in the WM_PAINT message handler with the following code.

//DrawLine with double buffering
LRESULT CALLBACK CMyDoc::OnPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    std::vector<Gdiplus::Point> points;
    std::vector<Gdiplus::Point>::iterator iter1, iter2;
    HDC hdc, hdcMem;
    HBITMAP hbmScreen, hbmOldBitmap;
    PAINTSTRUCT ps;
    RECT    rect;

    hdc = BeginPaint(hWnd, &ps);

    //Create memory dc
    hdcMem = CreateCompatibleDC(hdc);
    GetClientRect(hWnd, &rect);
    hbmScreen = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
    hbmOldBitmap = (HBITMAP)SelectObject(hdcMem, hbmScreen);

    //Fill the rect with white
    FillRect(hdcMem, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));

    //Draw the lines
    Gdiplus::Graphics graphics(hdcMem);
    Gdiplus::Pen blackPen(Gdiplus::Color(255, 0, 0));

    points = m_pPolyLine->GetPoints();
    for (iter1 = points.begin(); iter1 != points.end(); iter1++) {
        for (iter2 = iter1 + 1; iter2 != points.end(); iter2++)
            graphics.DrawLine(&blackPen, *iter1, *iter2);
    }

    //Copy the bitmap from memory dc to the real dc
    BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);

    //Clean up
    SelectObject(hdcMem, hbmOldBitmap);
    DeleteObject(hbmScreen);
    DeleteDC(hdcMem);

    EndPaint(hWnd, &ps);
    return 0;
}

However, if the size of points exceed 20, the client rect just flicker. I think the reason is that Gdiplus::DrawLines is too slow.

Is there any method to solve the flicker problem? Thanks.


Solution

  • The flickering may be caused by slow painting as well as by other things. In general, try/ensure the following:

    • Try to not rely on WM_ERASEBKGND message, i.e. return non-zero, or specify NULL in WNDCLASS::hbrBackground if possible. Often the paint method paints all the background of the dirty region, then there is no need to do the erasing.

    • If you need the erasing, it can often be optimized so that WM_ERASEBKGND returns non-zero, and the paint method then ensures the "erasing" by also painting areas not covered by the regular painted contents, if PAINTSTRUCT::fErase is set.

    • If reasonably possible, write the paint method so that it does not repaint the same pixels in one call. E.g. to make blue rect with red border, do not FillRect(red), then repainting inner part of it with FillRect(blue). Try to paint each pixel once as much as reasonably possible.

    • For complex controls/windows, the paint method may often be optimized to easily skip a lot of painting outside the dirty rect (PAINTSTRUCT::rcPaint) by proper organizing the control data.

    • When changing the control state, invalidate only the minimal required region of the control.

    • If it is not top-level window, consider using CS_PARENTDC. If your paint method does not rely on system setting clipping rectangle to the client rect of the control, this class style will lead to a somewhat better performance.

    • If you see the flickering on control/window resizing, consider to not using CS_HREDRAW and CS_VREDRAW. Instead invalidate the relevant parts of the control in WM_SIZE manually. This often allows to invalidate only smaller parts of the control.

    • If you see the flickering on control scrolling, do not invalidate whole client, but use ScrollWindow() and invalidate only small area which exposes the new (scrolled-in) content.

    • If everything above fails, then use double buffering.