Scrollbars part 12: Applying WM_NCCALCSIZE to our scrollbar sample

Date:September 17, 2003 / year-entry #66
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20030917-00/?p=42463
Comments:    2
Summary:Now that we have learned about the intricacies of the WM_NCCALCSIZE message, we can use it to get rid of the flicker in our resizing code. We just take the trick we used above and apply it to the scroll program. First, we need to get rid of the bad flickery resize, so return the...

Now that we have learned about the intricacies of the WM_NCCALCSIZE message, we can use it to get rid of the flicker in our resizing code. We just take the trick we used above and apply it to the scroll program.

First, we need to get rid of the bad flickery resize, so return the OnWindowPosChanging function to the version before we tried doing metaphor work:

BOOL OnWindowPosChanging(HWND hwnd, LPWINDOWPOS pwp)
{
    if (!(pwp->flags & SWP_NOSIZE)) {
        RECT rc = { 0, 0, pwp->cx, pwp->cy };
        AdjustSizeRectangle(hwnd, WMSZ_BOTTOM, &rc);
        pwp->cy = rc.bottom;
    }
    return 0;
}

Instead, our work will happen in the WM_NCCALCSIZE handler.

UINT OnNcCalcSize(HWND hwnd, BOOL fCalcValidRects,
                  NCCALCSIZE_PARAMS *pcsp)
{
    UINT uRc = (UINT)FORWARD_WM_NCCALCSIZE(hwnd,
                        fCalcValidRects, pcsp, DefWindowProc);

    if (fCalcValidRects) {
        //  Give names to these things
        RECT *prcClientNew = &pcsp->rgrc[0];
        RECT *prcValidDst  = &pcsp->rgrc[1];
        RECT *prcValidSrc  = &pcsp->rgrc[2];
        int dpos;
        int pos;

        // Did we drag the top edge enough to scroll?
        if (prcClientNew->bottom == prcValidSrc->bottom &&
            g_cyLine &&
            (dpos = (prcClientNew->top - prcValidSrc->top)
                                            / g_cyLine) != 0 &&
            (pos = ClampScrollPos(g_yOrigin + dpos)) != g_yOrigin) {

            *prcValidDst = *prcClientNew;
            ScrollTo(hwnd, pos, FALSE);
            prcValidDst->top -= dpos * g_cyLine;

            uRc = WVR_VALIDRECTS;
        }

    }
    return uRc;
}

    /* Add to WndProc */
    HANDLE_MSG(hwnd, WM_NCCALCSIZE, OnNcCalcSize);

This uses a new helper function which we extracted from the ScrollTo function. (If I had planned this better, this would have been factored out when we first wrote ScrollTo.)

int ClampScrollPos(int pos)
{
    /*
     *  Keep the value in the range 0 .. (g_cItems - g_cyPage).
     */
    pos = max(pos, 0);
    pos = min(pos, g_cItems - g_cyPage);
    return pos;
}

I am not entirely happy with this code, however. It is my personal opinion that the WM_NCCALCSIZE handler should be stateless. Notice that this one modifies state (by calling ScrollTo). If I had more time (sorry, I'm rushed now - you'll learn why soon), I would have put the state modification into the WM_WINDOWPOSCHANGING message. So I will leave that as another exercise.

Exercise: Keep the WM_NCCALCSIZE message stateless.


Comments (2)
  1. Adrian says:

    ClampScrollPos() has a bug. (I recently made the same mistake in one of my apps.) If the window is large enough that the window doesn’t need a scroll bar, then ClampScrollPos will return a negative value (because g_cItems – g_cyPage will be less than zero). The simple fix is to swap the two lines.

    In my apps, I’ve always tracked scrolling by pixels rather than lines. It simplifies many of the calculations, and smooth scrolling becomes a possibility. You just need a multiplier in the scroll-by-line functions that tells you the (average) pixels per line.

    Great series, though. I thought I knew everything there was about getting scrolling just right, but I’ve already learned a couple new bits. In a sense it’s a shame that it’s this much work to get scrolling right. That could explain all the custom controls out there that don’t do it right.

    ANIMATED SCROLLING

    I like animated scrolling to help the eye follow along. In addition to keeping track of the current scroll position, you also track the current *target* position. All of your scroll commands then compute a new target position.

    I make it a user selectable option (using SPI_GETLISTBOXSMOOTHSCROLLING as a default). If animated scrolling is disabled, then iPos = iTarget and proceed as usual.

    But if it’s enabled, send yourself a custom "animate" message instead. The handler for this message moves the current position closer to the target position and calls ScrollWindow(). At the end of OnPaint, you post another "animate" message if the current position hasn’t caught up with the target. Since WM_PAINT messages are low priority (only dispatched when cue is empty), you can still accumulate additional scrolling commands from the mouse or keyboard and move the target as the animation proceeds.

    What we want is snappy animation (so you can still jump to the top or bottom of a long document quickly) while giving enough motion for the eye to track and land. So what I do is:

    int delta = iTarget – iPos;

    if (-3 < delta || delta < 3)

    iPos += delta / 3;

    else

    iPos = iTarget

    Thus, if you have a long way to scroll, we whizz by. But as we get to the target location, the rate slows nicely. Really slick if you track things in pixels rather than items.

  2. Adrian says:

    Oops, I got the comparison wrong at the end of my last post. Should be:

    if (-3 < delta && delta < 3)

    iPos = iTarget;

    else

    iPos += delta/3;

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index