当前位置:C++技术网 > 精选软件 > win32鼠标移动实时提示信息框的自动消失功能实现

win32鼠标移动实时提示信息框的自动消失功能实现

更新时间:2015-12-23 16:29:01浏览次数:1+次

    在文章《win32鼠标移动时的实时提示框的代码实现》中,我详细 分析了鼠标移动时实时提示信息的基本功能实现。如果你还没有阅读,请先阅读这篇文章,然后再继续往后面阅读。

    所以本文只讲述自动消失的功能部分。我们从最开始实现的方法说起,然后会发现工作的效果和我们想象的不一样,也就是达不到效果,然后终于找到了根本病根,然后将其轻松解决。下面是效果图:

鼠标移动实时提示信息框的自动消失

【鼠标移动实时提示信息框的自动消失】
    从图中可以看到,提示框已经看不到了,消失了。
    我们想要达到的效果就是:鼠标在客户区中移动,就实时的将光标当前的坐标信息显示出来。只要移动就会显示出来。如果鼠标停在一个地方,那么过一会,提示框就自动消失。
    如果你有开发经验话,这个对你来说不难。但是如果你从未开发过,可能对于功能需求的实现,不能快速的想到,不过慢慢的思考实现思路,会想得到的。我们C++技术网的文章就是帮助你从问题的提出到解决问题的过程和最终解决问题,提供一个参考的实现经验,帮助你迅速积累开发经验和提高解决问题的能力。
    为了提高实时提示的效率,我们当然不能每次显示信息时就创建一个窗口,显示完就销毁。所以,我们在WM_CREATE消息时即主窗口一创建就创建一个提示窗口,用于实时提示信息的。当然,此时创建的提示窗口不会显示出来。我们用一个静态的窗口句柄存储了窗口的句柄,以备后面随时操作提示窗口。
    要实现自动让提示窗口消失功能,自然很容易想到就是隔一定时间就让窗口消失。如果鼠标移动,那么就让窗口显示。鼠标移动显示提示可以在WM_MOUSEMOVE消息中使用ShowWindow函数即可。而要让每隔一定时间让窗口消失一下,就需要定时器了。
    这里我们需要清楚一个问题,那就是,定时器只要一启动,就周而复始的计时。那么时间到了之后,尽管鼠标在移动,也会让窗口消失一下。然后鼠标继续移动时又让提示窗口显示出来。这个过程就会有一个闪烁。这一点的优化,我们可以在WM_MOUSEMOVE消息处理完之后,让计时器重新启动,这样就不会在移动鼠标的时候出现闪烁了。因为移动的过程中,让计时器来不及累计时间,从而就无法触发计时器消息执行。当鼠标停下来不动了,这样才能真正累计时间了。
    那么这个优化方案就最符合我们的要求。当然一开始你不了解,直接在创建窗口的消息WM_CREATE启动计时器,也是可以基本满足要求的。如果要追求完美,那肯定不够。
    那么我们只需要在WM_MOUSEMOVE消息处理完之后调用SetTimer函数即可。这样就表示,鼠标移动完后,创建一个计时器开始计时。然而,SetTimer是创建一个计时器,那么我们移动很多次,是不是要创建很多计时器?那不是混乱了吗?不是的,我们创建计时器时都使用同一个计时器ID,这样第一次调用是创建,然后后面的调用就相当于在重置这个计时器,也就是重新开始计时。
    重新计时就正好符合我们的要求,所以,最求最好的效果,就不要在创建窗口时创建计时器了,只要在WM_MOUSEMOVE消息处理结束的最后一条语句调用SetTimer即可。这样,只要你移动鼠标,计时器就会不断的被重置,这样时间一直累计不起来,就不会产生计时器消息WM_TIMER而将提示窗口隐藏。当然,当时间到达了之后,产生了计时器消息,那么在计时器消息WM_TIMER处理中,我们简单的调用ShowWindow来隐藏窗口即可,传入的参数是SW_HIDE。因为新手只知道用这个函数显示窗口,但不知道用这个函数来隐藏窗口。
    那么设置计时器时,你可以指定一个时间,大概3000毫秒,即3秒钟。而在鼠标移动消息获取坐标和提示坐标这些在前面提到的文章里讲了。
    那么说到这里,我们的提示框自动消失功能就算做完了。那么我们来运行程序,你会发现提示框不仅不会消失,而且还会隔一定时间闪烁一下。这和我们要的结果完全不一样。
    是哪里出了问题呢?这里我百思不得其解呀,寻求了朋友帮助也没有找到真正的问题。反而是放下这个问题,准备睡觉,然后躺着就想这个问题,结果意外的发现我一个特点。那就是,当提示框闪烁时,其实伴随着主窗口的激活状态的变化,提示框消失后,主窗口被激活。而在调试的时候,总是发现鼠标移动消息多执行了一次,让提示窗口显示出来了。问题就是为什么总是会多执行一次鼠标移动消息?如果让鼠标移动消息处理得当,就可以让提示框可以消失。朋友提供的一个方法就是这样的。
    然而没有找到问题的根本,我还是不痛快的。然后对这个激活状态产生了怀疑。然后我假设当提示框消失的时候,主窗口被激活。因为光标是主窗口的,主窗口激活后,会导致光标重新定位,这样就产生了一个鼠标移动的消息。尽管鼠标的位置没有变化,但是主窗口激活后的闪动,就造成了鼠标移动消息的产生。所以每次提示框一消失,主窗口被激活,然后提示窗口就被显示出来了。所以这样就出现了闪烁,而且,提示框不会消失。
    为什么会出现主窗口激活状态的变化呢?因为我们是用按钮窗口类来实现的提示框,按钮窗口类时主窗口的子窗口,所以子窗口显示时,子窗口被激活,主窗口就处于未被激活状态。当子窗口隐藏了,自然主窗口就被激活了。
    那么你会想,如果提示框不设置父窗口为主窗口,是不是会不一样呢?实际上还是不行,总是会伴随着一个激活状态的转变,毕竟提示框消失之前,提示框是激活的。提示框消失后,激活状态会切换到主窗口,这样也还是会激活主窗口。
    所以,我想到一个很好的方法,那就是,判断鼠标移动的位置。将本次的坐标存起来,在下一次移动时,和存储起来的坐标比较,如果一样,那就不显示窗口,因为你其实没有移动。而激活刚好就是这样的特点,所以如果我猜测的正确,那么,我判断到坐标一致的话,就直接返回而不继续处理消息,那么后面的显示提示框的函数就不会执行。这样就不会再显示出来了。然而你只要移动鼠标,人的手微微移动,都会让前后两次的坐标不一样,这样就是真的移动了鼠标,这样就显示提示框。
    结果用代码一验证,真的能够正常的消失了。移动又会实时的提示出来。这样,也就证明我的猜测是对的,所以,问题的根本就是激活状态的切换。这类问题,根本就没有文档说明,网上几乎找不到资料,即使是有,你也不知道是这个问题。所以这个问题的隐蔽性极强,如果不是细致,根本很难发现这个问题,自然也不会用这个简单的方法就达到了效果。

    当然一个读者也提到了一个问题,那就是当窗口缩小时移动到屏幕中央,就会发现提示框在主窗口外了,似乎与主窗口脱离了关系。这个细节也没有在本次解决,将在下一篇文章来解决分析。

    本文分析的程序完整代码如下:


#include "windows.h"
#include <Windowsx.h>//GET_X_LPARAM,GET_Y_LPARAM宏需要这个头文件
#include <tchar.h>
// - 项目是Unicode字符集
HINSTANCE g_hInstance=NULL;
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    TCHAR Info[100]=_T("C++技术网http://www.cjjjs.com");
    RECT rect;
    static HWND hTip=NULL;
    static POINT ptOld,ptNew;
    switch (message)
    {
    case WM_CREATE:
        hTip = CreateWindow(_T("Button"),_T("测试"),BS_FLAT|WS_POPUP|WS_CHILD,0,0,100,40,hwnd,NULL,g_hInstance,NULL);
        return 0;
     case WM_PAINT:
         hdc = BeginPaint(hwnd,&ps);
         GetClientRect(hwnd,&rect);
         SelectObject(hdc,CreatePen(PS_SOLID,1,RGB(120,120,120)));
         for (int i=0;i<rect.right;i++)
         {
             MoveToEx(hdc,i*10,0,NULL);
             LineTo(hdc,i*10,rect.bottom);
         }
         for (int i=0;i<rect.bottom;i++)
         {
             MoveToEx(hdc,0,i*10,NULL);
             LineTo(hdc,rect.right,i*10);
         }
         EndPaint(hwnd,&ps);
         return 0;
     case WM_KILLFOCUS:
         SetFocus(hwnd);
         return 0;
     case WM_MOUSEMOVE:
         {
             ptNew.x=GET_X_LPARAM(lParam);
             ptNew.y=GET_Y_LPARAM(lParam);
             if (ptNew.x==ptOld.x&&ptNew.y==ptOld.y)return 0;//如果上一次和本次的光标坐标一致,则未移动鼠标,不显示窗口
             TCHAR msg[100]=_T("");
             wsprintf(msg,_T("(%d ,%d)"),ptNew.x,ptNew.y);
             SetWindowText(hTip,msg);
             MoveWindow(hTip,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)+50,100,40,FALSE);
             ShowWindow(hTip,SW_SHOWNORMAL);
             ptOld.x = ptNew.x;

            ptOld.y = ptNew.y;
            SetTimer(hwnd,1,3000,NULL);
         }
         return 0;
     case WM_TIMER:
             ShowWindow(hTip,SW_HIDE);
        return 0;
     case WM_DESTROY:
         PostQuitMessage(0);
         return 0;
     default:
         return DefWindowProc(hwnd, message, wParam, lParam);
    }
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,LPSTR lpCmd,int iShow)
{
    TCHAR ClassName[] = _T("MyClass");
    TCHAR title1[] = _T("C++技术网http://www.cjjjs.com");
    WNDCLASS wndClass;
    wndClass.cbClsExtra=0;
    wndClass.cbWndExtra=0;
    wndClass.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
    wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    wndClass.hInstance = hInstance;
    wndClass.lpfnWndProc = WinProc;
    wndClass.lpszClassName = ClassName;
    wndClass.lpszMenuName=NULL;
    wndClass.style=CS_HREDRAW|CS_VREDRAW;

    if(!RegisterClass(&wndClass))return 0;
    g_hInstance = hInstance;
    HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,0,0,440,400,NULL,NULL,hInstance,NULL);
    ShowWindow(hwnd,SW_SHOWNORMAL);

    MSG msg;
    while (GetMessage(&msg,NULL,0,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}