在我的子类过程中未收到 WM_LBUTTONUP

Alw*_*uff 3 winapi listview mouseevent

我试图在用户单击列表视图后释放鼠标左键时捕获该事件。

在下面的代码中,我需要双击才能获得提示音。所需的行为是在用户释放鼠标左键后立即发出哔声。

代码胜于雄辩,所以这里是说明问题的最小示例:

#include <windows.h>
#include <windowsx.h>   // various listview macros etc
#include <CommCtrl.h>
#include <stdio.h>      // swprintf_s()

// enable Visual Styles
#pragma comment( linker, "/manifestdependency:\"type='win32' \
                         name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
                         processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
                         language='*'\"")

// link with Common Controls library
#pragma comment( lib, "comctl32.lib") 

//global variables
HINSTANCE hInst;

// subclass procedure for listview -> implements drag and drop
LRESULT CALLBACK Example(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam,
    UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    switch (message)
    {
    case WM_LBUTTONUP:    
    {
        MessageBeep(0);  // so I know  it is received
    }
        return DefSubclassProc(hwnd, message, wParam, lParam);

    case WM_NCDESTROY:
        ::RemoveWindowSubclass(hwnd, Example, 0);
        return DefSubclassProc(hwnd, message, wParam, lParam);

    }
    return ::DefSubclassProc(hwnd, message, wParam, lParam);
}

// main window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_CREATE:
    {
        //================ create an example listview
        RECT rec = { 0 };
        GetClientRect(hwnd, &rec);

        HWND hwndLV = CreateWindowEx(0, WC_LISTVIEW,
            L"", WS_CHILD | WS_VISIBLE | WS_BORDER | LVS_REPORT | WS_CLIPCHILDREN, 
            50, 50, 250, 200, hwnd, (HMENU)2000, hInst, 0);

        // set extended listview styles
        ListView_SetExtendedListViewStyle(hwndLV, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);

        // add some columns
        LVCOLUMN lvc = { 0 };

        lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
        lvc.fmt = LVCFMT_LEFT;

        for (long nIndex = 0; nIndex < 5; nIndex++)
        {
            wchar_t txt[50];
            swprintf_s(txt, 50, L"Column %d", nIndex);

            lvc.iSubItem = nIndex;
            lvc.cx = 60;
            lvc.pszText = txt;

            ListView_InsertColumn(hwndLV, nIndex, &lvc);
        }

        // add some items
        LVITEM lvi;

        lvi.mask = LVIF_TEXT;

        for (lvi.iItem = 0; lvi.iItem < 10000; lvi.iItem++)
        {
            for (long nIndex = 0; nIndex < 5; nIndex++)
            {
                wchar_t txt[50];
                swprintf_s(txt, 50, L"Item %d%d", lvi.iItem, nIndex);

                lvi.iSubItem = nIndex;
                lvi.pszText = txt;
                if (!nIndex)  // item 
                    SendDlgItemMessage(hwnd, 2000, LVM_INSERTITEM, 0, reinterpret_cast<LPARAM>(&lvi));
                else            // sub-item
                    SendDlgItemMessage(hwnd, 2000, LVM_SETITEM, 0, reinterpret_cast<LPARAM>(&lvi));
            }
        }

        //============================ subclass it
        SetWindowSubclass(hwndLV, Example, 0, 0);
    }
        return 0L;

    case WM_CLOSE:
        ::DestroyWindow(hwnd);
        return 0L;
    case WM_DESTROY:
    {
        ::PostQuitMessage(0);
    }
        return 0L;
    default:
        return ::DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

// WinMain

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
    int nCmdShow)
{
    // store hInstance in global variable for later use
    hInst = hInstance;

    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    // register main window class
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInst;
    wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = L"Main_Window";
    wc.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);

    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"Window Registration Failed!", L"Error!", MB_ICONEXCLAMATION |
            MB_OK);

        return 0;
    }

    // initialize common controls
    INITCOMMONCONTROLSEX iccex;
    iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    iccex.dwICC = ICC_LISTVIEW_CLASSES;
    InitCommonControlsEx(&iccex);

    // create main window
    hwnd = CreateWindowEx(0, L"Main_Window", L"Listview Drag and Drop",
        WS_OVERLAPPEDWINDOW,
        50, 50, 400, 400, NULL, NULL, hInstance, 0);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

    return Msg.wParam;
}
Run Code Online (Sandbox Code Playgroud)

单击列表视图中的一行后,没有发出哔声。我究竟做错了什么?

Rem*_*eau 5

根据这个讨论:

ListView 控件吃鼠标消息

...我正在对 ListView 进行子分类以观察 WM_LBUTTONDOWN 和 WM_LBUTTONUP... 我的问题:我看到 WM_LBUTTONDOWN,但从未看到 WM_LBUTTONUP。Spy++ 告诉我这两条消息都将发送到控件。我对控件如何“吃掉”WM_LBUTTONUP 感到困惑。

...

问题是列表控件本身设计有一些相当复杂的点击处理。当列表控件的默认 windowproc 收到 WM_LBUTTONDOWN 或 WM_RBUTTONDOWN 消息时,它进入自己的模式消息循环,一直持续到收到相应的 WM_LBUTTONUP 或 WM_RBUTTONUP 消息。所以是的,本质上,这个模态消息循环吃掉了 WM_LBUTTONUP/WM_RBUTTONUP 消息;您的窗口代码永远不会有机会使用它

解决:如果你需要处理的按钮式消息,那么你需要处理按键按压消息以及和你的按钮-按下信息处理程序应调用基的窗口过程。本质上,您自己的代码将需要接管所有点击处理逻辑;因此您的处理程序需要使用 GetKeyState 检查 Shift 和 Control 键的状态,并相应地选择和取消选择列表项。(要正确处理 Shift 键单击,您可以使用 ListView_GetSelectionMark 和 ListView_SetSelectionMark 来获取和设置多个选择的“锚”点。)

MSDN 文档实际上提到了这一点:

默认列表视图消息处理

WM_LBUTTONDOWN

根据是否启动单击或拖动操作以不同方式处理。为了确定涉及哪个操作,列表视图控件进入模态消息循环,直到释放按钮或移动鼠标。在单击的情况下,列表视图控件可能会更改具有焦点的项目和选择的项目,同时考虑光标位置、SHIFT 和 CTRL 键的状态等。然后列表视图控件向其父窗口发送一个 NM_CLICK(列表视图)通知代码。

如果拖动从一个项目开始,列表视图控件将选择并设置焦点到该项目。然后它向父窗口发送一个 LVN_BEGINDRAG 通知代码。父窗口负责实际执行拖动操作。

如果在窗口背景上开始拖动,列表视图控件将进入另一个模式消息循环,使用户能够通过拖动鼠标形成一个矩形。矩形内的项目被选中。

...

WM_R按钮DOWN

处理方式与 WM_LBUTTONDOWN 消息相同,除了控件发送 NM_RCLICK(列表视图)通知代码(而不是 NM_CLICK(列表视图))和 LVN_BEGINRDRAG 通知代码(而不是 LVN_BEGINDRAG)。请注意,控件会处理相应的 WM_RBUTTONUP 消息,并且不会调度它。因此,即使通过子类化控件,应用程序也无法看到此消息。