The curious pattern of pre-emptively rejecting the solution to your problem: Redrawing during resizing

Raymond Chen

Raymond

A customer had a program that drew a little bit of content against the right-hand edge of its client area. They found that when the user resized the window from small to large, they got bad rendering:

X
X
X
X
X
 

The customer added, “If we add the CS_HREDRAW window style, then the problem goes away, but we don’t want to use it.”

This is another curious case of pre-emptively rejecting the solution to your problem. They found the answer, and then asked for a way to solve the problem without using the answer.

Upon pressing further, we learned that the reason they don’t want to use the CS_HREDRAW window style is that it introduces flicker.

You can solve that by using a flicker-free updating model, like double-buffering.

But suppose they don’t want to do double-buffering, for whatever reason. Maybe the cost of a full repaint is too high, and they don’t want to repaint the parts that didn’t change.

What you can do is invalidate only the part that needs to be redrawn.

Let’s demonstrate the problem with a simplified version that merely draws a thin border along the right edge. This is basically laziness on my part so I don’t have to deal with fonts.

Start with the scratch program and make these changes:

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
    RECT rc;
    GetClientRect(hwnd, &rc);
    Polyline(pps->hdc, (POINT*)&rc, 2);
    rc.left = rc.right - 2;
    FillRect(pps->hdc, &rc, GetSysColorBrush(COLOR_DESKTOP));
}

In addition to drawing a two-pixel border along the right edge, the program also draws a diagonal line inside the window. This lets you see whether any unrelated content was repainted.

Observe that as-is, the program exhibits the problem when you resize the window wider.

Observe also that this change fixes the problem:

    wc.style = CS_HREDRAW;

However, it comes at a cost of redrawing the entire window, as evidenced by the fact that the diagonal line is always updated to match the window size.

Okay, change that line back to wc.style = 0; because we are going to try to solve the problem without triggering a full repaint.

What we want to do is be informed when the window is about to be resized, so we can invalidate the last two pixels. Enter the WM_WINDOW­POS­CHANGING message. This message is sent as part of the resizing operation. The window size hasn’t changed yet, but it’s about to!

BOOL OnWindowPosChanging(HWND hwnd, WINDOWPOS* lpwpos)
{
    if (!(lpwpos->flags & SWP_NOSIZE)) {
        RECT rc;
        GetClientRect(hwnd, &rc);
        rc.left = rc.right - 2;
        InvalidateRect(hwnd, &rc, TRUE);
    }
    return FORWARD_WM_WINDOWPOSCHANGING(hwnd, lpwpos, DefWindowProc);
}

    HANDLE_MSG(hwnd, WM_WINDOWPOSCHANGING, OnWindowPosChanging);

When we are informed that the window position is about to change, we check whether it’s due to a change in size. If so, then we get the client rectangle (which will be the old client rectangle) and invalidate the last two columns of pixels, which is exactly the size of the right-aligned content. We then allow the message to be processed normally.

When you run this program, you’ll notice two things:

  1. The two-pixel border on the right hand side draws correctly. In particular, the previous border erases when the window changes width.
  2. The main content of the window does not repaint. You can see this because the diagonal line is drawn from corner to corner of the old window size.

 

Raymond Chen
Raymond Chen

Follow Raymond   

7 comments

Comments are closed.

  • Harold H
    Harold H

    An interesting and educational exercise, BUT, it seems to me, there’s a problem:

    “the diagonal line is drawn from corner to corner of the old window size.”

    So, yes, you have eliminated the “flicker” of the window being redrawn, but you’ve introduced a new problem. When the user resizes the window, the content all stays the same. Having the diagonal line drawn to the old window size will look strange and may not be what the user wants.

    • Paul Mercer
      Paul Mercer

      The diagonal line was provided to show if the full windows was or wasn’t updated – purely for demonstration purposes. If the entire window needed redrawing then a typical double buffered approach as mentioned in the article would probably be best.

    • Avatar
      Edward Wohlman

      Presumably you’re mainly concerned about the cost of a full redraw while you are dragging the window border around, as it will keep firing off redraws every few pixels of resize.
      So you can use this trick of just invalidating the rightmost edge of the window during the resize, and once the user releases the mouse button do a single full redraw to update the whole window including the diagonal line.
      That keeps things fluid while minimizing the amount of work being wasted before the final size has been reached.

    • Avatar
      Brian MacKay

      Very good management of invalidation regions has been the mainstay of creating reactive graphical Windows apps since the days of 16-bit. If you move a rectangle, you would inflate the initial rectangle bounds slightly, invalidate that inflated rectangle and then invalidate the new (slightly inflated) rectangle position. If you re-route a line connection between two rectangles, you do the same thing, but this time with rectangles drawn around each line segment (for the initial and final position). If you make the window larger, you invalidate the rectangles that represent the newly shown region of the screen. I wrote a bunch of graphical apps back in the 90s and never used double-buffering.

      • Avatar
        Ivan Kljajic

        True. But when it’s not your code you have to sigh and briefly wonder if for() loops are still that slow.

  • Avatar
    cheong00

    The recurring pattern of these questions typically involves something that does A, B, C, D and E, and the user just want to remove D behavior but don’t want to do A, B, C and E themselves.

  • Avatar
    Ulysses

    Many people get this wrong, but I’m a little disappointed that you have too.

    Double (et al) buffering is a specific setup where two or more fullscreen GPU buffers take turns driving the display signal. It plays nice with the display refresh cycle to provide coherent frames. What you describe is merely called buffering, back buffering, or offscreen rendering.