Jack
Jack

Reputation: 16724

How do I handle WM_MOUSEWHEEL?

I'm handling the WM_MESSAGE like below but I cannot make the window move at all. I can't figure out why. What am I missing?

void handleMouseWheel(HWND hwnd, int delta)
{
    SCROLLINFO si;
    si.cbSize = sizeof(SCROLLINFO);
    si.fMask = SIF_PAGE | SIF_POS;

    if(!GetScrollInfo(hwnd, SB_VERT, &si)) {
        ErrorExit(NAMEOF(GetScrollInfo),  __LINE__, __FILENAME__);   
    }

    int nOldPos = si.nPos;
    int nPos = nOldPos + wheelScrollLines(hwnd, delta, si.nPage);
    
    SetLastError(0);
    nPos = SetScrollPos(hwnd, SB_VERT, nPos, TRUE);

    POINT pt;
    pt.x = 0;
    pt.y = nOldPos - nPos;

    HDC hdc = GetDC(hwnd);
    if(!hdc) {
        assert(!"GetDC failed");
    }

    if(!LPtoDP(hdc, &pt, 1)) {
        assert(!"LPtoDP failed");
    }

    ReleaseDC(hwnd, hdc);

    if(!ScrollWindow(hwnd, 0, -pt.y, NULL, NULL)) {
        ErrorExit(NAMEOF(ScrollWindow), __LINE__, __FILENAME__);
    }
}

where wheelScrollLines() is defined as:

int wheelScrollLines(HWND hwnd, int delta, int nPage)
{
    static int accumulator;
    static int lastActivity;
    static HWND lastHwnd;

    int lines;

    int dwNow = GetTickCount();

    if(nPage < 1) {
        nPage = 1;
    }

    int linesPerDataWheelDelta = defScrollSpeed;
    SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerDataWheelDelta, 0);

    if(linesPerDataWheelDelta == WHEEL_PAGESCROLL || linesPerDataWheelDelta > nPage) {
        linesPerDataWheelDelta = nPage;
    }

    if(lastHwnd != hwnd)
    {
        lastHwnd = hwnd;
        accumulator = 0;
    }
    else if((dwNow - lastActivity) > GetDoubleClickTime() * 2)
    {
        accumulator = 0;
    }
    else if(accumulator > 0 && delta < 0)
    {
        accumulator = 0;
    }

    if(linesPerDataWheelDelta > 0)
    {
        accumulator += delta;
        lines = (accumulator * linesPerDataWheelDelta) / WHEEL_DELTA;
        accumulator -= (lines * WHEEL_DELTA) / linesPerDataWheelDelta;
    }
    else
    {
        lines = 0;
        accumulator = 0;
    }

    lastActivity = dwNow;

    return -lines;
}

fullcode:

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Gdi32.lib")

#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE

#include <windows.h>
#include <limits.h>
#include <Commctrl.h>
#include <crtdbg.h>
#include <strsafe.h>
#include <string.h>
#include <assert.h>

#ifdef UNICODE
#define STRSPLIT wcsrchr
#else
#define STRSPLIT strrchr
#endif

#define __FILENAME__ (STRSPLIT(TEXT(__FILE__), '/') ? STRSPLIT(TEXT(__FILE__), '/') + 1 : TEXT(__FILE__))
#define NAMEOF(s) TEXT(#s)
#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))

#define defScrollSpeed 3

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProc1(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WndProc2(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
void createWindow1(HWND);
void createWindow2(HWND);
int scrollHeight(void);
DWORD ShowLastError(LPWSTR lpszFunction, int line, LPWSTR filename);
void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename);
int scrollHeight(void);
int getHeight(HWND control);
void setUpScrollBar(HWND hwnd);
void handleMouseWheel(HWND hwnd, int delta);
int wheelScrollLines(HWND hwnd, int delta, int nPage);

HBRUSH hBrush1, hBrush2;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {

    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"my window";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    hBrush1 = CreateSolidBrush(RGB(173, 164, 237));
    hBrush2 = CreateSolidBrush(RGB(171, 171, 171));

    RegisterClassW(&wc);
    HWND hWnd =
    CreateWindowW(wc.lpszClassName, L"window",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE | ES_AUTOHSCROLL,
                  100, 100, 330, 270, NULL, 0, hInstance, 0);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!IsDialogMessage(hWnd, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    DeleteObject(hBrush1);
    DeleteObject(hBrush2);

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    switch(msg)
    {
        case WM_CREATE:
            createWindow1(hwnd);
            createWindow2(hwnd);
            setUpScrollBar(hwnd);
        break;

        case WM_MOUSEWHEEL:
            handleMouseWheel(hwnd, HIWORD(wParam));
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void createWindow1(HWND hOwner)
{
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"window2";
    wc.hInstance     = NULL;
    wc.hbrBackground = hBrush1;
    wc.lpfnWndProc   = WndProc1;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"window2",
                  WS_VISIBLE | WS_TABSTOP | WS_CHILD,
                  5, 5, 300, 200, 
                  hOwner, 0, NULL, 0);
}

void createWindow2(HWND hOwner)
{
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"window3";
    wc.hInstance     = NULL;
    wc.hbrBackground = hBrush2;
    wc.lpfnWndProc   = WndProc2;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"window3",
                  WS_VISIBLE | WS_TABSTOP | WS_CHILD,
                  5, 120, 300, 200, 
                  hOwner, 0, NULL, 0);
}

LRESULT CALLBACK WndProc1(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

    switch(msg)
    {
        case WM_CREATE:
            CreateWindow(L"Button", L"Button A",
                         WS_VISIBLE | WS_TABSTOP | WS_CHILD,
                         5, 5, 80, 25,
                         hwnd, 0, NULL, 0);
        break;

        case WM_KEYUP:
        case WM_KEYDOWN:
            MessageBox(NULL, L"hello from proc1", L"", MB_OK);
            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK WndProc2(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 
{

    switch(msg)
    {
        case WM_CREATE:
            CreateWindow(L"Button", L"Button B", 
                         WS_VISIBLE | WS_TABSTOP | WS_CHILD,
                         5, 5, 80, 25,
                         hwnd, 0, NULL, 0);
        break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void setUpScrollBar(HWND hwnd)
{
    RECT rc = { 0 };
    GetClientRect(hwnd, &rc);
    SCROLLINFO si = { 0 };
    si.cbSize = sizeof(SCROLLINFO);
    si.fMask = SIF_ALL;
    si.nMin = 0;
    si.nMax = 300;
    si.nPage = (rc.bottom - rc.top);
    si.nPos = 0;
    si.nTrackPos = 0;
    SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
}

int wheelScrollLines(HWND hwnd, int delta, int nPage)
{
    static int accumulator;
    static int lastActivity;
    static HWND lastHwnd;

    int lines;

    int dwNow = GetTickCount();

    if(nPage < 1) {
        nPage = 1;
    }

    int linesPerDataWheelDelta = defScrollSpeed;
    SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerDataWheelDelta, 0);

    if(linesPerDataWheelDelta == WHEEL_PAGESCROLL || linesPerDataWheelDelta > nPage) {
        linesPerDataWheelDelta = nPage;
    }

    if(lastHwnd != hwnd)
    {
        lastHwnd = hwnd;
        accumulator = 0;
    }
    else if((dwNow - lastActivity) > GetDoubleClickTime() * 2)
    {
        accumulator = 0;
    }
    else if(accumulator > 0 && delta < 0)
    {
        accumulator = 0;
    }

    if(linesPerDataWheelDelta > 0)
    {
        accumulator += delta;
        lines = (accumulator * linesPerDataWheelDelta) / WHEEL_DELTA;
        accumulator -= (lines * WHEEL_DELTA) / linesPerDataWheelDelta;
    }
    else
    {
        lines = 0;
        accumulator = 0;
    }

    lastActivity = dwNow;

    return -lines;
}

void handleMouseWheel(HWND hwnd, int delta)
{
    SCROLLINFO si;
    si.cbSize = sizeof(SCROLLINFO);
    si.fMask = SIF_PAGE | SIF_POS;

    if(!GetScrollInfo(hwnd, SB_VERT, &si)) {
        ErrorExit(NAMEOF(GetScrollInfo),  __LINE__, __FILENAME__);   
    }

    int nOldPos = si.nPos;
    int nPos = nOldPos + wheelScrollLines(hwnd, delta, si.nPage);
    
    SetLastError(0);
    nPos = SetScrollPos(hwnd, SB_VERT, nPos, TRUE);

    POINT pt;
    pt.x = 0;
    pt.y = nOldPos - nPos;

    HDC hdc = GetDC(hwnd);
    if(!hdc) {
        assert(!"GetDC failed");
    }

    if(!LPtoDP(hdc, &pt, 1)) {
        assert(!"LPtoDP failed");
    }

    ReleaseDC(hwnd, hdc);

    if(!ScrollWindow(hwnd, 0, -pt.y, NULL, NULL)) {
        ErrorExit(NAMEOF(ScrollWindow), __LINE__, __FILENAME__);
    }
}

int getHeight(HWND control)
{
    RECT rt;

    if(!GetWindowRect(control, &rt)) {
        ErrorExit(NAMEOF(getHeight), __LINE__, __FILENAME__);
    }

    return rt.bottom - rt.top;
}

void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename)
{
    DWORD dw = ShowLastError(lpszFunction, line, filename);
    ExitProcess(dw);
}

DWORD ShowLastError(LPWSTR lpszFunction, int line, LPWSTR filename)
{
    #define MAX_DIGITS 16

   /* 
    * NOTE!!: calling GetLastError() must be done before calling
    * any other function, that would reset the GetLastError(), making
    * this function report error about the wrong function.
    */
    DWORD dw = GetLastError();
    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0,
        NULL
    );

    lpDisplayBuf = (LPVOID) LocalAlloc(LMEM_ZEROINIT, 
            (lstrlen((LPCTSTR)lpMsgBuf) +
            lstrlen((LPCTSTR)lpszFunction) + 40 +
            (line > 0 ? MAX_DIGITS : 0) +
            (filename != NULL ? lstrlen(filename) : 0)) *
            sizeof(TCHAR)
    );
    StringCchPrintf((LPTSTR)lpDisplayBuf,
                    LocalSize(lpDisplayBuf) / sizeof(TCHAR),
                    TEXT("%s failed with %d: %s"),
                    lpszFunction, dw, lpMsgBuf
    );
    MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
    return dw;
}

Upvotes: 0

Views: 1190

Answers (2)

Zeus
Zeus

Reputation: 3880

As Jonathan said, you need to use:

handleMouseWheel(hwnd, GET_WHEEL_DELTA_WPARAM(wParam));

But the window does not move because of a calculation error, not the missing SW_SCROLLCHILDREN flag. When you call:

nPos = SetScrollPos(hwnd, SB_VERT, nPos, TRUE);

npos will be updated to the previous position of the scroll box. At this time, npos is equal to nOldPos. And every subsequent update will be equal.

However, pt.y = nOldPos-nPos;. So pt.y is 0 every time. When you call ScrollWindow(hwnd, 0, pt.y, NULL, NULL), the window will not move .

So you need to change the code to:

//nPos = SetScrollPos(hwnd, SB_VERT, nPos, TRUE); //old code
SetScrollPos(hwnd, SB_VERT, nPos, TRUE);
...
if (!ScrollWindow(hwnd, 0, pt.y, NULL, NULL))
...

Edit:

Because your pt.y = nOldPos-nPos; may make pt.y calculate the wrong value at the top or bottom.So you can use GetScrollInfo directly, like this:

void handleMouseWheel(HWND hwnd, int delta)
{
    SCROLLINFO si;
    si.cbSize = sizeof(SCROLLINFO);
    si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;

    if (!GetScrollInfo(hwnd, SB_VERT, &si)) {
        ErrorExit(LPWSTR(NAMEOF(GetScrollInfo)), __LINE__, LPWSTR(__FILENAME__));
    }
    int nOldPos = si.nPos;
    int nPos = nOldPos + wheelScrollLines(hwnd, delta, si.nPage);
    si.fMask = SIF_POS;
    SetScrollPos(hwnd, SB_VERT, nPos, TRUE);
    GetScrollInfo(hwnd, SB_VERT, &si);

    if (!ScrollWindow(hwnd, 0, nOldPos - si.nPos, NULL, NULL)) {
        ErrorExit(LPWSTR(NAMEOF(ScrollWindow)), __LINE__, LPWSTR(__FILENAME__));
    }
    UpdateWindow(hwnd);
}

And it works for me.

Upvotes: 1

Jonathan Potter
Jonathan Potter

Reputation: 37122

You're using HIWORD to extract the delta from wParam in the WM_MOUSEWHEEL message, but this is incorrect according to the documentation:

enter image description here

HIWORD returns an unsigned value; as the delta is a signed value, you need to use GET_WHEEL_DELTA_WPARAM instead.

Upvotes: 1

Related Questions