C++ Win32:测试鼠标是否悬停了一秒钟

C++ Win32:测试鼠标是否悬停了一秒钟,c++,winapi,callback,mouse,C++,Winapi,Callback,Mouse,我正在尝试创建一个函数,该函数仅在鼠标在同一位置停留一段时间后才运行,而不会使程序暂停(因此没有Sleep(1000)functions)。我试着让回调函数在鼠标移动时在paint中运行一个函数。在该函数中,将有一个while循环,确保鼠标自上一帧起没有移动,如果单击或移动鼠标,鼠标将断开 在回调中: case WM_MOUSEMOVE: POINT pt; GetCursorPos(&pt); ScreenToClient(hWnd, &pt);

我正在尝试创建一个函数,该函数仅在鼠标在同一位置停留一段时间后才运行,而不会使程序暂停(因此没有
Sleep(1000)
functions)。我试着让回调函数在鼠标移动时在paint中运行一个函数。在该函数中,将有一个while循环,确保鼠标自上一帧起没有移动,如果单击或移动鼠标,鼠标将断开

在回调中:

case WM_MOUSEMOVE:
    POINT pt;
    GetCursorPos(&pt);
    ScreenToClient(hWnd, &pt);
    iPosX = pt.x;
    iPosY = pt.y;

    if (!mouseMoved) {
        mouseMoved = true;
        period = 0;
        InvalidateRect(hWnd, 0, FALSE);
    }
    break;
在油漆中:

       if (mouseMoved && !MouseClicked) {

            test.newBit(1, 100, 0, hdc); //Draws a picture as a test

            oldx = iPosX;
            oldy = iPosY;

            period = 0;

            while (period <= 1000000 && !MouseClicked) {

                oldx = iPosX;
                oldy = iPosY;

                POINT pt;
                GetCursorPos(&pt);
                ScreenToClient(hWnd, &pt);
                iPosX = pt.x;
                iPosY = pt.y;

                if (iPosX != oldx || iPosY != oldy) {
                    mouseMoved = false;
                    period = 0;
                    break;
                }

                period++;
            }

            if (period <= 1000000) {
                period = 0;
                test.newBit(0, 0, 0, hdc); //Draws a picture as a test
                mouseMoved = false;
            }

        }
if(mouseMoved&!MouseClicked){
test.newBit(1100,0,hdc);//绘制一张图片作为测试
oldx=iPosX;
oldy=Ipsy;
周期=0;

while(period系统已为您实现此功能。您只需通过调用以下命令来请求鼠标悬停消息:

当光标在调用
TrackMouseEvent
中指定的时间段内悬停在窗口的客户端区域时,应用程序会收到一条消息。一旦生成
WM\u MOUSEHOVER
消息,悬停跟踪就会停止。要进一步接收
WM\u MOUSEHOVER
消息,应用程序需要重新请求hover跟踪(使用与上述代码相同的代码)

应用程序可以调用并使用
SPI\u GETMOUSEHOVERTIME
检索默认的悬停超时

请注意,这将考虑悬停矩形(
SPI_GETMOUSEHOVERWIDTH
SPI_GETMOUSEHOVERHEIGHT
)。这是鼠标光标周围的一个区域,允许鼠标在不重置悬停超时的情况下向内移动


如果你需要一个只在鼠标完全没有移动时报告鼠标悬停的解决方案,你必须自己实现。一个基于计时器的解决方案是一个干净的解决方案

每当鼠标移动时,我们都需要重置计时器。如果鼠标以前在窗口的客户端区域之外,我们还必须重新请求
WM_MOUSELEAVE
消息。这是必要的,以便在鼠标离开客户端区域时取消计时器:

LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
    enum TimerId { TimerId_MouseHover = 1 };
    static const UINT HoverTimeoutInMs = 1000;
    static int PrevX = INT_MIN;
    static int PrevY = INT_MIN;
    static bool IsMouseOutside = true;
    static bool IsMouseHovered = false;

    switch ( message ) {

    case WM_MOUSEMOVE: {
        // If mouse was previously outside, re-request WM_MOUSELEAVE messages
        if ( IsMouseOutside ) {
            TRACKMOUSEEVENT tme = { sizeof( tme ) };
            tme.dwFlags = TME_LEAVE;
            tme.hwndTrack = hWnd;
            ::TrackMouseEvent( &tme );
            IsMouseOutside = false;
        }

        int CurrX = GET_X_LPARAM( lParam );
        int CurrY = GET_Y_LPARAM( lParam );
        if ( ( CurrX != PrevX ) || ( CurrY != PrevY ) ) {
            // Mouse moved -> reset timer
            ::SetTimer( hWnd, TimerId_MouseHover, HoverTimeoutInMs, nullptr );
            PrevX = CurrX;
            PrevY = CurrY;
            IsMouseHovered = false;
            // For testing only:
            ::InvalidateRect( hWnd, nullptr, FALSE );
        }
        return 0;
    }
    case WM_MOUSELEAVE:
        ::KillTimer( hWnd, TimerId_MouseHover );
        IsMouseOutside = true;
        PrevX = INT_MIN;
        PrevY = INT_MIN;
        return 0;
每当鼠标离开客户端区域时,我们都需要取消计时器。如果不取消计时器,即使鼠标移动到窗口客户端区域之外,计时器也会过期:

LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
    enum TimerId { TimerId_MouseHover = 1 };
    static const UINT HoverTimeoutInMs = 1000;
    static int PrevX = INT_MIN;
    static int PrevY = INT_MIN;
    static bool IsMouseOutside = true;
    static bool IsMouseHovered = false;

    switch ( message ) {

    case WM_MOUSEMOVE: {
        // If mouse was previously outside, re-request WM_MOUSELEAVE messages
        if ( IsMouseOutside ) {
            TRACKMOUSEEVENT tme = { sizeof( tme ) };
            tme.dwFlags = TME_LEAVE;
            tme.hwndTrack = hWnd;
            ::TrackMouseEvent( &tme );
            IsMouseOutside = false;
        }

        int CurrX = GET_X_LPARAM( lParam );
        int CurrY = GET_Y_LPARAM( lParam );
        if ( ( CurrX != PrevX ) || ( CurrY != PrevY ) ) {
            // Mouse moved -> reset timer
            ::SetTimer( hWnd, TimerId_MouseHover, HoverTimeoutInMs, nullptr );
            PrevX = CurrX;
            PrevY = CurrY;
            IsMouseHovered = false;
            // For testing only:
            ::InvalidateRect( hWnd, nullptr, FALSE );
        }
        return 0;
    }
    case WM_MOUSELEAVE:
        ::KillTimer( hWnd, TimerId_MouseHover );
        IsMouseOutside = true;
        PrevX = INT_MIN;
        PrevY = INT_MIN;
        return 0;
如果计时器过期,则会发生悬停事件:

    case WM_TIMER:
        if ( wParam == TimerId_MouseHover ) {
            // The mouse hasn't been moved for the specified timeout:
            // This is a hover event
            IsMouseHovered = true;
            // For testing only:
            ::InvalidateRect( hWnd, nullptr, FALSE );
            return 0;
        }
        else {
            return ::DefWindowProc( hWnd, message, wParam, lParam );
        }
作为测试,每当悬停超时过期时,让我们用黑色笔刷填充客户端区域:

    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint( hWnd, &ps );
        ::FillRect( hdc, &ps.rcPaint, GetStockBrush( IsMouseHovered ? BLACK_BRUSH :
                                                                      WHITE_BRUSH ) );
        EndPaint( hWnd, &ps );
        return 0;
    }

请注意,当鼠标离开某个区域(由
SPI\u GETMOUSEHOVERWIDTH
SPI\u GETMOUSEHOVERHEIGHT
定义)时,将报告此情况,不是一个像素。哦!它不合理地闪烁的原因是小于号应该是大于号…哦,你可以通过在
WM_MOUSEMOVE
KillTimer();SetTimer()中设置计时器来更轻松地完成
并从
WM_TIMER
事件调用函数。如果在机器上运行此代码,速度会比您的快?或者在机器上运行速度会比您的慢?