键盘和鼠标在 windows 中的重要性不必多说,地球人都知道!键盘上每一个有意义的键都对应着一个唯一的标识值,称之为扫描码。但是这种扫描码是硬件相关的,为了实现设备无关性的要求,在 windows 应用程序中,使用的往往是与设备无关的虚拟码。
对键盘操作的响应过程基本如下:
用户按下一个键时,与键盘驱动程序( KEYBOARD.DRV )进行中断处理并调用 windows 用户模块( USER.EXE )中的有关程序来生成键盘消息,然后消息被发送到系统的消息队列中由相应的应用应用程序进行处理。鼠标的处理过程与键盘类似。但是注意无论是鼠标还是键盘的所产生的消息经过操作系统处理后都只会被发送给特定的窗口,即具有“输入焦点”的窗口来进行处理。
键盘消息
键盘消息通常可分为按键消息和字符消息两类,用户按下或松开一个键时,就产生一个按键消息,当一个按键组合产生了一个可以显示的字条时,就产生了一个字符消息。
按键消息一般又可以分为系统按键和非系统按键。
系统按键:是指使用了 Alt 等与相关输入键组成产生的消息,一般这些消息都由操作系统内部直接处理。如果应用程序处理了这些系统键消息,就要调用 DefWindowProc 函数,以便不影响 windows 对它们的处理。
非系统按键:对应于那些不使用组合键的按键消息。
再对按键消息的两个变量 wParam 和 lParam 做一些解释:(名堂还真不少 T_T )
1. wParam
包含了识别按下键的虚键码,这些码是由系统定义的设备无关的。可以在 windows.h 中找到找到相关定义。
2. lParam
32 位的变量 lParam 所表示的含义可以分为以下 7 个部分
(1) 重复计数位( 0~15 位)
表示当前消息的重复次数。
(2) OEM 扫描码( 16~23 位)
OEM 扫描码是键盘发送的码值,因为是设备相关的帮一般被忽略掉。
(3) 扩展键标志( 24 位)
在有 Alt 或 Ctrl 键按下时为 1, 否则为 0 。
(4) 保留位( 25~28 位)
系统保留,一般不用。
(5) 关联码( 29 位)
主要用来记录某键与 Alt 等键的组合状态,若按下 Alt 键,当 WM_SYSKEYDOWN 消息发送到某个激活窗口时,其值为 1, 否则为 0 。
(6) 键的先前状态( 30 位)
用于记录先前某键的状态。
(7) 转换状态( 31 位)
用于记录被始终按下的某键所产生的消息。
在 WinMain 函数里的消息循环中包含了 TranslateMessage 函数,它的主要功能是把按键消息转化为字符消息,即把按键所产生的原始的KEYDOWN/KEYUP消息转化成WM_CHAR消息。 同样,字符消息也可以分为系统和非系统消息两类。
Windows 系统支持两类字符集: OEM 和 ANSI 。 OEM 是 IBM 的字符集,在 windows 中使用不多,目前大多使用的是 ANSI 字符集。
鼠标消息
1. 鼠标操作
简单的单击操作包含了按下和松开这一全过程;而双击操作实际上是指用户在知时间内(默认为 0.5 秒)的再次单击操作。
在鼠标消息中,参数 lParam 包含了鼠标的位置,低字节是 X 坐标,高字节是 Y 坐标。参数 wParam 则包含了一个指示各种虚键状态的值。
通过用户区消息的 wParam 和 lParam 参数,程序员就可以确定鼠标的位置和状态。
对于鼠标的消息处理,一般分为两种,一种要对 Ctrl 等键进行监视,另一种则不需要。下面是一个示例
case WM_LbUTTONDWON: // 鼠标按下时 ctrl 和 shift 都被按下
If(( wParam&MK_CONTROL) && ( wParam&MK_SHIFT) )
…
break;
case WM_LBUTTONDOWN: // 不监视组合按键
…
break;
此外,要使窗口能监视双击消息,必须在注册窗口类的时候使该类具有 CS_DBLCLKS 属性才行,否则只能收到两条单击消息。
2. 光标
可以使用系统光标或者调用 LoadCursor 加载自定义光标资源
示例程序
在下面的一个例子中,显示鼠标和键盘的消息响应。程序的用户区被分为四个区域,每个区域里光标设置成不同的样式。通过监视键盘按键,可对一个 10 个字符长的缓冲区里输入字符,并最后显示在鼠标上方,鼠标移动时同时修改字符输出的位置。可以按下 BACK 键删掉已经输入的字符,缓冲区满时再输入和空的时候删字符的操作都被拒绝,并用消息框进行提示。代码如下:
#define BufSize 10
static char lpszBuffer[BufSize];
static int nNumChar = 0 ;
static int i = 0 ;
static POINT pt;
……
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMsg,
UINT wParam,
LONG lParam)
{
HDC hdc;
PAINTSTRUCT ps;
HCURSOR hCur;
switch (iMsg)
{
case WM_CHAR: // 处理非系统键的消息
if (wParam == VK_BACK) // 按下退格键
{
if (nNumChar == 0 )
{
MessageBox(hWnd, " 没有字符可以删除! " , NULL, MB_OK);
}
else
{
-- nNumChar;
// 此函数刷新用户区,会产生PAINT消息
InvalidateRect(hWnd, NULL, TRUE);
}
break ;
}
if (nNumChar >= BufSize) // 字符超过缓冲区大小
{
MessageBox(hWnd, " 缓冲区已满!删除字符请用退格键 " , NULL, MB_OK);
break ;
}
lpszBuffer[nNumChar ++ ] = (unsigned char )wParam;
InvalidateRect(hWnd, NULL, TRUE);
break ;
case WM_PAINT: // 将处理过的字符输出
hdc = BeginPaint(hWnd, & ps);
// 调整坐标使字出现在鼠标上方
TextOut(hdc, pt.x - 15 , pt.y - 15 , lpszBuffer, nNumChar);
EndPaint(hWnd, & ps);
break ;
case WM_MOUSEMOVE: // 移动鼠标后改变文本输出坐标并刷新
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam); // 鼠标的坐标
// 在不同的区域显示不同的光标
if (pt.x < 400 && pt.y < 400 )
hCur = LoadCursor(NULL, IDC_NO);
else if (pt.x > 400 && pt.y < 400 )
hCur = LoadCursor(NULL, IDC_HELP);
else if (pt.x < 400 & pt.y > 400 )
hCur = LoadCursor(NULL, IDC_SIZEALL);
else if (pt.x > 400 && pt.y > 400 )
hCur = LoadCursor(NULL, IDC_CROSS);
SetCursor(hCur);
InvalidateRect(hWnd, NULL, TRUE);
break ;
case WM_DESTROY:
PostQuitMessage( 0 );
return 0 ;
default :
return (DefWindowProc(hWnd, iMsg, wParam, lParam));
}
return 0 ;
}