Scrollbars bart 6 – The wheel

Date:August 7, 2003 / year-entry #15
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20030807-00/?p=42963
Comments:    4
Summary:The mouse wheel is tricky because the mouse wheel UI guidelines indicate that you should scroll by a user-specified amount for each "click" of the mouse, where one click is WHEEL_DELTA mouse units (called a "detent"). There are two subtle points about the above requirement: First, that the amount of scrolling is a user setting...

The mouse wheel is tricky because the mouse wheel UI guidelines indicate that you should scroll by a user-specified amount for each "click" of the mouse, where one click is WHEEL_DELTA mouse units (called a "detent"). There are two subtle points about the above requirement: First, that the amount of scrolling is a user setting which must be respected, and second, that the wheel can report values that are not perfect multiples of WHEEL_DELTA.

In particular, there is the possibility that a high-resolution mouse wheel will report wheel scroll units smaller than WHEEL_DELTA. For example, consider a wheel mouse that supports "half-clicks". When you turn the wheel halfway between clicks, it reports WHEEL_DELTA/2, and when you reach a full click, it reports another WHEEL_DELTA/2. To handle this properly, you need to make sure that by the time the full click is reached, the window has scrolled by exactly the amount it would have scrolled if the user had been using a low-resolution wheel that reported a single wheel motion of WHEEL_DELTA.

(I once referred to this in email as a "sub-detent wheel" and was accused of coining a phrase.)

To handle the first point, we requery the user's desired scroll delta at each mouse wheel message. To handle the second point, we accumulate detents as they arrive and consume as many of them as possible, leaving the extras for the next wheel message.

int g_iWheelCarryover;      /* Unused wheel ticks */

LRESULT OnMouseWheel(HWND hwnd, int xPos, int yPos, int zDelta, UINT fwKeys)
{
    UINT uScroll;
    if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &uScroll, 0)) {
        uScroll = 3;    /* default value */
    }

    /*
     *  If user specified scrolling by pages, do so.
     */
    if (uScroll == WHEEL_PAGESCROLL)
    {
        uScroll = g_cLinesPerPage;
    }

    /*
     *  If user specified no wheel scrolling, then don't do wheel scrolling.
     *  This also avoids a divide-by-zero below.
     */
    if (uScroll == 0)
    {
        return 0;
    }

    zDelta += g_iWheelCarryover;    /* Accumulate wheel motion */

    /*
     *  See how many lines we should scroll.
     *  This relies on round-towards-zero.
     */
    int dLines = zDelta * (int)uScroll / WHEEL_DELTA;

    /*
     *  Record the unused portion as the next carryover.
     */
    g_iWheelCarryover = zDelta - dLines * WHEEL_DELTA / (int)uScroll;


    /*
     *  Do the scrolling.
     */
    ScrollDelta(hwnd, -dLines);

    return 0;
}

    /* Add to WndProc */
    HANDLE_MSG(hwnd, WM_MOUSEWHEEL, OnMouseWheel);

Exercise: What is the significance of the (int) cast in the computation of dLines?

Exercise: Assuming you don't have a high-resolution wheel mouse, how would you test that your sub-detent mouse wheel handling was working properly?


Comments (4)
  1. milbertus says:

    #1: Isn’t that because WHEEL_DELTA might be a float or double, which would cause a truncation (or cast) compiler warning?

    #2: Hmmm…maybe you could hard code in a huge value, so that one click of your mouse wheel wouldn’t accumulate to the amount of what your app is looking for.

  2. MacTruck says:

    Those who can’t do math right should not write code. Period.

    For those who are looking to use the above to calculate delta carryovers, the above formula for is mathematically wrong. Let’s go through code with the following assumptions:

    uScroll = 3;

    zDelta = WHEEL_DELTA / 2;

    g_iWheelCarryover = 0;

    Those are the initial default values the system should give. WHEEL_DELTA, according to MSDN Library, is 120, so zDelta = 60;

    zDelta += g_iWheelCarryover;

    // zDelta += 0;

    // Therefore, zDelta is still 60.

    int dLines = zDelta * (int)uScroll / WHEEL_DELTA;

    // dLines = 60 * 3 / 120;

    // Therefore, dLines = 1. 180 / 120 = 1.5, but this is truncated to 1 because it is an integer.

    g_iWheelCarryover = zDelta – (dLines * WHEEL_DELTA / (int)uScroll);

    // g_iWheelCarryover = 60 – (1 * 120 / 3);

    // Therefore, g_iWheelCarryover = 60 – 40 = 20.

    20 is incorrect and that algorithm is _WAY_ off base. 60 is what the value _SHOULD_ be. g_iWheelCarryover is supposed to contain the remainder of the scroll amount in WHEEL_DELTA units. In this case, 1/2 a line (.5) was "lost". The correct algorithm for g_iWheelCarryover should be:

    g_iWheelCarryover = (zDelta * (int)uScroll) – (dLines * WHEEL_DELTA);

    // g_iWheelCarryover = (60 * 3) – (1 * 120);

    // g_iWheelCarryover = (180) – (120);

    // g_iWheelCarryover = 60;

    This also means that the check for divide by zero is unnecessary.

    Learn to write code correctly before posting to the Internet.

    (Oh, and the answer to #2 is to use SendMessage() with the appropriate parameters).

  3. Raymond Chen says:

    Um, no, 20 is the correct value for g_iWheelCarryover at the end. 40 units of zDelta were used to scroll one line, leaving 20 units of carryover.

    Learn to write code correctly before posting to the Internet.

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