第六章 鼠标消息与键盘消息

  在Microsoft Windows 中,键盘和鼠标是两个标准的用户输入源,在一些交叠的操作中通常相互补充使用。当然,鼠标在今天的应用程序中比10年前使用得更为广泛。甚至在一些应用程序中,我们更习惯于使用鼠标,例如在游戏、画图程序、音乐程序,以及Web创览器等程序中就是这样。然而,我们可以不使用鼠标,但绝对不能从一般的PC中拆掉键盘。
  相对于个人计算机的其他组件,键盘有非常久远的历史,它起源于1874年的第一台Remington打字机。早期的计算机程序员用键盘在Hollerith卡片上打孔,以后在哑终端上用键盘直接与大型主机通讯。PC上的键盘在某些方面进行了扩展,包括了功能键、光标定位键和(通常都带有的)单独的数字键盘,但它们的输入原理基本相同。

键盘基础

  Windows程序获得键盘输入的方式:键盘输入以消息的形式传递给程序的窗口过程。实际上,第一次学习消息时,键盘就是一个明显的例子:消息应该传递给应用程序的信息类型。
  Windows用8种不同的消息来传递不同的键盘事件。这好像太多了,但是(就像我们所看到的一样)程序可以忽略其中至少一半的消息而不会有任何问题。并且,在大多数情况下,这些消息中包含的键盘信息会多于程序所需要的。处理键盘的部分工作就是识别出哪些消息是重要的,哪些是不重要的。
一、键盘基础知识
  虽然应用程序在很多情况下可以通过鼠标实现信息的输入,但到现在为止键盘仍然是PC机中不可替代的重要输入设备。
  用键盘当作输入设备,每当用户按下或释放某一个键时,会产生一个中断,该中断激活键盘驱动程序KEYBOARD.DRV来对键盘中断进行处理。KEYBOARD.DRV程序会根据用户的不同操作进行编码,然后调用Windows用户模块USER.EXE生成键盘消息,并将该消息发送到消息队列中等候处理。
1.扫描码和虚拟码
  扫描码对应着键盘上的不同键,每一个键被按下或释放时,都会产生一个唯一的扫描码作为本身的标识。扫描码依赖于具体的硬件设备,即当相同的键被 按下或释放时,在不同的机器上可能产生不同的扫描码。在程序中通常使用由Windows系统定义的与具体设备无关的虚拟码。在击键产生扫描码的同时,键盘驱动程序KEYBOARD.DRV截取键的扫描码,然后将其翻译成对应的虚拟码,再将扫描码和虚拟码一齐编码形成键盘消息。所以,最后发送到消息队列的键盘消息中,既包含了扫描码又包含了虚拟码。
  经常使用的虚拟码在WINDOWS.H文件中定义,常用虚拟码的数值、常量符号和含义如表所示。

取值(16进制)
常量符号
含义
01
VK_LBUTTON
鼠标左键
02
VK_RBUTTON
鼠标右键
03
VK_CANCEL
Break中断键
04
VK_MBUTTON
鼠标中键
05-07
--
未定义
08
VK_BACK
(BackSpace)键
09
VK_TAB
Tab键
0A-0B
--
未定义
0C
VK_CLEAR
Clear键
0D
VK_RETURN
Enter键
0E-0F
--
未定义
10
VK_SHIFT
Shift键
11
VK_CONTROL
Ctrl键
12
VK_MENU
Alt键
13
VK_PAUSE
Pause键
14
VK_CAPTIAL
CapsLock键
15-19
--
汉字系统保留
1A
--
未定义
1B
VK_ESCAPE
Esc键
1C-1F
--
汉字系统保留
20
VK_SPACE
空格键
21
VK_PRIOR
PageUp键
22
VK_NEXT
PageDown键
23
VK_END
End键
24
VK_HOME
Home键
25
VK_LEFT
←(Left Arrow)键
26
VK_UP
↑(Up Arrow)键
27
VK_RIGHT
→(Right Arrow)键
28
VK_DOWN
↓(Down Arrow)键
29
VK_SELECT
Select键
2A
--
OEM保留
2B
VK_EXECUTE
Execute键
2C
VK_SNAPSHOT
Print Screen键
2D
VK_INSERT
Insert键
2E
VK_DELETE
Delete键
2F
VK_HELP
Help键
30-39
VK_0-VK_9
数字键0-9
3A-40
--
未定义
41-5A
VK_A-VK_Z
字母键A-Z
5B-5F
--
未定义
60-69
VK_NUMPAD0-VK_NUMPAD9
小键盘数字键0-9
6A
VK_MULTIPLY
*(乘号)键
6B
VK_ADD
+(加号)键
6C
VK_SEPAPATOR
分隔符键
6E
VK_SUBTRACT
-(减号)键
6F
VK_DECIMAL
.(小数点)键
70-87
VK_DIVIDE
/(除号)键
88-8F
VK_F1-VK_F24
F1-F24功能键
90
VK_NUMBERLOCK
Number lock键
91
VK_SCROLL
Scroll lock键
92-B9
--
未定义
BA-C0
--
OEM保留
C1-DA
--
未定义
DB_E4
--
OEM保留
E5
--
未定义
E6
--
OEM保留
E7-E8
--
未定义
E9-F5
--
OEM保留
F6-FE
--
未定义


2.输入焦点
  同一时刻,Windows中可能有多个不同的程序在运行,也就是说有多个窗口同时存在。这时,键盘由多个窗口共享,但只有一个窗口能够接收到键盘消息,这个能够接收键盘消息的窗口被称为拥有输入焦点的窗口。
  拥有输入焦点的窗口应该是当前的活动窗口,或者是活动窗口的子窗口,其标题和边框会以高亮度显示,以区别于其他窗口。拥有输入焦点的也可以是图标而不是窗口,此时,Windows也将消息发送给图标,只是消息的格式略有不同。
  窗口过程可以通过发送WM_SETFOCUS和 WM_KILLFOCUS消息使窗体获得或失去输入焦点。程序也可以通过捕获WM_SETFOCUS和WM_KILLFOCUS消息来判断窗体何时获得或失去输入焦点。其中WM_SETFOCUS消息表示窗口正获得输入焦点,WM_ KILLFOCUS消息表示窗口正失去输入焦点。
3.键盘消息
  键盘消息分为系统键消息和非系统键消息。系统键消息是指由Aft键和其他键组合而产生的按键消息。当系统键被按下时产生WM_ SYSKEYDOWN消息,当系统键被释放时产生WM_SYSKEYUP消息。 Aft键与其他键形成的组合键通常用于对程序菜单和系统菜单进行选择,或用于在不同的程序之间进行切换。因此,系统键消息应该交由Windows进行处理,用户所编制的程序一般不处理系统键消息,而是将这些消息交由DefWindowProc函数进行处理。如果用户想对系统键消息进行处理,应该在处理完这些消息后,再将其发送给DefWindowProc函数,使得Windows系统能够正常工作。
  某些击键消息可以被转换成字符消息,例如字母键、数字键等。而有些键只能产生按键消息而没有字符消息,例如 Shift键、Insert键等。消息循环中的 TranslateMessage函数可以实现从击键消息向字符消息的转化。当GetMessage函数捕获一个WM_SYSKEYDOWN消息或WM_KEYDOWN消息后,TranslateMessage函数判断产生该消息的键是否能够被转换成字符消息,如果能,就将该消息转换成字符消息,再通过DispatchMessape函数将转换后的字符消息发送到消息队列中去。字符消息共有以下四种,如表所示。

字符
系统字符
非系统字符
普通字符
WM_SYSCHAR
WM_CHAR
死字符
WM_SYSDEADCHAR
WM_DEADCHAR

  其中死字符是由某些特殊键盘上的按键所造成的,Windows一般忽略死字符所产生的消息。
  Windows的消息一般是通过一个MSG结构体变量传送给消息处理函数的。对于键盘消息, MSG结构体变量的各个域中较重要的是lParam域和 wParam域。wParam域用于保存按键的虚拟键代码或字符的ASCII码。对于非字符消息,wParam域保存按键的虚拟健代码;对于字符消息,wParam域不保存字符的ASCII码。lParam域则用于保存击键时产生的附加信息,实际上一个32位的lParam变量被分为六部分,记录了以下相关信息:重复次数、OEM扫描码、扩展键标志、关联键标志、前一击键状态和转换状态。lParam域各位的含义如表所示。

位数
含义
0-15
击键重复次数累加
16-23
OEM扫描码
24
是否为扩展键
25-28
未定义
29
是否便用关联键,及Alt键是否同时按下。
30
前一次击键状态,0表示该键前一次状态为抬起,1表示前一次状态为按下
31
转换状态

  按键的次序不同,产生的消息也不相同。例如,按下并释放1键,读过程依次产生如表所示三条消息。按下1键所产生的消息和wParam的取值

消息
wParam变量取值
WM_KEYDOWN
虚拟码1
WM_CHAR
ASCII码“1”
WM_KEYUP
虚拟码1

  如果按下Shift键后再按下1键并释放,则依次产生如表所示的消息。按下 Shift键后按 1健所产生的消息和 wParam的取值

消息
wParam变量取值
WM_KEYDOWN
虚拟码 VK_SHIFT
WM_KEYDOWN
虚拟码 VK_1
WM_CHAR
ASCII码 “1”
WM_KEYUP
虚拟码 VK_1
WM_KEYUP
虚拟码 VK_SHIFT

二、键盘应用实例
  下面通过一个应用程序实例来说明在实际编程中如何处理键盘消息。
#include <windows.h>
#include <stdio.h>
// 全局变量
RECT rc; //记录滚屏的矩形区域

int xChar, yChar; //文本输入点坐标 

WNDCLASSEX wnd; //窗口类结构变量 

char szAppName[] = "键盘消息监视程序"; //窗口类名
//函数声明
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE hInstance,int iCmdShow);
//函数:WinMain
//作用:入口函数
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR szCmdLine,int iCmdShow) 
{
  MSG msg; 
  if(!MyRegisterClass(hInstance)) 
  { 
    return FALSE; 
  }
  
   if(!InitInstance(hInstance,iCmdShow)) 
  { 
    return FALSE; 
  } 
  
  while (GetMessage (&msg, NULL, 0, 0)) 
  { 
    TranslateMessage (&msg); 
    DispatchMessage (&msg); 
  } 
  return msg.wParam; 
}
//函数:ShowKey 
//作用:实现在窗口中显示按键信息 
void ShowKey (HWND hwnd, int iType,char *szMessage,WPARAM wParam,LPARAM lParam) 
{
  static char *szFormat[2] ={"%-14s %3d %c %6u %4d %5s %5s %6s %6s",
                "%-14s %3d %c %6u %4d %5s %5s %6s %6s" }; 
  char szBuffer[80]; 
  HDC hdc; 
  ScrollWindowEx(hwnd, 0, -yChar, &rc,&rc,NULL,NULL,SW_INVALIDATE); 
  hdc = GetDC (hwnd); 
  SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)); 
  TextOut (hdc,
       xChar,
       rc.bottom - yChar,
       szBuffer,
       wsprintf szBuffer,
       szFormat[iType],
       szMessage, //消息
       wParam, //虚拟键代码
       (BYTE) (iType ? wParam :‘ ’),//显示字符值
       LOWORD (lParam), // 重复次数
       HIWORD (lParam) & 0xFF, // OEM键盘扫描码
       //判断是否为增强键盘的扩展键
       (PSTR) (0x01000000 & lParam ? “是” : “否”),
       //判断是否同时使用了ALT键
       (PSTR) (0x20000000 & lParam ? “是” : “否”),
       (PSTR) (0x40000000 & lParam ? “按下” : “抬”),
       //判断前一次击键状
       (PSTR)(0x80000000 & lParam ? “按下” : “抬起”))
       //判断转换状态?
       ); 
  ReleaseDC (hwnd, hdc); ?
  ValidateRect (hwnd, NULL); ?

//函数:WndProc 
//作用:处理主窗口的消息 
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) 

  static char szTop[] ="消息键 字符 重复数 扫描码 扩展码 ALT 前一状态 转换状态"; 
  static char szUnd[] ="_______ __ ____ _____ ______ ______ ___ _______ ______";

  //在窗口中输出文字作为信息标题 
  HDC hdc; 
  PAINTSTRUCT ps; 
  TEXTMETRIC tm; 

  switch (iMsg) 
  { 
    case WM_CREATE://处理窗口创建的消息 
    hdc = GetDC (hwnd); //设定字体 
    SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)); //检取当前字体的度量数据 
    GetTextMetrics (hdc, &tm); 
    xChar = tm.tmAveCharWidth;//保存字体平均宽度 
    yChar = tm.tmHeight; //保存字体高度 
    ReleaseDC (hwnd, hdc); 
    rc.top = 3 * yChar / 2; 
    return 0;

    case WM_SIZE://处理窗口大小改变的消息 
    //窗体改变后保存新的滚屏区域右下角坐标 
    rc.right = LOWORD (lParam); 
    rc.bottom = HIWORD (lParam); 
    UpdateWindow (hwnd); 
    return 0; 

    case WM_PAINT: //处理窗口重绘消息 
    InvalidateRect (hwnd, NULL, TRUE); 
    hdc = BeginPaint (hwnd, &ps); 
    SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; 
    SetBkMode (hdc, TRANSPARENT) ; 
    TextOut (hdc, xChar, yChar / 2, szTop, (sizeof szTop) - 1) ; 
    TextOut (hdc, xChar, yChar / 2, szUnd, (sizeof szUnd) - 1) ; 
    EndPaint (hwnd, &ps); 
    return 0;

    case WM_KEYDOWN:
    //处理键盘上某一键按下的消息 
    ShowKey (hwnd, 0, "WM_KEYDOWN",wParam, lParam); 
    return 0; 

    case WM_KEYUP:
    //处理键盘上某一按下键被释放的消息 
    ShowKey (hwnd, 0, "WM_KEYUP", wParam, lParam); 
    return 0; 

    case WM_CHAR:
    //处理击键过程中产生的非系统键的可见字符消息 
    howKey (hwnd, 1, "WM_CHAR", wParam, lParam); 
    return 0; 

    case WM_DEADCHAR:
    //处理击键过程中产生的非系统键"死字符"消息 
    ShowKey (hwnd, 1, "WM_DEADCHAR", wParam, lParam); 
    return 0; 

    case WM_SYSKEYDOWN:
    //处理系统键按下的消息 
    ShowKey (hwnd, 0, "WM_SYSKEYDOWN",wParam, lParam); 
    break; 

    case WM_SYSKEYUP:
    //处理系统键抬起的消息 
    ShowKey (hwnd, 0, "WM_SYSKEYUP", wParam, lParam); 
    break; 

    case WM_SYSCHAR://处理系统键可见字符消息 
    ShowKey (hwnd, 1, "WM_SYSCHAR", wParam, lParam); 
    break; 

    case WM_SYSDEADCHAR://处理系统键"死字符"消息 
    ShowKey (hwnd, 1, "WM_SYSDEADCHAR", wParam, lParam); 
    break;

    case WM_DESTROY:
    //处理结束应用程序的消息 
    PostQuitMessage (0); 
    return 0; 
  } 
  return DefWindowProc (hwnd, iMsg, wParam, lParam); 

//函数:MyRegisterClass 
//作用:注册窗口类 
BOOL MyRegisterClass(HINSTANCE hInstance) 

  wnd.cbSize= sizeof (wnd); 
  wnd.style = CS_HREDRAW | CS_VREDRAW; 
  wnd.lpfnWndProc = WndProc; 
  wnd.cbClsExtra = 0; 
  wnd.cbWndExtra = 0; 
  wnd.hInstance = hInstance; 
  wnd.hIcon = LoadIcon (NULL, IDI_APPLICATION);?
  wnd.hCursor = LoadCursor (NULL, IDC_ARROW); 
  wnd.hbrBackground = (HBRUSH)
  GetStockObject (WHITE_BRUSH); 
  wnd.lpszMenuName = NULL; 
  wnd.lpszClassName = szAppName; 
  wnd.hIconSm = LoadIcon (NULL, IDI_APPLICATION); 
  return RegisterClassEx (&wnd); 
}
//函数:InitInstance 
//作用:创建主窗口 
BOOL InitInstance(HINSTANCE hInstance,int iCmdShow) 

  HWND hwnd; 
  hwnd = CreateWindow (szAppName,
             "键盘消息监视程序", 
             WS_OVERLAPPEDWINDOW, 
             CW_USEDEFAULT,CW_USEDEFAULT, 
             CW_USEDEFAULT,CW_USEDEFAULT, 
             NULL,NULL,hInstance,NULL 
             ); 
  if(!hwnd)
  {
    return FALSE;
  }

  ShowWindow (hwnd, iCmdShow);
  UpdateWindow (hwnd);
  return TRUE;
}

  本实例的作用是通过程序捕获键盘消息,然后将wParam参数所包含的数据进行分解,最后将各项信息通过窗口显示出来。实例的源文件包含了Initlnstance、MyRegisterClass、ShowKey、WinMain和WndProc五个函数。程序的基本思路是以WinMain函数作为程序入口,再调用 MyRegisterClass函数和 InitInstance函数注册窗口类并创建和保存窗日,然后创建和显示窗口,最后进入消息循环。
  下面重点分析函数WndProc和 ShowKey。
1.WndProc函数
在本实例中WndProc函数处理的消息主要有WM_CREATE、WM_SIZE、WM_PAINT和键盘消息。
  case WM_CREATE://处理窗口创建的消息 
  hdc = GetDC (hwnd);//设定字体 
  SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT));//检取当前字体的度量数据 
  GetTextMetrics (hdc, &tm); 
  xChar = tm.tmAveCharWidth;//保存字体平均宽度 
  yChar = tm.tmHeight;//保存字体高度 
  ReleaseDC (hwnd, hdc); 
  rc.top = 3 * yChar / 2; 
  return 0;
  这一程序段的主要作用是将字体对象选入当前窗体的设备描述表中,同时取得字体高度和平均宽度,再初始化编辑区的滚屏区域的右上角Y坐标。进入该程序段后,首先通过GetDC函数获得当前窗体的设备描述表,再通过GetStockObject函数获得系统字体,然后用 SelectObject函数将字体对家选入窗体的设备描述表中。其中,hdc为设备描述表句柄。在完成所有操作后,程序还必须通过ReleaseDC函数释放设备描述表。在该程序段中使用了GetTextMetrics函数来获得字体的几何尺寸。GetTextMetrics函效的原型定义如下:
BOOL GetTextMetrics(HDC hdc,// 指向设备描述表的句柄
          LPTEXTMETRIC lptm // TEXTMETRIC结构体变量的指针
          // 所获得的所有信息保存在TEXTMETRIC结构体变量中 
          );
  其中lptm是一个指向 TEXTMETRIC结构体的指针。TEXTMETRIC结构体包含了与字体的几何尺寸相关的基本信息。该结构体的具体定义如下:
typedef struct tagTEXTMETRIC
{ // tm
  LONG tmHeight;// 字体高度
  LONG tmAscent;//字体高于基准线的高度
  LONG tmDescent;// 字体低于基准线的高度
  LONG tmInternalLeading;// 给大写字母留出的空间
  LONG tmExtenalLeading; // 由字体设计者推荐的附加行距
  LONG tmAveCharWidth;// 字体平均宽度
  LONG tmMaxCharWidth;// 字体最大宽度
  LONG tmWeight; // 字体黑度
  LONG tmOverhang; // 在合成斜体或黑体时加在字符上的附加宽度值
  LONG tmDigitizedAspectX;// 字体所适合的高宽比的宽
  LONG tmDigitizedAspectY; // 字体所适合的高宽比的高
  BCHAR tmFirstChar; // 字体中定义的第一个字符
  BCHAR tmLastChar; //字体中定义的最后一个字符
  BCHAR trnDefaultChar; //字体中的默认字符
  BCHAR trnBreakChar; // windows在调整文本时用于分裂词的字符
  BYTE tmItalic; // 取非零值时表示斜体字体
  BYTE tmUnderLined; // 取非零值时表示下划线字体
  BYTE tmStruckOut;// 取非零值时为删除线字体
  BYTE tmPitchAndFamily; // 低二位为字符间距,高四位为系列值
  BYTE tmCharSet; // 指定字符集
} TEXTMETRIC; 
  该结构中所有的字体大小都是按逻辑单位给出的,这就是说字体的大小取决于当前显示设备的映射模式。
  在例中,所获得的字体几何尺寸保存在TEXTMETRIC结构体变量tm中。滚屏区域的范围是通过RECT结构体变量re保存的,RECT结构体变量可以通过记录矩形区域的右上角和左下角的坐标来确定一个矩形区域。
RECT结构的原型定义如下:
typedef struc RECT{
  LONG left; // 矩形左上角 X坐标
  LONG top; // 左上角 Y坐标
  LONG right; // 右下角 X坐标
  LONG bottom; // 右下角Y坐标
} RECT;
  该结构定义了一个矩形区域的左上角和右下角的坐标。由结构的原型定义我们可以知道该结构包括四个域,其中left域表示矩形的左上角X坐标,top域表示左上角Y坐标,right域表示右下角X坐标,bottom域表示右下角Y坐标。通常用于一个矩形区域范围的记录和传递。
  例如,通过RECT结构的变量将一个矩形区域范围的四个角的值传递FillRect函数,则调用该函数后,矩形区域除了最下方的一行和最右方一列外都被填充。在本实例中,初始化编辑区的滚屏区域的左上角Y坐标时,使用了如下程序:
  rc.top= 3 * yChar/2;
  这是因为在窗口中首先要输出两行的题头信息,一行为中文,一行为下划线。中文字符的高度为1个字体高度单位,而下划线的高度为半个字体高度单位。这两行信息是一直保持,不参与滚屏的。因此,滚屏区域的左上角Y坐标从3/2个字体高度处开始。
在WndProc函数中,处理WM_ SIZE
消息的程序段如下: 
  case WM_SIZE: //处理窗口大小改变的消息
  //窗体改变后保存新的滚屏区域右下角坐标
  rc.right = LOWORD (lParam);
  rc.bottom = HIWORD (lParam);
  UpdateWindow (hwnd);
  return 0;
  该程序段比较简单,只是当窗口的尺寸改变时重新设定滚屏区域的右下角坐标,并更新窗口。值得注意的是, WM_SIZE消息的wParam变量保存了窗体新尺寸的左上角坐标,变量的32位分为两个部分,低16位保存X坐标,高16位保存Y坐标。 lParam变量保存了窗体新尺寸的右下角坐标,保存方式与wParam变量相同。在编程过程中,通常通过LOWORD宏定义来获得32位变量的低16位数值,通过HIWORD宏定义来获得32位变量的高历位数值。
  该程序段比较简单,只是当窗口的尺寸改变时重新设定滚屏区域的右下角坐标,并更新窗口。值得注意的是,WM_SIZE消息的wParam变量保存了窗体新尺寸的左上角坐标,变量的32位分为两个部分,低16位保存X坐标,高16位保存Y坐标。 lParam变量保存了窗体新尺寸的右下角坐标,保存方式与wParam变量相同。在编程过程中,通常通过LOWORD宏定义来获得32位变量的低16位数值,通过HIWORD宏定义来获得32位变量的高历位数值。
WndProc函数中,处理WM_PAINT消息的程序段如下:
  case WM_PAINT: //处理窗口重绘消息 ?
  InvalidateRect (hwnd, NULL, TRUE); ?
  hdc = BeginPaint (hwnd, &ps); ?
  SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; ?
  SetBkMode (hdc, TRANSPARENT) ; ?
  TextOut (hdc, xChar, yChar / 2, szTop, (sizeof szTop) - 1) ; ?
  TextOut (hdc, xChar, yChar / 2, szUnd, (sizeof szUnd) - 1) ; ?
  EndPaint (hwnd, &ps); ?
  return 0; 
  该程序段首先调用InvalidateRect函数使窗口无效,InvalidateRect函数的功能是使窗口的某一部分无效,也就是通知Windows该部分需要被刷新和重画。 
  在InvalidateRect函数之后,程序调用函数BeginPaint准备重画窗口。
BeginPaint 函数的原型定义如下:
HDC BeginPaint (HWND hwnd , // 重画窗口的句柄
        LPPAINTSTRUCT lpPaint // 指向一个用于保存所有重
        // 画信息的 PAINTSTRUCT 结构体变量的指针);
  BeginPaint 函数的作用是完成重画窗体之前的准备,并将重画窗体 的数据保存在一个 PAINTSTRUCT 结构体变量中。 PAINTSTRUCT 结构体可以用于保存窗口重画时的数据以方便以后使用。
PAINTSTRUCT结构体的定义如下:
typedef struct tagPAINTSTRUCT{ // ps
  HDC hdc; // 重画区域所在窗口的句柄
  BOOL fErase;// 是否擦去背景
  RECT rcPaint; // 指定重画窗体的范围
  BOOL fRestore; // 系统保留域
  BOOL fIncUpdate;// 系统保留域
  BYTE rgbReserved[32];// 系统保留
}PA INTSTRU CT;
  BeginPaint函数如果操作成功会返回一个被操作窗口的设备描述表的句柄。如果操作不成功则函数返回NULL值,表明显示设备不可用。该函数在运行过程中,会进行自动调整,使得所有区域都包含在刷新区域的范围内。而原有需要刷新的区域是由InvalidateRect函数或 InvalidateRgn函数指定的。一般来说,只有当程序处理 WM_PAINT消息时才调用BeginPaint函数,而且,每次调用BeginPaint函数都需要对应调用一个EndPaint函数来结束重画过程。在BeginPaint函数调用后,会将插入符光标自动隐藏。EndPaint函数原型定义如下:
BOOL EndPaint ( HWND hWnd, // 窗口句柄
        CONST PAINTSTRUCT* lpPaint // 指向 PAINTSTRUCT结构体变量的指针
       );
  EndPaint函数标志着窗口重画过程的结束。该函数执行后总返回一个非零值。如果在BeginPaint函数执行时将插入符号隐藏了,那么EndPaint函数会重新显示插入符号。
消息处理函数 WndProc处理的键盘消息有:
WM_ KEYDOWN、WM_KEYUP
WM_CHAR、WM_DEADCHAR、
WM_SYSKEYDOWN、WM_SYSKEYUP、
WM_SYSCHAR 和 WM_SYSDEADCHAR。
  根据不同的消息,程序会用不同的参数调用 ShowKey函数在窗口中显示各键盘消息的相关信息。
2.ShowKey函数
ShowKey函数是用户自定义函数,其作用是从键盘消息的各域中提取信息并显示在窗口中。
ShowKey函数的具体定义如下:
// 作用:实现在窗口中显示按键信息 
void ShowKey (HWND hwnd, int iType, char *szMessage,WPARAM wParam, LPARAM lParam) 

  static char *szFormat[2] = {"%-14s %3d %c %6u %4d %5s %5s %6s %6s", 
                "%-14s %3d %c %6u %4d %5s %5s %6s %6s" } ; 
  char szBuffer[80]; 
  HDC hdc; 
  SelectObject( hdc,
         GetStockObject(SYSTEM_FIXED_FONT));
         TextOut (hdc, xChar, rc.bottom - yChar,
         szBuffer,wsprintf (szBuffer, szFormat [iType],
         szMessage, //消息
         wParam, //虚拟键代码
         (BYTE) (iType ? wParam : ' '),//显示字符值
         LOWORD (lParam), //重复次数
         HIWORD (lParam) & 0xFF, //OEM键盘扫描码
         //判断是否为增强键盘的扩展键
         (PSTR) (0x01000000 & lParam ? "是" : "否"),
         //判断是否同时使用了ALT键
         (PSTR) (0x20000000 & lParam ? "是" : "否"),
         (PSTR) (0x40000000 & lParam ? "按下" : "抬起"),
         //判断前一次击键状态
         (PSTR) (0x80000000 & lParam ? "按下" : "抬起"))
         //判断转换状态
         );
}

  ShowKey函数首先定义了szFormat字符串,并在其中针对字符消息和非字符消息定义了两种不同的输出格式。 然后调用ScrollWindowEx函数使显示区域滚屏,为信息输出作准备。ScrollWindowEx函数的主要功能是使窗口编辑区中的某一矩形区域产生滚屏效果。
ScrollWindowEx函数的原型定义如下:
int ScrollWindowEx (HWND hwnd, // 发生滚屏的窗口的句柄
          int dx, // 水平滚屏的数值
          int dy, // 垂直滚屏的数值
          CONST RECT*prcScroll,//记录发生滚屏的矩形区域的RECT结构体的地址
          CONST RECT* prcClip, //记录发生剪切的矩形区域的 RECT结构体的地址
          HRGN hrgnUpdate,// 需要更新区域的句柄
          LPRECT prcUpdate, // 记录需要更新矩形区域的 RECT结构体的地址
          UINT flags // 滚屏控制标志
          );
  其中,dx参数给出了以设备单位尺寸(对于显示器为像素)为单位的每一次水平滚屏的度量值。dx参数取正值表示向右滚屏,取负值表示向左滚屏。如参数给出了以设备单位尺寸(对于显示器为像素)为单位的每一次垂直滚屏的度量值。如参数取正值表示向下滚屏,取负值表示向上滚屏。dx和dy两个参数不能同时取非零值,也就是说,ScrollWindowEx函数不能使编辑区同时向水平和垂直方向滚屏。
  prcScroll参数为一个指向记录滚屏的矩形区域的RECT结构体变量的指针,如果取值为NULL,则整个编辑区发生滚屏。
  hrgnUpdate参数为因滚屏而变得无效的矩形区域的句柄,多数情况下可以取NULL。 prcUpdate参数指向一个记录因为滚屏而变得无效的矩形区域的 RECT结构体变量。多数情况下取NULL。
flags变量可以通过不同的取值来控制滚屏的状况,其取值和意义如下所示。
  SW_ ERASE当和 SW_INVALIDATE值同时使用时,会通过向 window发送一个WM_ ERASEBKGND消息将最近变得无效的区域抹去;
  SW_INVALIDATE在发生滚屏后使由hrgnUpdate参数指定的区域无效;
  SW_SCROLLCHILDREN使所有的子窗口都发生滚屏;
  SW_ SMOOTHSCROLL在 Windows 95及以后的版本中使窗口发生平滑滚屏。如果ScrollWindowEx函数执行成功,则返回值为以下三者之一:
  SIMPLEREGION表示有一个矩形的无效区域;
  COMPLEXREGION表示没有无效区域和重叠区域;
  NULLREGION表示没有无效区域。
  如果ScrollWindowEx函数执行不成功,则返回ERROR。
ScrollWindowEx函数的功能也可以通过ScrollWindow函数来实现,ScrollWindow 函数的原型定义如下:
BOOL Scrollwindow(HWND hwnd //窗口句柄
         int XAmount, // 水平滚屏的数值
         int YAmount, // 垂直滚屏的数值
         CONST RECT* lpReCt, //记录发生滚屏的矩形区域的 RECT结构体的地址
         CONST RECT* lpClipRect, //记录发生剪切的矩形区域的 RECT结构体的地址 
         );
  可以看出,ScrollWindow函数与ScrollWindowEx函数十分相似,其参数的意义也基本相同。事实上,ScrollWindow函数是为了保持对较低版本的Windows兼容而设计的,用户在编程时,除非需要考虑程序的向下兼容,否则一般都应使用ScrollWindowEx函数。
  在滚屏后,函数开始调用TextOut函数进行信息输出。TextOut函数的原型定义如下:
BOOL TextOut( HDC hdc,// 设备描述表句柄
       int nXStart, // 文本输出起始点 X坐标
       int nYStart, // 文本输出起始点 Y坐标
       LPCTSTR lpString, // 指向输出字符串的指针
       int cbString // 字符串中字符的数目
       );
  TextOut函数能够用当前设定的字体在窗口的指定部位输出一段文本信息。如果操作成功则返回一非零值,否则返回零值。捕获键盘消息的信息主要根据表中的描述,通过使用按位操作确定某些特定位的值,然后再判断具体的状态。
  在TextOut函数调用过程中,还调用了wsprintf函数,并使其返回值作为TextOut函数的一个参数值。wsprintf函数的原型定义如下:
int wsprintf (LPTSTR lpOut,// 指向需要输出的字符串的指针
       LPCTSTR lpFmt, //指向格式控制字符串的指针
       …… // 其他可选参数
       );
  wsprintf函数能够将一组字符序列按lpFmt参数指定的格式转换,然后保存在lpOut参数指定的字符缓冲区中等待输出。其中,字符序列由可选参数决定,而可选参数的数目和具体内容应该与lpFmt所指定的格式一致。
  如果wsprintf函数操作成功,则返回输出字符的数目,但这个字符数目不包括表示结束的NULL标志。如果操作失败,返回的整数值将与输出的字符数目不相符。
  实例主要说明了如何处理键盘消息,读者应该着重理解各种信息在MSG结构体变量中是如何保存的,怎样才能够对其中的具体信息进行识别和提取。程序运行后将产生一个背景色为灰色的简单窗口,并在窗口的顶部出现标题提示信息。这时用户如果进行键盘操作,则窗体中便会显示该操作所产生的键盘消息,每显示一条消息程序都会滚屏和重绘窗口,滚屏区域的颜色为白色。

键盘消息实例2:
#include <windows.h>
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int);
LRESULT CALLBACK WndProc( HWND,UINT, WPARAM,LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  WNDCLASSEX wcex;
  wcex.cbSize = sizeof(WNDCLASSEX);
  wcex.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
  wcex.lpfnWndProc = (WNDPROC)WndProc;
  wcex.cbClsExtra = 0;
  wcex.cbWndExtra = 0;
  wcex.hInstance = hInstance;
  wcex.hIcon= LoadIcon(NULL, (LPCTSTR)IDI_APPLICATION);
  wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
  wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  wcex.lpszMenuName = NULL;
  wcex.lpszClassName = "SeeKeyMessage";
  wcex.hIconSm = LoadIcon(NULL,(LPCTSTR)IDI_APPLICATION);

  if(!RegisterClassEx(&wcex)) return FALSE;

  int SW_XFS = GetSystemMetrics(SM_CXSCREEN);
  int SW_YFS = GetSystemMetrics(SM_CYSCREEN);
  HWND hWnd;
  hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,
             "SeeKeyMessage",
             "Trace Key Operation",
             WS_OVERLAPPEDWINDOW,
             0, 0, SW_XFS, SW_YFS-25,
             NULL, 
             NULL,
             hInstance,
             NULL);
  if(!hWnd) return FALSE;

  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);
  MSG msg;

  while(GetMessage(&msg, NULL, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  HDC hDC;
  PAINTSTRUCT ps;
  static char Buffer[256];
  switch(message)
  {
    case WM_KEYDOWN:
    hDC = GetDC(hWnd); 
    wsprintf(Buffer," ");
    TextOut(hDC,20,40,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_KEYDOWN %3d %3d%3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,40,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC);
    break;

    case WM_KEYUP:
    hDC = GetDC(hWnd); 
    wsprintf(Buffer," ");
    TextOut(hDC,20,60,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_KEYUP %3d %3d %3d",wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,60,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC); 
    break;

    case WM_PAINT:
    hDC = BeginPaint(hWnd,&ps);
    wsprintf(Buffer," ");
    TextOut(hDC,20,20,Buffer,strlen(Buffer));
    wsprintf(Buffer," Message wParam LOWORD(lParam) HIWORD(lParam)");
    TextOut(hDC,20,20,Buffer,strlen(Buffer));
    EndPaint(hWnd,&ps); 
    break;

    case WM_DESTROY:
    PostQuitMessage(0);
    break;

    default:
    return DefWindowProc(hWnd,message,wParam,lParam);
  }
  return 0;
}

鼠标消息

  随着 Windows 操作系统的流行,鼠标因为其精确定位和操作方便的优点而成为计算机不可缺少的输入设备。
一、鼠标的基础知识
  本节将介绍在程序中用鼠标作为输入设备的方法和技巧。1 .鼠标操作和鼠标消息用户在使用鼠标操作的过程中,经常会使用的主要方式有五种 ,如表所示。

操作名称
描述
单击(Click)
按下并迅速释放鼠标按钮。
双击(Double Click)
连续快速完成两次单击操作。
移动(Move)
鼠标光标移动。
拖动(Drag)
按下鼠标一键不放,同时执行鼠标移动操作。
与键盘的特殊键组合
在按下Ctrl键或Shift键的同时执行鼠标单击操作。

  其中,前三种操作是最为基本的操作,可以产生Windows内部定义的消息,并通过这些消息来判断用户具体执行了哪种操作。
  Windows定义的鼠标消息共有20条,其中非编辑区的鼠标消息一般交由系统处理,程序只处理编辑区内的鼠标消息。编辑区内的鼠标消息共有10条,如表所示。

消息常量
操作描述
WM_MOUSEMOVE
移动鼠标
WM_LVBUTTONDOWN
按下鼠标左键
WM_LBUTTONUP
释放鼠标左键
WM_LBUTTONDBLCLK
双击鼠标左键
WM_RVBUTTONDBLCLK
按下鼠标右键
WM_RBUTTONUP
释放鼠标右键
WM_RBUTTONDBLCLK
双击鼠标右键
WM_MVBUTTONDOWM
按下鼠标中键
WM_MBUTTONUP
释放鼠标中键
WM_MBUTTONDBLCLK
双击鼠标中键

  对于前表所列的鼠标操作中的最后两种,不能直接使用Windows定义的消息来判断,只能通过编程,将多种消息和数据组合之后判断。例如,判断用户是否按下鼠标左键之后进行拖动操作可以通过以下程序段来实现,用case语句来实现: 
case WM_MOUSEMOVE:

if (wParam&MK_LBUTTON) //只处理鼠标拖动的消息
{ …… // 处理程序


   在处理鼠标消息的过程中,消息的wParam参数和lParam参数起了重要的作用。wParam参数中保存了在消息产生时其他操作进行的状态;用户可以通过位屏蔽操作来判断在该消息产生的同时,其余操作是否正在进行。这正是在程序中判断复杂鼠标操作的基本方法。例如,上面判断拖动操作的程序段就用了位操作 wParam& MK_LBUTTON, 判断在鼠标移动(WM_MOUSEMOVE)的同时鼠标左键是否同时被接下。如果,鼠标左键同时按下,则位操作的结果为TRUE,说明当前操作为拖动操作,程序可以继续进行下一步处理。又如需要判断单击鼠标左键时是否同时按下了Ctrl键或Shift键,可以用以下程序段来处理:
case WM_ LBUTTONDOWN:
if(wParam& MK_CTROL)
{//Ctrl键同时按下
  if (wParam&MK_ SHIFT)
  {// Ctrl 键和Shift键都同时按下
    …… // 处理程序
  }
  else { // Ctrl健同时按下,但 Shift键没有被按下
    ……. // 处理程序
  }
}
else if(wParam&MK_ SHIFT)
{ // Shift键同时按下,但 Ctrl键没有被接下
  …… // 处理程序

else
{// Shift 键和Ctrl键都未按下
  …… // 处理程序

  lParam参数保存了消息产生时鼠标所在点的坐标,其中低16位为X坐标,高16位为Y坐标。
  在处理鼠标消息的时候,如果需要处理鼠标双击消息,则在注册窗口类时,窗口的风格必须包括CS_DBCLCKS。否则即使执行了双击操作,窗口也只能收到两条WM_ BUTTONUP和 WM_BUTTONDOWN消息。区分双击操作和两次单击操作是以两次击键的时间间隔为标准的。当两次击键的时间间隔小于 500毫秒时, Windows将其视为双击操作:如果两次击键的时间间隔大于500毫秒,Windows将其视为两次单击操作。500毫秒为默认的时间间隔,用户可以通过调用SetDoubleClickTime函数来修改这一时间间隔。SetDoubleClickTime函数的原型定义如下:
BOOL SetDoubleClickTime(UINT uInterval // 新的击键时间间隔)
2.鼠标捕捉
  在通常情况下,只有当鼠标位于窗体内时,窗体才能接收到鼠标的消息。如果需要接收所有的鼠标消息而不论鼠标是否在窗口内,这时可以调用SetCapture函数来实现。SetCapture函数的原型定义如下:
HWND SetCapture (
  HWND hwnd // 窗口句柄
);
  调用SetCapture函数后,所有鼠标操作所产生的消息都直接发送到指定窗口。因为此时鼠标可能位于窗口之外,所以鼠标的坐标可能为负值。由于调用该函数会使其他窗口不能接收到键盘和鼠标的消息,因此在完成操作后应及时调用ReleaseCapture 函数释放鼠标捕获。ReleaseCapture函数的原型定义如下:
BOOL ReleaseCapture(VOID);
二、鼠标应用实例
下面是一个在程序设计中如何捕获鼠标消息的实例。
#include <windows.h> 
//全局变量 
WNDCLASSEX wnd; 
static char szAppName[] = "mouse";//窗口类名 
//函数声明 
long WINAPI WndProc (HWND, UINT, WPARAM, LPARAM); 
BOOL MyRegisterClass(HINSTANCE hInstance); 
BOOL InitInstance(HINSTANCE hInstance, int iCmdShow); 
//函数:WinMain 
//作用:入口函数 
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow) ?

  MSG msg; 
  if(!MyRegisterClass(hInstance)) 
  { 
    return FALSE; 
  } 

  if(!InitInstance(hInstance,iCmdShow)) 
  { 
    return FALSE; 
  } 

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

//函数:WndProc 
//作用:处理主窗口的消息 
long WINAPI WndProc (HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) 

  static POINT points[256];//保存点坐标 
  static POINT center;//保存中心点坐标 
  static int iCount;//点数目累加值 
  HDC hdc; 
  PAINTSTRUCT ps; 
  int i;//循环计数 
  RECT rect; 
  switch (msg) 
  { 
    case WM_MBUTTONDOWN:
    //处理鼠标中键按下的消息 
    iCount = 0;//重新初始化点数目 
    InvalidateRect (hwnd, NULL, TRUE);
    //通知系统重画窗口 
    hdc = BeginPaint (hwnd,&ps); 
    GetClientRect(hwnd,&rect); 
    if(wParam&MK_CONTROL)//判断Shift键和Ctrl键是否被按下 
    { 
      if(wParam&MK_SHIFT) 
      { //根据不同的情况给出不同的提示
        DrawText(hdc,"Ctrland Shift", -1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER?);
      } 
      else 
      {
        DrawText(hdc,"Ctrl Only" ,-1,&rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
      }
    }
    else if(wParam&MK_SHIFT)
    {
      DrawText(hdc,"Shift Only",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
    }
    else
    {
      DrawText(hdc,
           "Middle Button of mouse only",
           -1,
           &rect,
           DT_SINGLELINE|DT_CENTER|DT_VCENTER);
    }
    EndPaint(hWnd,&ps);
    return 0; 

    case WM_RBUTTONDOWN:
    //处理鼠标右键按下的消息 
    iCount = 0;//重新初始化点数目 
    center.x=LOWORD (lParam);
    //保存新的中心点坐标 
    center.y=HIWORD (lParam); 
    InvalidateRect (hwnd, NULL, TRUE);//通知系统重画窗口 
    return 0; 

    case WM_MOUSEMOVE://处理鼠标移动的消息 
    if (wParam & MK_LBUTTON && iCount < 256)//只处理鼠标拖动的消息 
    { 
      points[iCount].x = LOWORD (lParam);//保存点的X坐标 
      points[iCount++].y = HIWORD (lParam);//保存点的Y坐标 
      hdc = GetDC (hwnd);//获得窗口的设备描述表句柄 
      SetPixel (hdc, LOWORD (lParam), HIWORD (lParam), 0L);//绘点 
      ReleaseDC (hwnd, hdc);//释放设备描述表句柄 
    }?return 0; 

    case WM_LBUTTONUP:
    //处理鼠标左键抬起的消息 
    InvalidateRect (hwnd, NULL, FALSE);
    //通知系统重画窗口 
    return 0; 

    case WM_PAINT://处理窗口重画的消息 
    hdc = BeginPaint (hwnd, &ps);//获得设备描述表句柄 
    SetCursor (LoadCursor (NULL, IDC_WAIT));//设置新的鼠标光标 
    ShowCursor (TRUE);//显示鼠标光标 
    for (i = 0 ; i < iCount ; i++) 
    { 
      MoveToEx(hdc, center.x, center.y,NULL);//绘制直线 
      LineTo(hdc, points[i].x, points[i].y); 
    } 
    ShowCursor(FALSE);//隐藏鼠标 
    SetCursor(LoadCursor (NULL, IDC_ARROW));
    //恢复原来的鼠标光标 ?
    EndPaint(hwnd, &ps);
    return 0;

    case WM_DESTROY://处理销毁窗口的消息 
    PostQuitMessage (0); 
    return 0; 
  } 
  return DefWindowProc (hwnd, msg, wParam, lParam); 

//函数:MyRegisterClass 
//作用:注册窗口类 
BOOL MyRegisterClass(HINSTANCE hInstance) 

  wnd.cbSize= sizeof (wnd); 
  wnd.style= CS_HREDRAW | CS_VREDRAW; 
  wnd.lpfnWndProc = WndProc; 
  wnd.cbClsExtra = 0; 
  wnd.cbWndExtra = 0; 
  wnd.hInstance = hInstance; 
  wnd.hIcon = LoadIcon (NULL, IDI_APPLICATION); 
  wnd.hCursor = LoadCursor (NULL, IDC_ARROW); 
  wnd.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); 
  wnd.lpszMenuName= NULL; 
  wnd.lpszClassName = szAppName; 
  wnd.hIconSm = LoadIcon (NULL, IDI_APPLICATION); 
  return RegisterClassEx (&wnd); 

//函数:InitInstance 
//作用:创建窗口 
BOOL InitInstance(HINSTANCE hInstance, int iCmdShow) 
{
  HWND hwnd; 
  hwnd = CreateWindow(szAppName, 
            "跟踪鼠标移动", 
            WS_OVERLAPPEDWINDOW, 
            CW_USEDEFAULT, CW_USEDEFAULT, 
            CW_USEDEFAULT, CW_USEDEFAULT, 
            NULL, NULL, hInstance, NULL); 
  if(!hwnd) return FALSE; 
  ShowWindow (hwnd, iCmdShow); 
  UpdateWindow (hwnd);
  return TRUE; 
}
  例题的主要功能是在窗口中某个位置单击鼠标右键时,程序保存捕获的鼠标点处的坐标。紧接着按下鼠标左键在窗口中拖动,程序会记录下鼠标运动的轨迹,并以刚才右击鼠标时确定的点为中心绘制一簇射线。本实例最多可以绘制256条射线。程序的另一目的是为了让读者进一步了解如何捕获鼠标与Ctrl键或Shift键组合时的复杂鼠标消息。如果在窗口中单击鼠标中键,程序会在窗口中央显示文本信息说明用户是否同时按下Ctrl键和Shift键。
  源文件与本书前面所介绍的其他实例一样,都具有基本的 Windows API 程序的结构。即以WinMain函数作为程序入口,调用MyRegisterClass函数和InitInstance函数注册窗口类和创建窗口,再进入消息循环。并在消息循环中调用WndProc函数处理鼠标消息。下面主要介绍WndProc函数处理鼠标消息的方法和技巧。
  在例中WndProc函数能够处理的消息包括 WM_MBUTTONDOWN、WM_RBUTTONDOWN、WM_MOUSEMOVE、WM_LBUTTONUP和WM_PAINT。
处理WM_ MBUTTONDOWN消息的程序段如下:
case WM_MBUTTONDOWN:
//处理鼠标中键按下的消息 
iCount = 0;//重新初始化点数目 
InvalidateRect (hwnd, NULL, TRUE);//通知系统重画窗口 
hdc = BeginPaint (hwnd,&ps); 
GetClientRect(hwnd,&rect); 
if(wParam&MK_CONTROL)//判断Shift键和Ctrl键是否被按下 

  if(wParam&MK_SHIFT) 
  { //根据不同的情况给出不同的提示
    DrawText(hdc,"Ctrland Shift", -1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER); 
  } 
  else
  {
    DrawText(hdc,"Ctrl Only" ,-1,&rect, 
    DT_SINGLELINE|DT_CENTER|DT_VCENTER); 
  }
}
else

  if(wParam&MK_SHIFT)
  {
    DrawText(hdc,"Shift Only",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
  }
  else
  {
    DrawText(hdc,"Middle Button of mouse only",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
  }

EndPaint(hWnd,&ps);
return 0;
  这种判断复杂鼠标操作的方法在基础知识部分已经详细介绍过,这里不再赘述。这一段程序的作用是根据在按下鼠标中键的同时,再按Ctrl键或Shift键的不同情况而在窗口中输出不同的信息。
处理WM_ RBUTTONDOWN消息的程序段如下:
case WM_RBUTTONDOWN://处理鼠标右键按下的消息
iCount = 0;//重新初始化点数目
center.x=LOWORD (lParam);//保存新的中心点坐标
center.y=HIWORD (lParam);
InvalidateRect (hwnd, NULL, TRUE);//通知系统重画窗口
return 0;
  这一段程序的主要作用是将鼠标右击点的坐标保存在POINT结构体变量center中,再将计数变量iCount归零,为绘制一簇射线作准备。完成以上工作后,程序调用InvalidateRect函数通知系统重画窗口,将窗口中原有的射线族擦去。处理WM_MOUSEMOVE消息的程序段如下:
case WM_MOUSEMOVE://处理鼠标移动的消息
if (wParam & MK_LBUTTON && iCount<256)//只处理鼠标拖动的消息
{
  points[iCount].x = LOWORD (lParam);//保存点的X坐标
  points[iCount++].y = HIWORD (lParam);//保存点的Y坐标
  hdc = GetDC (hwnd);//获得窗口的设备描述表句柄
  SetPixel (hdc, LOWORD (lParam), HIWORD (lParam), 0L);
  //绘点
  ReleaseDC (hwnd, hdc);//释放设备描述表句柄
}
  事实上,该程序只是处理鼠标左键拖动操作的消息。在执行拖动操作的过程中,程序不断将鼠标点的坐标记录在points数组中。points数组是WndProc函数中定义的一个静态的POINT结构体数组。在记录点坐标的同时,程序调用了SetPixel函数在窗口上的绘制点,对被记录的点进行标志。对于WM_ LBUTTONUP消息,程序只调用了IvalidateRect函数通知系统拖动操作已经结束,可以开始绘制射线族了。
处理WM _PAINT消息的程序段如下:
case WM_PAINT://处理窗口重画的消息 
hdc = BeginPaint (hwnd, &ps);//获得设备描述表句柄 
SetCursor (LoadCursor (NULL, IDC_WAIT));//设置新的鼠标光标 
ShowCursor (TRUE);//显示鼠标光标 
for (i = 0 ; i < iCount ; i++) 

  MoveToEx(hdc, center.x, center.y,NULL);//绘制直线 
  LineTo(hdc, points[i].x, points[i].y); 

ShowCursor(FALSE);//隐藏鼠标 
SetCursor(LoadCursor (NULL, IDC_ARROW));
//恢复原来的鼠标光标 
EndPaint(hwnd, &ps); 
return 0; 
  以上程序的功能是实现射线族的绘制。程序使用了一个for循环,循环次数为iCoun变量记录的点数。在循环体中反复调用 MoveToEx函数和 LineTo函数绘制直线。MoveToEx函数使直线的起点回到有center变量记录的中点,LineTo函数实现由中点向points数组中的各点绘制直线。
  在处理WM_PAINT消息的程序中还使用了SetCursor函数和ShowCursor函数。SetCursor函数能设定一个鼠标图标,ShowCursor函数能将设定好的图标显示出来。在本实例中,调用这两个函数的目的是当程序绘制射线族的时候将鼠标图标转换成沙漏图案,表示程序正在执行某次操作。当给制完成后,又重新设置鼠标图像为箭头图标。
  程序运行后,首先生成一个窗口,等待用户执行鼠标操作。用户右击后,再按下鼠标左键并拖动,则程序会绘制出一簇美丽的射线。运行结果如图所示。

第六章 鼠标消息与键盘消息_第1张图片

鼠标消息实例2
#include <windows.h>
int WINAPI WinMain(HINSTANCE, HINSTANCE,LPSTR,int);
LRESULT CALLBACK WndProc(HWND,UINT, WPARAM,LPARAM);
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
  WNDCLASSEX wcex;
  wcex.cbSize = sizeof(WNDCLASSEX);
  wcex.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
  wcex.lpfnWndProc = (WNDPROC)WndProc;
  wcex.cbClsExtra = 0;
  wcex.cbWndExtra = 0;
  wcex.hInstance = hInstance;
  wcex.hIcon = LoadIcon (NULL,(LPCTSTR)IDI_APPLICATION);
  wcex.hCursor = LoadCursor (NULL,IDC_ARROW);
  wcex.hbrBackground = (HBRUSH) (COLOR_WINDOW+1);
  wcex.lpszMenuName = NULL;
  wcex.lpszClassName = "SeeMouseMessage";
  wcex.hIconSm = LoadIcon(NULL,(LPCTSTR)IDI_APPLICATION);
  if(!RegisterClassEx(&wcex)) return FALSE;
  int SW_XFS = GetSystemMetrics(SM_CXSCREEN);
  int SW_YFS = GetSystemMetrics(SM_CYSCREEN);
  HWND hWnd;
  hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,
             "SeeMouseMessage",
             "Trace Mouse Operation",
             WS_OVERLAPPEDWINDOW,
             0,
             0,
             SW_XFS,
             SW_YFS-25,
             NULL,
             NULL,
             hInstance,
             NULL);
  if(!hWnd) return FALSE;
  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);
  MSG msg;

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

LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
  HDC hDC;
  PAINTSTRUCT ps;
  static char Buffer[256];
  switch(message)
  {
    case WM_MOUSEMOVE:
    hDC = GetDC(hWnd); 
    wsprintf(Buffer," ");
    TextOut(hDC,20,40,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_MOUSEMOVE %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,40,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC); 
    break;

    case WM_LBUTTONDOWN:
    hDC = GetDC(hWnd); 
    wsprintf(Buffer," ");
    TextOut(hDC,20,60,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_LBUTTONDOWN %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,60,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC); 
    break;

    case WM_LBUTTONUP:
    hDC = GetDC(hWnd); 
    wsprintf(Buffer," ");
    TextOut(hDC,20,80,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_LBUTTONUP %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,80,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC); 
    break;

    case WM_LBUTTONDBLCLK:
    hDC = GetDC(hWnd); 
    wsprintf(Buffer," ");
    TextOut(hDC,20,100,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_LBUTTONDBLCLK %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,100,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC); 
    break;

    case WM_RBUTTONDOWN:
    hDC = GetDC(hWnd); 
    wsprintf(Buffer," ");
    TextOut(hDC,20,120,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_RBUTTONDOWN %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,120,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC); 
    break;

    case WM_RBUTTONUP:
    hDC = GetDC(hWnd); 
    wsprintf(Buffer," ");
    TextOut(hDC,20,140,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_RBUTTONUP %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,140,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC); 
    break;

    case WM_RBUTTONDBLCLK:
    hDC = GetDC(hWnd); 
    wsprintf(Buffer," ");
    TextOut(hDC,20,160,Buffer,strlen(Buffer));
    wsprintf(Buffer,"WM_RBUTTONDBLCLK %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,160,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC); 
    break;

    case WM_PAINT:
    hDC = BeginPaint(hWnd,&ps);
    wsprintf(Buffer," ");
    TextOut(hDC,20,20,Buffer,strlen(Buffer));
    wsprintf(Buffer," Message wParam x y");
    TextOut(hDC,20,20,Buffer,strlen(Buffer));
    EndPaint(hWnd,&ps);
    break;

    case WM_DESTROY:
    PostQuitMessage(0);
    break;

    default:
    return DefWindowProc(hWnd,message,wParam,lParam);
  }
  return 0;
}

你可能感兴趣的:(第六章 鼠标消息与键盘消息)