当前位置:C++技术网 > 精选软件 > 长按键盘按键的WM_KEYDOWN消息的重复计数原理和作用分析及代码验证

长按键盘按键的WM_KEYDOWN消息的重复计数原理和作用分析及代码验证

更新时间:2016-01-03 22:10:14浏览次数:1+次

    在学习到键盘消息部分时,书中没有对键盘按下的重复计数做详细讲解,使我还是迷惑不解。然而为了研究清楚,网上这方面的底层基础知识基本上没有,所以还是自己动手假设和验证,来做研究性学习。
    我们不清楚的就是,重复计数是如何进行的。是产生很多消息,并对消息进行编号,实现重复计数,还是只是每次只更细编号,消息还是只有一个呢?为了研究这个问题,还是自己设计一个验证的方案,用代码来证实是最靠谱的,即使网上一些只言片语的定论,也不足以说明问题的原委。

    所以我的假设和验证图如下:

按键长按产生的重复计数假设验证图

【按键长按产生的重复计数假设验证图】
    如果是产生很多的消息在消息队列中,那么就会让我们一个一个的处理。消息不会自己跑掉的,必须是我们处理了才会取走。所以,我们可以在第一次执行这个消息处理的时候,主动延迟一定的时间,同时我们长按键盘按键不放,这样就会产生很多的重复消息,就会触发重复计数。然后在延迟过后,一个一个的来处理消息,直接将编号输出。如果有很多的消息,那么这个消息就是一长串消息的编号。我将这个编号格式化在一个字符串中,最后在WM_PAINT消息中输出。
    而如果是第二种情形,即只会有一个WM_KEYDOWN消息在消息队列中,只递增消息的编号,那么我们最终输出的就只有一个编号,也就是最后一次重复的消息的编号。延迟越长,长按键盘按键越长,产生的最终编号就越大。不过这种情形只会有一个消息,也就只有一个编号。
    以上两种方案可以明显区分开,下面就使用代码来验证。注意,我这里没有直接将研究的结果告诉你,是希望你也可以形成这种研究的方式去探索知识,而不是一味的接受知识。研究得来的知识,比直接接受的知识深刻很多,理解更为深刻。
    下面是验证的代码:
if (VK_BACK==wParam)
{
    if (bisFirst)
    {
        Sleep(2000);//第一次消息 进行延迟
        bisFirst=false;
    }
    else
    {
        //第一次的按键不输出,只输出长按按键产生的重复的WM_KEYDOWN消息
        int ic = LOWORD(lParam);//低字部分存储了按键重复的次数编号
        wsprintf(tip,_T("%s %d"),tip,ic);//将历次的编号格式化到字符串数组中
        //以后没有延迟的长按都会迅速被处理,也就看到的计数是1
    }
}
InvalidateRect(hwnd,NULL,TRUE);//刷新显示结果

    执行的结果如下图所示:

按键长按产生的重复计数结果图

【按键长按产生的重复计数结果图】
    从图中可以看出,我们的假设2成立,延迟后重复计数累计到了47,之后长按没有延迟都能够及时处理,所有计数都是1。其实我们也很好理解,这样是最省空间的,不然大量的消息还不把消息队列撑爆了。
    总结一下:系统有这样一个机制,那就是在WM_KEYDOWN消息的lParam参数的低字部分,存储了消息重复的编号。长按开始的第一个WM_KEYDOWN消息的lParam参数的低字存储的编号为1。如果消息处理不及时,导致消息队列中消息的堆积,系统就会对这个参数的编号进行递增,而不会产生大量的WM_KEYDOWN消息放在消息队列中。所以,当你处理好一个消息的时候,紧接去获取下一个消息时(由消息循环产生),还是只有一个WM_KEYDOWN消息,而这个消息的lParam参数的低字部分就是消息产生的重复的次数。如果你要保证消息不丢失的话,就可以利用这个重复次数一次性的处理,而不需要一个一个的处理WM_KEYDOWN消息,达到的效果是一样的。比如删除,你可以一次性删除重复的次数这个数量的文字的个数。如果你对重复的WM_KEYDOWN消息不感兴趣,而只保证处理最新一次按下的消息,那么即使重复N次你也不用管,只将这个消息当做普通的消息,忽略lParam参数的低字部分的重复计数即可。
    我们利用重复计数可以实现长按按键实现删除所有文字的效果,请阅读后续的分析文章。下面是本程序的所有完整代码:
#include "windows.h"
#include <tchar.h>
// - 项目是Unicode字符集
TCHAR cjjjs[100]=_T("C++技术网http:www.cjjjs.com");
//TCHAR tip2[100]=_T("提示:按下键盘的←(退格键)可以删除了字符");
TCHAR tip[100]=_T("");
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static RECT rect;
    static int iDelCount=0;
    static int sum=0;
    static bool bisFirst=true;
    switch (message)
    {
    case WM_PAINT:
        hdc = BeginPaint(hwnd,&ps);
        SelectObject(hdc,GetStockObject(SYSTEM_FIXED_FONT));
        TextOut(hdc,0,0,cjjjs,lstrlen(cjjjs));
        TextOut(hdc,0,50,tip,lstrlen(tip));
        EndPaint(hwnd,&ps);
        return 0;
     case WM_KEYDOWN:
        if (VK_BACK==wParam)
        {
            if (bisFirst)
            {
                Sleep(2000);//第一次消息 进行延迟
                bisFirst=false;
            }
            else
            {
                //第一次的按键不输出,只输出长按按键产生的重复的WM_KEYDOWN消息
                int ic = LOWORD(lParam);//低字部分存储了按键重复的次数编号
                wsprintf(tip,_T("%s %d"),tip,ic);//将历次的编号格式化到字符串数组中
                //以后没有延迟的长按都会迅速被处理,也就看到的计数是1
            }
        }
        InvalidateRect(hwnd,NULL,TRUE);//刷新显示结果
        return 0;
     case WM_RBUTTONDOWN:
         {
            InvalidateRect(hwnd,NULL,TRUE);
         }
         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_HAND);
    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,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;
}