WM_HOVER 无法使用 WinAPI ListView

WM_HOVER not working with WinAPI ListView

我在屏幕上绘制了一个 ListView。现在,我试图在用户将鼠标悬停在项目上时调用一个函数。我现在正在尝试使用 WM_HOVER 因为它似乎是最直接的;但是,它似乎没有用。我让 WM_CLICK 工作正常,但不是为了悬停。

WM_HOVER 会做我需要的事情吗,还是我应该研究其他事情,例如 TrackMouseEvent()

这是一些示例代码。我认为,真正唯一相关的部分是 WndProc() 中紧跟 case WM_HOVER:

的几行
   //libraries
#pragma comment ("lib", "Comctl32.lib")
#pragma comment ("lib", "d2d1.lib")

#include "targetver.h"
#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <vector>
#include <string>
#include <dwrite.h>
#include <d2d1.h>
#include <commctrl.h> 
#include <strsafe.h>
#define IDS_APP_TITLE           103
#define IDR_MAINFRAME           128
#define IDD_PRACTICE_DIALOG 102
#define IDD_ABOUTBOX            103
#define IDM_EXIT                105
#define IDI_PRACTICE            107
#define IDI_SMALL               108
#define IDC_PRACTICE            109
#define IDC_MYICON              2
#ifndef IDC_STATIC
#define IDC_STATIC              -1
#endif
#define MAX_LOADSTRING 100

#define PROJECT_LIST_VIEW     110


BOOL mouseTracking = false;
 
// Global Variables:
HINSTANCE hInst;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name

std::vector<std::string> stringsVector = { "String1", "String2", "String3" };

//D2D1 pointers
ID2D1Factory* m_pD2DFactory;
ID2D1DCRenderTarget* m_pRenderTarget;
ID2D1SolidColorBrush* m_pBlackBrush;

//dimension variables
int w, h;
RECT rc;


HWND listViewHandle;

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

ATOM MyRegisterClass(HINSTANCE hInstance);

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow);

HRESULT CreateDeviceResources(HWND hwnd, HDC hdc);

bool onRender(HWND hwnd, PAINTSTRUCT* ps);

///////////////////
//Function to insert the items into the list view
//////////////////
BOOL InsertListViewItems(HWND hWndListView, int cItems);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    // Initialize global strings
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_PRACTICE, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

    // Perform application initialization:
    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_PRACTICE));
    MSG msg;
    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    switch (message)
    {
    case WM_CREATE:
        //Create the List View Control
        listViewHandle = CreateWindow(WC_LISTVIEW, L"", WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_EDITLABELS, 100, 100, 500, 500, hWnd, (HMENU)PROJECT_LIST_VIEW, hInst, NULL);
        InsertListViewItems(listViewHandle, stringsVector.size());
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_PAINT:
    {
        HDC hdc = BeginPaint(hWnd, &ps);
        onRender(hWnd, &ps);
        EndPaint(hWnd, &ps);
        break;
    }
    case WM_SIZE:
    {
        if (m_pRenderTarget)
        {
            m_pRenderTarget->Release();
            m_pRenderTarget = nullptr;
        }
        break;
    }
    ////////////////////////////////////////////
    //
    // HERE ARE THE MOUSE MOVE CASES
    //
    /////////////////////////////////////////////
    case WM_MOUSEHOVER:
    {
        OutputDebugStringA("HOVER\n");
        mouseTracking = FALSE;
        break;
    }
    case  WM_MOUSEMOVE:
        if (!mouseTracking)
        {
            // start tracking if we aren't already
            TRACKMOUSEEVENT tme;
            tme.cbSize = sizeof(TRACKMOUSEEVENT);
            tme.dwFlags = TME_HOVER | TME_LEAVE;
            tme.hwndTrack = listViewHandle; //This is the handle to the ListView window
            tme.dwHoverTime = HOVER_DEFAULT;
            mouseTracking = TrackMouseEvent(&tme);
        }
        break;
    case  WM_MOUSELEAVE:
        mouseTracking = FALSE; 
        break;
    case WM_NOTIFY:
    {
        NMLVDISPINFO* plvdi;
        switch (((LPNMHDR)lParam)->code)
        {
        case NM_CLICK:
            OutputDebugStringA("CLICK\n");
            break;
        case LVN_GETDISPINFO:
        {
            ////////////////////
            //This is the callback that sets the pszText attribute of the items
            ////////////////////

            plvdi = (NMLVDISPINFO*)lParam;

            const char* inString = stringsVector[plvdi->item.iItem].c_str();
            size_t size = strlen(inString) + 1;
            wchar_t* outString = new wchar_t[size];

            size_t outSize;
            mbstowcs_s(&outSize, outString, size, inString, size - 1);
            LPWSTR ptr = outString;

            StringCchCopy(plvdi->item.pszText, plvdi->item.cchTextMax, outString);

            delete[] outString;
            break;
        }
        }
        break;
    }
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PRACTICE));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_PRACTICE);
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    return RegisterClassExW(&wcex);
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    D2D1CreateFactory(
        D2D1_FACTORY_TYPE_SINGLE_THREADED,
        &m_pD2DFactory
    );

    hInst = hInstance; // Store instance handle in our global variable
    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr);

    if (!hWnd)
    {
        return FALSE;
    }
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return TRUE;
}
//////////////////
//Function to insert the items into the list view
//////////////////
BOOL InsertListViewItems(HWND hWndListView, int cItems)
{
    LVCOLUMN lvc;
    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
    // Initialize LVITEM members that are common to all items.

    // Initialize LVITEM members that are different for each item.
    lvc.iSubItem = 0;
    lvc.pszText = (LPWSTR)L"test";
    lvc.cx = 200;
    // Insert items into the list.
    if (ListView_InsertColumn(hWndListView, 0, &lvc) == -1)
        return FALSE;

    LVITEM lvI;
    // Initialize LVITEM members that are common to all items.
    lvI.pszText = LPSTR_TEXTCALLBACK; //This should send an LVN_GETDISPINFO message
    lvI.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    lvI.stateMask = 0;
    lvI.iSubItem = 0;
    lvI.state = 0;

    // Initialize LVITEM members that are different for each item.
    for (int index = 0; index < cItems; index++)
    {
        lvI.iItem = index;
        lvI.iImage = index;

        // Insert items into the list.
        if (ListView_InsertItem(hWndListView, &lvI) == -1)
            return FALSE;
    }

    return TRUE;
}

HRESULT CreateDeviceResources(HWND hwnd, HDC hdc)
{
    HRESULT hr = S_OK;

    if (!m_pRenderTarget) { //If m_pRenderTarget changes size

        // Create a DC render target.
        D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
            D2D1_RENDER_TARGET_TYPE_DEFAULT,
            D2D1::PixelFormat(
                DXGI_FORMAT_B8G8R8A8_UNORM,
                D2D1_ALPHA_MODE_IGNORE),
            0,
            0,
            D2D1_RENDER_TARGET_USAGE_NONE,
            D2D1_FEATURE_LEVEL_DEFAULT
        );

        hr = m_pD2DFactory->CreateDCRenderTarget(&props, &m_pRenderTarget);


        GetClientRect(hwnd, &rc);

        if (SUCCEEDED(hr))
            hr = m_pRenderTarget->BindDC(hdc, &rc);

        D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);
        w = size.width;
        h = size.height;

        if (SUCCEEDED(hr))
        {//position objects

            // Create a black brush
            hr = m_pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &m_pBlackBrush);
        }

    }
    return hr;
}

bool onRender(HWND hwnd, PAINTSTRUCT* ps)
{
    HDC hdc = ps->hdc;

    HRESULT hr;
    hr = CreateDeviceResources(hwnd, hdc);


    if (SUCCEEDED(hr))
    {
        m_pRenderTarget->BeginDraw();

        m_pRenderTarget->SetTransform(D2D1::IdentityMatrix());
        m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));

        

        hr = m_pRenderTarget->EndDraw();
    }

    SetWindowPos(listViewHandle, HWND_TOP, w * .1, h * .1, w * .3, h * .3, SWP_SHOWWINDOW);
    SendMessage(listViewHandle, LVM_SETCOLUMNWIDTH, 0, w * .2);

    if(SUCCEEDED(hr))
        return true;
    return false;
}

正如dxiv所说,如果你需要处理NM_HOVER消息,你需要添加LVS_EX_TRACKSELECT样式:

ListView_SetExtendedListViewStyle(listViewHandle, LVS_EX_TRACKSELECT);

如果不想选择hover,只想处理hover,可以参考:How can I detect if the mouse is over an item/subitem in a List View control?

编辑:

根据 : TrackMouseEvent tracks mouse events in your window, but only if the events belong to your window

因此您需要继承 ListView 并处理其 window 过程:

WNDPROC oldListViewProc; //A global variable

oldListViewProc = (WNDPROC)SetWindowLongPtr(listViewHandle, GWL_WNDPROC, (LONG_PTR)ListViewProc);

......


LRESULT CALLBACK ListViewProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_MOUSEHOVER:
    {
        OutputDebugStringA("HOVER\n");
        return 0;
    }   

    case  WM_MOUSEMOVE:
        if (!mouseTracking)
        {
            // start tracking if we aren't already
            TRACKMOUSEEVENT tme;
            tme.cbSize = sizeof(TRACKMOUSEEVENT);
            tme.dwFlags = TME_HOVER | TME_LEAVE;
            tme.hwndTrack = hwnd; //This is the handle to the ListView window
            tme.dwHoverTime = HOVER_DEFAULT;
            mouseTracking = TrackMouseEvent(&tme);
        }
        break;
    case  WM_MOUSELEAVE:
        mouseTracking = FALSE;
        break;
    }

    return CallWindowProc(oldListViewProc, hwnd, msg, wParam, lParam);
}