我们不清楚的就是,重复计数是如何进行的。是产生很多消息,并对消息进行编号,实现重复计数,还是只是每次只更细编号,消息还是只有一个呢?为了研究这个问题,还是自己设计一个验证的方案,用代码来证实是最靠谱的,即使网上一些只言片语的定论,也不足以说明问题的原委。
所以我的假设和验证图如下:
【按键长按产生的重复计数假设验证图】
如果是产生很多的消息在消息队列中,那么就会让我们一个一个的处理。消息不会自己跑掉的,必须是我们处理了才会取走。所以,我们可以在第一次执行这个消息处理的时候,主动延迟一定的时间,同时我们长按键盘按键不放,这样就会产生很多的重复消息,就会触发重复计数。然后在延迟过后,一个一个的来处理消息,直接将编号输出。如果有很多的消息,那么这个消息就是一长串消息的编号。我将这个编号格式化在一个字符串中,最后在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;
}