为listview重现默认变量滚动速度

时间:2015-09-18 04:35:28

标签: listview winapi

引言:

用户启动多个项目选择,如下图所示:

enter image description here

然后他将这个选择扩展到最后一个垂直可见项目之外。

Listview开始自动滚动,但其自动滚动速度会根据鼠标位置而变化

问题:

我想知道如何重现这种行为,因为没有API为我这样做。因此,我必须自己做,但无法找到相关的例子。

为了使事情变得清晰,我希望重现默认的Windows 行为,而不是滚动我自己的自动滚动逻辑。

我努力解决这个问题:

我使用Spy ++尝试查找执行描述行为但无法找到任何有用信息的WM_TIMER消息。

我能够确定的是,listview经常调用0x10C1消息,该消息未正式记录。谷歌搜索帮助我发现这条消息被称为LVM_GETACCVERSION,但这是我能找到的。没有关于此消息的文档,也没有代码示例,或者看起来如此。

我已经用Google搜索驱动自动滚动逻辑的等式,但没有找到任何内容。

出于任何其他想法,我转过来寻求帮助。

问题:

我们如何重现INTRODUCTION部分中描述的默认自动滚动行为?

同样,为了使事情变得清晰,我想重现默认的Windows 行为,而不是滚动我自己的自动滚动逻辑。

1 个答案:

答案 0 :(得分:1)

算法很简单:

  1. 计算控件的客户端矩形和鼠标光标之间的像素数。
  2. ScrollWindow()乘以那么多像素。
  3. 立即重复,直到鼠标开始。
  4. 研究滚动量
    测试代码是为垂直滚动列表视图提供的。

    子类列表视图:

    g_OriginalListViewProc = (WNDPROC)GetWindowLongPtr(m_hwndListView, GWLP_WNDPROC);
    SetWindowLongPtr(m_hwndListView, GWLP_WNDPROC, (LONG_PTR)ListViewProc);
    

    新窗口程序:

    int GetCursorOffset(HWND a_Window)
    {
        RECT listRect;
        GetClientRect(a_Window, &listRect);
        ClientToScreen(a_Window, (POINT*)&listRect + 0);
        ClientToScreen(a_Window, (POINT*)&listRect + 1);
    
        POINT cursorPos;
        GetCursorPos(&cursorPos);
    
        if (cursorPos.y < listRect.top)
            return cursorPos.y - listRect.top;
        else if (cursorPos.y > listRect.bottom)
            return cursorPos.y - listRect.bottom;
    
        return 0;
    }
    
    WNDPROC g_OriginalListViewProc = 0;
    LRESULT ListViewProc(HWND a_HWND, UINT a_Message, WPARAM a_WParam, LPARAM a_LParam)
    {
        switch (a_Message)
        {
        case WM_PAINT:
            {
                static int lastPos = 0;
    
                int scrollPos = GetScrollPos(a_HWND, SB_VERT);
                int delta = scrollPos - lastPos;
                lastPos = scrollPos;
    
                TRACE(_T("Ticks=%d Pos=%06d Delta=%05d Offs=%04d\n"), GetTickCount(), scrollPos, delta, GetCursorOffset(a_HWND));
            }
            break;
        }
    
        return g_OriginalListViewProc(a_HWND, a_Message, a_WParam, a_LParam);
    }
    

    滚动列表视图并观察scroll_amount == offset_from_control。

    研究触发器
    打开任务管理器,开始滚动。您将在一个处理器核心上看到CPU负载峰值达到100%。这证明他们在繁忙的循环中滚动而不是处理一些计时器。

    研究循环
    在父窗口,SetTimer()到类似5000毫秒的东西,在计时器的消息到达时放置一个断点并开始滚动。当计时器命中时,你会在callstack上看到类似的东西:

    MyExe.exe!DialogProc(WM_TIMER)
    user32.dll!__InternalCallWinProc@20()
    user32.dll!UserCallDlgProcCheckWow()
    user32.dll!DefDlgProcWorker()
    user32.dll!_DefDlgProcW@16()
    user32.dll!__InternalCallWinProc@20()
    user32.dll!UserCallWinProcCheckWow()
    user32.dll!DispatchMessageWorker()
    user32.dll!_DispatchMessageW@4()
    comctl32.dll!_ListView_DragSelect@12()
    comctl32.dll!_ListView_HandleMouse@24()
    comctl32.dll!_ListView_WndProc@16()
    ...
    

    这表明_ListView_DragSelect运行内部消息循环,处理滚动迭代之间的任何消息。

    额外备注

    1. 当鼠标离开控件的rect时,您需要在SetCaptureWM_LBUTTONDOWN接收WM_MOUSEMOVE。不要忘记稍后ReleaseCapture
    2. 您可以在每次迭代后发布自定义窗口消息,并在到达后处理它,而不是使用处理消息进行循环。
    3. 您可以设置计时器并对其进行处理(ReactOS has implemented it with a timer),而不是繁忙的循环。这样,你就不会加速到100%的CPU,滚动速度也不会取决于用户的CPU速度。
    4. 当鼠标重新进入控制区域并像往常一样处理WM_MOUSEMOVE时,您可以停止循环/计时器。