当前位置:C++技术网 > 精选软件 > 用连续的鼠标单击创造鼠标双击消息,实现双击客户区最大化窗口

用连续的鼠标单击创造鼠标双击消息,实现双击客户区最大化窗口

更新时间:2016-01-10 19:18:32浏览次数:1+次

    在Windows零基础入门课程的文章《Windows零基础入门:2.19 窗口类结构体之窗口类风格CS_DBLCLKS》中,我们解释了,窗口类中含有CS_DBLCLKS类风格的窗口才能接受到双击客户区的消息,也就是WM_LBUTTONDBLCLK消息。
    双击客户区的时候,我们让窗口最大化,再双击,则还原窗口为上次的大小。这个功能在2.19的课程里已经实现了。所以,双击的逻辑,我们就不再重复讲述。
    技术永无止境。在2.19节课中,我们提到,没有CS_DBLCLKS的窗口类风格,窗口不支持双击客户区。因为没有这个风格,即使你双击了客户区,系统也不会将这个双击事件告诉窗口的,也就是不会将WM_LBUTTONDBLCLK消息投递给窗口的消息队列中。
    既然如此,我们就不设置这个窗口类风格,但是仍然要实现双击客户区实现最大化窗口呢?技术上是完全可行的!为什么呢?

    你不要被系统的各种规定所限制。既然我们学的深入了,就要灵活应用。没有CS_DBLCLKS窗口类风格,系统不会将双击的消息投递到我们的窗口消息队列中。这个意思就是,即使系统检测到了双击,也不理睬。这个过程包含了两个步骤:第一,系统要检测鼠标在客户区的双击;第二,系统还要将WM_LBUTTONDBLCLK发送到窗口的消息队列。下面用一张图简要的展示了这个过程。

系统检测双击客户区和系统投递双击消息到窗口消息队列的过程

【系统检测双击客户区和系统投递双击消息到窗口消息队列的过程】
    系统帮我们做了前面两件事情的条件就是,你要设置CS_DBLCLKS窗口类风格哈。不然系统才不会帮你干活呢!我不想给系统这个好处(CS_DBLCLKS窗口类风格),那就自己动手。
    投递消息可以使用SendMessage消息实现,这个实现很简单。您现在是要投递WM_LBUTTONDBLCLK消息,所以,可以用下面这样的语句完成:
SendMessage(hwnd,WM_LBUTTONDBLCLK,wParam,lParam);
    第一个参数就是本窗口的句柄;第二个就是消息的宏WM_LBUTTONDBLCLK,指示消息类型;第三个和第四个参数含带的信息,如果你双击时不需要,可以都填成NULL。如果处理双击消息时需要,那就将单击时的参数直接传给它就行了,也就是上面显示的那样。而且单击和双击的参数都是一样的,所以直接传给双击消息也是可以的。
    那么以上就实现了消息投递了。下面就是双击消息的检测了。我们实际上是检测两次单击,如果两次单击间隔的时间很短,就可以看成是双击了。所以,检测消息的时间,就成为主要任务。很遗憾的是,在鼠标单击的消息参数中,并没有时间的信息。不过我们可以使用函数GetMessageTime来获取当前消息发生时的时间。
    GetMessageTime函数不需要参数,返回时间值是LONG型的值,单位是毫秒。我们应该知道,快速的双击间隔应该就是毫秒级的,所以这个正好。我们也不需要知道时间的构成,反正是一个LONG型值,那么两次相减能够得到间隔时间即可。
    那么说到这里,关键的地方都已经分析出来了。那么剩下的就是如何来判断双击了。我们将两次的间隔定为500毫秒,低于500毫秒就认为是双击了。要计算间隔时间,就要存储上一次的时间。第一次检测上次时间是否为0,如果为0则,直接存储单击的时间。第二次才是计算间隔。第二次单击之后,无论如何都要将时间重置为0,以保证后续的双击的正常检测。
    这样的逻辑,基本的双击功能是有了。然而别高兴的太早。如果用户单击一下,然后隔离一秒再点击一下,这样就不构成双击。那么当用户第一次单击一下,此时已经记下了时间。然后用户后续每次都连续的快速双击,而双击与双击时间间隔比较长,结果会发现无法达到双击的效果。

    这种情况,看下图所示的序列:

第一次单击后,都是连续的双击

【第一次单击后,都是连续的双击】
    可以明显看到,第一次单击之后,后面每次都是连续的单击,而且单击的时间都小于500ms,也就是本该触发双击消息的。但是,如果我们第一次将单击存起来,然后再检测第二次单击,计算间隔,这种情况,就永远也不会检测到。这是一个Bug。因为低二次后,始终都要将消息置为0,就每次都将连续的单击两次拆开了,而不是组合在一起。
    为了解决这个问题,成功产生双击消息后,我们将上次的时间置为0,否则将当期的时间设置为上次单击的时间。如此一来,就可以解决这个问题了。我这里测试,都能够正常的工作。虽然上述的情况会在第二次连续单击的第一次单击就可能已经触发了,感觉上稍微偏差,但是还是能够达到用户想要的效果的。至于更多优化,就请你自己来处理了。
    下面是完整的代码:
#include "windows.h"
#include <tchar.h>
TCHAR tip[]=_T("C++技术网www.cjjjs.com");
// - 项目是Unicode字符集
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    static LONG LastClickTime=0;//初始化为0,便于第一次单击检测
    switch (message)
    {
        case WM_LBUTTONDOWN:
            {
                LONG lNow = GetMessageTime();//获取当前消息产生的时间,以毫秒为单位
                if (LastClickTime==0)
                {
                    LastClickTime=lNow;//第一次单击设置时间
                }
                else
                {
                    int iMs = lNow-LastClickTime;//计算时间间隔
                    if (iMs<500)
                    {
                        //检测两次单击时间间隔在指定的范围内,发送双击消息(产生真正的双击消息,而不是程序模拟一个双击逻辑判断哦)
                        SendMessage(hwnd,WM_LBUTTONDBLCLK,wParam,lParam);
                        LastClickTime=0;//重置时间,便于下次双击使用
                    }
                    else
                    {
                        LastClickTime=lNow;//未产生双击消息,则更新当前的上一次单击的时间
                    }
                }
            }
        return 0;
        case WM_LBUTTONDBLCLK:
            if(IsZoomed(hwnd))//-判断窗口是不是最大化了
                ShowWindow(hwnd, SW_SHOWNORMAL);// - 显示正常大小的窗口
            else
                ShowWindow(hwnd, SW_SHOWMAXIMIZED);// - 最大化窗口
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
        break;//跳出到默认处理
    }
    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;
    HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,0,0,350,400,NULL,NULL,hInstance,NULL);
    ShowWindow(hwnd,SW_SHOWNORMAL);

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