鼠标消息与键盘消息
作者:admin 日期:2007-06-25
在MicrosoftWindows中,键盘和鼠标是两个标准的用户输入源,在一些交叠的操作中通常相互补充使用。当然,鼠标在今天的应用程序中比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进制)常量符号含义
01VK_LBUTTON鼠标左键
02VK_RBUTTON鼠标右键
03VK_CANCELBreak中断键
04VK_MBUTTON鼠标中键
05-07--未定义
08VK_BACK(BackSpace)键
09VK_TABTab键
0A-0B--未定义
0CVK_CLEARClear键
0DVK_RETURNEnter键
0E-0F--未定义
10VK_SHIFTShift键
11VK_CONTROLCtrl键
12VK_MENUAlt键
13VK_PAUSEPause键
14VK_CAPTIALCapsLock键
15-19--汉字系统保留
1A--未定义
1BVK_ESCAPEEsc键
1C-1F--汉字系统保留
20VK_SPACE空格键
21VK_PRIORPageUp键
22VK_NEXTPageDown键
23VK_ENDEnd键
24VK_HOMEHome键
25VK_LEFT←(LeftArrow)键
26VK_UP↑(UpArrow)键
27VK_RIGHT→(RightArrow)键
28VK_DOWN↓(DownArrow)键
29VK_SelectSelect键
2A--OEM保留
2BVK_EXECUTEExecute键
2CVK_SNAPSHOTPrintScreen键
2DVK_InsertInsert键
2EVK_DeleteDelete键
2FVK_HELPHelp键
30-39VK_0-VK_9数字键0-9
3A-40--未定义
41-5AVK_A-VK_Z字母键A-Z
5B-5F--未定义
60-69VK_NUMPAD0-VK_NUMPAD9小键盘数字键0-9
6AVK_MULTIPLY*(乘号)键
6BVK_ADD+(加号)键
6CVK_SEPAPATOR分隔符键
6EVK_SUBTRACT-(减号)键
6FVK_DECIMAL.(小数点)键
70-87VK_DIVIDE/(除号)键
88-8FVK_F1-VK_F24F1-F24功能键
90VK_NUMBERLOCKNumberlock键
91VK_SCROLLScrolllock键
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_SYSCHARWM_CHAR
死字符WM_SYSDEADCHARWM_DEADCHAR
其中死字符是由某些特殊键盘上的按键所造成的,Windows一般忽略死字符所产生的消息。
Windows的消息一般是通过一个MSG结构体变量传送给消息处理函数的。对于键盘消息,MSG结构体变量的各个域中较重要的是lParam域和wParam域。wParam域用于保存按键的虚拟键代码或字符的ASCII码。对于非字符消息,wParam域保存按键的虚拟健代码;对于字符消息,wParam域不保存字符的ASCII码。lParam域则用于保存击键时产生的附加信息,实际上一个32位的lParam变量被分为六部分,记录了以下相关信息:重复次数、OEM扫描码、扩展键标志、关联键标志、前一击键状态和转换状态。lParam域各位的含义如表所示。
位数含义
0-15击键重复次数累加
16-23OEM扫描码
24是否为扩展键
25-28未定义
29是否便用关联键,及Alt键是否同时按下。
30前一次击键状态,0表示该键前一次状态为抬起,1表示前一次状态为按下
31转换状态
按键的次序不同,产生的消息也不相同。例如,按下并释放1键,读过程依次产生如表所示三条消息。按下1键所产生的消息和wParam的取值
消息wParam变量取值
WM_KEYDOWN虚拟码1
WM_CHARASCII码“1”
WM_KEYUP虚拟码1
如果按下Shift键后再按下1键并释放,则依次产生如表所示的消息。按下Shift键后按1健所产生的消息和wParam的取值
消息wParam变量取值
WM_KEYDOWN虚拟码VK_SHIFT
WM_KEYDOWN虚拟码VK_1
WM_CHARASCII码 “1”
WM_KEYUP虚拟码VK_1
WM_KEYUP虚拟码VK_SHIFT
二、键盘应用实例
下面通过一个应用程序实例来说明在实际编程中如何处理键盘消息。
#include<windows.h>
#include<stdio.h>
//全局变量
RECTrc;//记录滚屏的矩形区域
?
intxChar,yChar;//文本输入点坐标
WNDCLASSEXwnd;//窗口类结构变量
charszAppName[]="键盘消息监视程序";//窗口类名
//函数声明
LRESULTCALLBACKWndProc(HWND,UINT,WPARAM,LPARAM);
BOOLMyRegisterClass(HINSTANCEhInstance);
BOOLInitInstance(HINSTANCEhInstance,intiCmdShow);
//函数:WinMain
//作用:入口函数
intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRszCmdLine,intiCmdShow)
{
MSGmsg;
if(!MyRegisterClass(hInstance))
{
returnFALSE;
}
if(!InitInstance(hInstance,iCmdShow))
{
returnFALSE;
}
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
returnmsg.wParam;
}
//函数:ShowKey
//作用:实现在窗口中显示按键信息
voidShowKey(HWNDhwnd,intiType,char*szMessage,WPARAMwParam,LPARAMlParam)
{
staticchar*szFormat[2]={"%-14s%3d%c%6u%4d%5s%5s%6s%6s",
"%-14s%3d%c%6u%4d%5s%5s%6s%6s"};
charszBuffer[80];
HDChdc;
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,
wsprintfszBuffer,
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
//作用:处理主窗口的消息
LRESULTCALLBACKWndProc(HWNDhwnd,UINTiMsg,WPARAMwParam,LPARAMlParam)
{
staticcharszTop[]="消息键字符重复数扫描码扩展码ALT前一状态转换状态";
staticcharszUnd[]="______________________________________________";
//在窗口中输出文字作为信息标题
HDChdc;
PAINTSTRUCTps;
TEXTMETRICtm;
switch(iMsg)
{
caseWM_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;
return0;
caseWM_SIZE://处理窗口大小改变的消息
//窗体改变后保存新的滚屏区域右下角坐标
rc.right=LOWORD(lParam);
rc.bottom=HIWORD(lParam);
UpdateWindow(hwnd);
return0;
caseWM_PAINT://处理窗口重绘消息
InvalidateRect(hwnd,NULL,TRUE);
hdc=BeginPaint(hwnd,&ps);
SelectObject(hdc,GetStockObject(SYSTEM_FIXED_FONT));
SetBkMode(hdc,TRANSPARENT);
TextOut(hdc,xChar,yChar/2,szTop,(sizeofszTop)-1);
TextOut(hdc,xChar,yChar/2,szUnd,(sizeofszUnd)-1);
EndPaint(hwnd,&ps);
return0;
caseWM_KEYDOWN:
//处理键盘上某一键按下的消息
ShowKey(hwnd,0,"WM_KEYDOWN",wParam,lParam);
return0;
caseWM_KEYUP:
//处理键盘上某一按下键被释放的消息
ShowKey(hwnd,0,"WM_KEYUP",wParam,lParam);
return0;
caseWM_CHAR:
//处理击键过程中产生的非系统键的可见字符消息
howKey(hwnd,1,"WM_CHAR",wParam,lParam);
return0;
caseWM_DEADCHAR:
//处理击键过程中产生的非系统键"死字符"消息
ShowKey(hwnd,1,"WM_DEADCHAR",wParam,lParam);
return0;
caseWM_SYSKEYDOWN:
//处理系统键按下的消息
ShowKey(hwnd,0,"WM_SYSKEYDOWN",wParam,lParam);
break;
caseWM_SYSKEYUP:
//处理系统键抬起的消息
ShowKey(hwnd,0,"WM_SYSKEYUP",wParam,lParam);
break;
caseWM_SYSCHAR://处理系统键可见字符消息
ShowKey(hwnd,1,"WM_SYSCHAR",wParam,lParam);
break;
caseWM_SYSDEADCHAR://处理系统键"死字符"消息
ShowKey(hwnd,1,"WM_SYSDEADCHAR",wParam,lParam);
break;
caseWM_DESTROY:
//处理结束应用程序的消息
PostQuitMessage(0);
return0;
}
returnDefWindowProc(hwnd,iMsg,wParam,lParam);
}
//函数:MyRegisterClass
//作用:注册窗口类
BOOLMyRegisterClass(HINSTANCEhInstance)
{
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);
returnRegisterClassEx(&wnd);
}
//函数:InitInstance
//作用:创建主窗口
BOOLInitInstance(HINSTANCEhInstance,intiCmdShow)
{
HWNDhwnd;
hwnd=CreateWindow(szAppName,
"键盘消息监视程序",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,
NULL,NULL,hInstance,NULL
);
if(!hwnd)
{
returnFALSE;
}
ShowWindow(hwnd,iCmdShow);
UpdateWindow(hwnd);
returnTRUE;
}
本实例的作用是通过程序捕获键盘消息,然后将wParam参数所包含的数据进行分解,最后将各项信息通过窗口显示出来。实例的源文件包含了Initlnstance、MyRegisterClass、ShowKey、WinMain和WndProc五个函数。程序的基本思路是以WinMain函数作为程序入口,再调用MyRegisterClass函数和InitInstance函数注册窗口类并创建和保存窗日,然后创建和显示窗口,最后进入消息循环。
下面重点分析函数WndProc和ShowKey。
1.WndProc函数
在本实例中WndProc函数处理的消息主要有WM_Create、WM_SIZE、WM_PAINT和键盘消息。
caseWM_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;
return0;
这一程序段的主要作用是将字体对象选入当前窗体的设备描述表中,同时取得字体高度和平均宽度,再初始化编辑区的滚屏区域的右上角Y坐标。进入该程序段后,首先通过GetDC函数获得当前窗体的设备描述表,再通过GetStockObject函数获得系统字体,然后用SelectObject函数将字体对家选入窗体的设备描述表中。其中,hdc为设备描述表句柄。在完成所有操作后,程序还必须通过ReleaseDC函数释放设备描述表。在该程序段中使用了GetTextMetrics函数来获得字体的几何尺寸。GetTextMetrics函效的原型定义如下:
BOOLGetTextMetrics(HDChdc,//指向设备描述表的句柄
LPTEXTMETRIClptm//TEXTMETRIC结构体变量的指针
//所获得的所有信息保存在TEXTMETRIC结构体变量中
);
其中lptm是一个指向TEXTMETRIC结构体的指针。TEXTMETRIC结构体包含了与字体的几何尺寸相关的基本信息。该结构体的具体定义如下:
typedefstructtagTEXTMETRIC
{//tm
LONGtmHeight;//字体高度
LONGtmAscent;//字体高于基准线的高度
LONGtmDescent;//字体低于基准线的高度
LONGtmInternalLeading;//给大写字母留出的空间
LONGtmExtenalLeading;//由字体设计者推荐的附加行距
LONGtmAveCharWidth;//字体平均宽度
LONGtmMaxCharWidth;//字体最大宽度
LONGtmWeight;//字体黑度
LONGtmOverhang;//在合成斜体或黑体时加在字符上的附加宽度值
LONGtmDigitizedAspectX;//字体所适合的高宽比的宽
LONGtmDigitizedAspectY;//字体所适合的高宽比的高
BCHARtmFirstChar;//字体中定义的第一个字符
BCHARtmLastChar;//字体中定义的最后一个字符
BCHARtrnDefaultChar;//字体中的默认字符
BCHARtrnBreakChar;//windows在调整文本时用于分裂词的字符
BYTEtmItalic;//取非零值时表示斜体字体
BYTEtmUnderLined;//取非零值时表示下划线字体
BYTEtmStruckOut;//取非零值时为删除线字体
BYTEtmPitchAndFamily;//低二位为字符间距,高四位为系列值
BYTEtmCharSet;//指定字符集
}TEXTMETRIC;
该结构中所有的字体大小都是按逻辑单位给出的,这就是说字体的大小取决于当前显示设备的映射模式。
在例中,所获得的字体几何尺寸保存在TEXTMETRIC结构体变量tm中。滚屏区域的范围是通过RECT结构体变量re保存的,RECT结构体变量可以通过记录矩形区域的右上角和左下角的坐标来确定一个矩形区域。
RECT结构的原型定义如下:
typedefstrucRECT{
LONGleft;//矩形左上角X坐标
LONGtop;//左上角Y坐标
LONGright;//右下角X坐标
LONGbottom;//右下角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
消息的程序段如下:
caseWM_SIZE://处理窗口大小改变的消息
//窗体改变后保存新的滚屏区域右下角坐标
rc.right=LOWORD(lParam);
rc.bottom=HIWORD(lParam);
UpdateWindow(hwnd);
return0;
该程序段比较简单,只是当窗口的尺寸改变时重新设定滚屏区域的右下角坐标,并更新窗口。值得注意的是,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消息的程序段如下:
caseWM_PAINT://处理窗口重绘消息?
InvalidateRect(hwnd,NULL,TRUE);?
hdc=BeginPaint(hwnd,&ps);?
SelectObject(hdc,GetStockObject(SYSTEM_FIXED_FONT));?
SetBkMode(hdc,TRANSPARENT);?
TextOut(hdc,xChar,yChar/2,szTop,(sizeofszTop)-1);?
TextOut(hdc,xChar,yChar/2,szUnd,(sizeofszUnd)-1);?
EndPaint(hwnd,&ps);?
return0;
该程序段首先调用InvalidateRect函数使窗口无效,InvalidateRect函数的功能是使窗口的某一部分无效,也就是通知Windows该部分需要被刷新和重画。
在InvalidateRect函数之后,程序调用函数BeginPaint准备重画窗口。
BeginPaint函数的原型定义如下:
HDCBeginPaint(HWNDhwnd,//重画窗口的句柄
LPPAINTSTRUCTlpPaint//指向一个用于保存所有重
//画信息的PAINTSTRUCT结构体变量的指针);
BeginPaint函数的作用是完成重画窗体之前的准备,并将重画窗体的数据保存在一个PAINTSTRUCT结构体变量中。PAINTSTRUCT结构体可以用于保存窗口重画时的数据以方便以后使用。
PAINTSTRUCT结构体的定义如下:
typedefstructtagPAINTSTRUCT{//ps
HDChdc;//重画区域所在窗口的句柄
BOOLfErase;//是否擦去背景
RECTrcPaint;//指定重画窗体的范围
BOOLfRestore;//系统保留域
BOOLfIncUpdate;//系统保留域
BYTErgbReserved〔32〕;//系统保留
}PAINTSTRUCT;
BeginPaint函数如果操作成功会返回一个被操作窗口的设备描述表的句柄。如果操作不成功则函数返回NULL值,表明显示设备不可用。该函数在运行过程中,会进行自动调整,使得所有区域都包含在刷新区域的范围内。而原有需要刷新的区域是由InvalidateRect函数或InvalidateRgn函数指定的。一般来说,只有当程序处理WM_PAINT消息时才调用BeginPaint函数,而且,每次调用BeginPaint函数都需要对应调用一个EndPaint函数来结束重画过程。在BeginPaint函数调用后,会将插入符光标自动隐藏。EndPaint函数原型定义如下:
BOOLEndPaint(HWNDhWnd,//窗口句柄
CONSTPAINTSTRUCT*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函数的具体定义如下:
//作用:实现在窗口中显示按键信息
voidShowKey(HWNDhwnd,intiType,char*szMessage,WPARAMwParam,LPARAMlParam)
{
staticchar*szFormat[2]={"%-14s%3d%c%6u%4d%5s%5s%6s%6s",
"%-14s%3d%c%6u%4d%5s%5s%6s%6s"};
charszBuffer[80];
HDChdc;
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函数的原型定义如下:
intScrollWindowEx(HWNDhwnd,//发生滚屏的窗口的句柄
intdx,//水平滚屏的数值
intdy,//垂直滚屏的数值
CONSTRECT*prcScroll,//记录发生滚屏的矩形区域的RECT结构体的地址
CONSTRECT*prcClip,//记录发生剪切的矩形区域的RECT结构体的地址
HRGNhrgnUpdate,//需要更新区域的句柄
LPRECTprcUpdate,//记录需要更新矩形区域的RECT结构体的地址
UINTflags//滚屏控制标志
);
其中,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在Windows95及以后的版本中使窗口发生平滑滚屏。如果ScrollWindowEx函数执行成功,则返回值为以下三者之一:
SIMPLEREGION表示有一个矩形的无效区域;
COMPLEXREGION表示没有无效区域和重叠区域;
NULLREGION表示没有无效区域。
如果ScrollWindowEx函数执行不成功,则返回ERROR。
ScrollWindowEx函数的功能也可以通过ScrollWindow函数来实现,ScrollWindow函数的原型定义如下:
BOOLScrollwindow(HWNDhwnd//窗口句柄
intXAmount,//水平滚屏的数值
intYAmount,//垂直滚屏的数值
CONSTRECT*lpReCt,//记录发生滚屏的矩形区域的RECT结构体的地址
CONSTRECT*lpClipRect,//记录发生剪切的矩形区域的RECT结构体的地址
);
可以看出,ScrollWindow函数与ScrollWindowEx函数十分相似,其参数的意义也基本相同。事实上,ScrollWindow函数是为了保持对较低版本的Windows兼容而设计的,用户在编程时,除非需要考虑程序的向下兼容,否则一般都应使用ScrollWindowEx函数。
在滚屏后,函数开始调用TextOut函数进行信息输出。TextOut函数的原型定义如下:
BOOLTextOut(HDChdc,//设备描述表句柄
intnXStart,//文本输出起始点X坐标
intnYStart,//文本输出起始点Y坐标
LPCTSTRlpString,//指向输出字符串的指针
intcbString//字符串中字符的数目
);
TextOut函数能够用当前设定的字体在窗口的指定部位输出一段文本信息。如果操作成功则返回一非零值,否则返回零值。捕获键盘消息的信息主要根据表中的描述,通过使用按位操作确定某些特定位的值,然后再判断具体的状态。
在TextOut函数调用过程中,还调用了wsprintf函数,并使其返回值作为TextOut函数的一个参数值。wsprintf函数的原型定义如下:
intwsprintf(LPTSTRlpOut,//指向需要输出的字符串的指针
LPCTSTRlpFmt,//指向格式控制字符串的指针
……//其他可选参数
);
wsprintf函数能够将一组字符序列按lpFmt参数指定的格式转换,然后保存在lpOut参数指定的字符缓冲区中等待输出。其中,字符序列由可选参数决定,而可选参数的数目和具体内容应该与lpFmt所指定的格式一致。
如果wsprintf函数操作成功,则返回输出字符的数目,但这个字符数目不包括表示结束的NULL标志。如果操作失败,返回的整数值将与输出的字符数目不相符。
实例主要说明了如何处理键盘消息,读者应该着重理解各种信息在MSG结构体变量中是如何保存的,怎样才能够对其中的具体信息进行识别和提取。程序运行后将产生一个背景色为灰色的简单窗口,并在窗口的顶部出现标题提示信息。这时用户如果进行键盘操作,则窗体中便会显示该操作所产生的键盘消息,每显示一条消息程序都会滚屏和重绘窗口,滚屏区域的颜色为白色。
键盘消息实例2:
#include<windows.h>
intWINAPIWinMain(HINSTANCE,HINSTANCE,LPSTR,int);
LRESULTCALLBACKWndProc(HWND,UINT,WPARAM,LPARAM);
intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow)
{
WNDCLASSEXwcex;
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))returnFALSE;
intSW_XFS=GetSystemMetrics(SM_CXSCREEN);
intSW_YFS=GetSystemMetrics(SM_CYSCREEN);
HWNDhWnd;
hWnd=CreateWindowEx(WS_EX_CLIENTEDGE,
"SeeKeyMessage",
"TraceKeyOperation",
WS_OVERLAPPEDWINDOW,
0,0,SW_XFS,SW_YFS-25,
NULL,
NULL,
hInstance,
NULL);
if(!hWnd)returnFALSE;
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
MSGmsg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
returnmsg.wParam;
}
LRESULTCALLBACKWndProc(HWNDhWnd,UINTmessage,WPARAMwParam,LPARAMlParam)
{
HDChDC;
PAINTSTRUCTps;
staticcharBuffer[256];
switch(message)
{
caseWM_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;
caseWM_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;
caseWM_PAINT:
hDC=BeginPaint(hWnd,&ps);
wsprintf(Buffer,"");
TextOut(hDC,20,20,Buffer,strlen(Buffer));
wsprintf(Buffer,"MessagewParamLOWORD(lParam)HIWORD(lParam)");
TextOut(hDC,20,20,Buffer,strlen(Buffer));
EndPaint(hWnd,&ps);
break;
caseWM_DESTROY:
PostQuitMessage(0);
break;
default:
returnDefWindowProc(hWnd,message,wParam,lParam);
}
return0;
}
鼠标消息
随着Windows操作系统的流行,鼠标因为其精确定位和操作方便的优点而成为计算机不可缺少的输入设备。
一、鼠标的基础知识
本节将介绍在程序中用鼠标作为输入设备的方法和技巧。1.鼠标操作和鼠标消息用户在使用鼠标操作的过程中,经常会使用的主要方式有五种,如表所示。
操作名称描述
单击(Click)按下并迅速释放鼠标按钮。
双击(DoubleClick)连续快速完成两次单击操作。
移动(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语句来实现:
caseWM_MOUSEMOVE:
if(wParam&MK_LBUTTON)//只处理鼠标拖动的消息
{……//处理程序
}
在处理鼠标消息的过程中,消息的wParam参数和lParam参数起了重要的作用。wParam参数中保存了在消息产生时其他操作进行的状态;用户可以通过位屏蔽操作来判断在该消息产生的同时,其余操作是否正在进行。这正是在程序中判断复杂鼠标操作的基本方法。例如,上面判断拖动操作的程序段就用了位操作wParam&MK_LBUTTON,判断在鼠标移动(WM_MOUSEMOVE)的同时鼠标左键是否同时被接下。如果,鼠标左键同时按下,则位操作的结果为TRUE,说明当前操作为拖动操作,程序可以继续进行下一步处理。又如需要判断单击鼠标左键时是否同时按下了Ctrl键或Shift键,可以用以下程序段来处理:
caseWM_LBUTTONDOWN:
if(wParam&MK_CTROL)
{//Ctrl键同时按下
if(wParam&MK_SHIFT)
{//Ctrl键和Shift键都同时按下
……//处理程序
}
else{//Ctrl健同时按下,但Shift键没有被按下
…….//处理程序
}
}
elseif(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函数的原型定义如下:
BOOLSetDoubleClickTime(UINTuInterval//新的击键时间间隔)
2.鼠标捕捉
在通常情况下,只有当鼠标位于窗体内时,窗体才能接收到鼠标的消息。如果需要接收所有的鼠标消息而不论鼠标是否在窗口内,这时可以调用SetCapture函数来实现。SetCapture函数的原型定义如下:
HWNDSetCapture(
HWNDhwnd//窗口句柄
);
调用SetCapture函数后,所有鼠标操作所产生的消息都直接发送到指定窗口。因为此时鼠标可能位于窗口之外,所以鼠标的坐标可能为负值。由于调用该函数会使其他窗口不能接收到键盘和鼠标的消息,因此在完成操作后应及时调用ReleaseCapture函数释放鼠标捕获。ReleaseCapture函数的原型定义如下:
BOOLReleaseCapture(VOID);
二、鼠标应用实例
下面是一个在程序设计中如何捕获鼠标消息的实例。
#include<windows.h>
//全局变量
WNDCLASSEXwnd;
staticcharszAppName[]="mouse";//窗口类名
//函数声明
longWINAPIWndProc(HWND,UINT,WPARAM,LPARAM);
BOOLMyRegisterClass(HINSTANCEhInstance);
BOOLInitInstance(HINSTANCEhInstance,intiCmdShow);
//函数:WinMain
//作用:入口函数
intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,PSTRszCmdLine,intiCmdShow)?
{
MSGmsg;
if(!MyRegisterClass(hInstance))
{
returnFALSE;
}
if(!InitInstance(hInstance,iCmdShow))
{
returnFALSE;
}
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
returnmsg.wParam;
}
//函数:WndProc
//作用:处理主窗口的消息
longWINAPIWndProc(HWNDhwnd,UINTmsg,WPARAMwParam,LPARAMlParam)
{
staticPOINTpoints[256];//保存点坐标
staticPOINTcenter;//保存中心点坐标
staticintiCount;//点数目累加值
HDChdc;
PAINTSTRUCTps;
inti;//循环计数
RECTrect;
switch(msg)
{
caseWM_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,"CtrlandShift",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER?);
}
else
{
DrawText(hdc,"CtrlOnly",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
}
}
elseif(wParam&MK_SHIFT)
{
DrawText(hdc,"ShiftOnly",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
}
else
{
DrawText(hdc,
"MiddleButtonofmouseonly",
-1,
&rect,
DT_SINGLELINE|DT_CENTER|DT_VCENTER);
}
EndPaint(hWnd,&ps);
return0;
caseWM_RBUTTONDOWN:
//处理鼠标右键按下的消息
iCount=0;//重新初始化点数目
center.x=LOWORD(lParam);
//保存新的中心点坐标
center.y=HIWORD(lParam);
InvalidateRect(hwnd,NULL,TRUE);//通知系统重画窗口
return0;
caseWM_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);//释放设备描述表句柄
}?return0;
caseWM_LBUTTONUP:
//处理鼠标左键抬起的消息
InvalidateRect(hwnd,NULL,FALSE);
//通知系统重画窗口
return0;
caseWM_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.x,points.y);
}
ShowCursor(FALSE);//隐藏鼠标
SetCursor(LoadCursor(NULL,IDC_ARROW));
//恢复原来的鼠标光标?
EndPaint(hwnd,&ps);
return0;
caseWM_DESTROY://处理销毁窗口的消息
PostQuitMessage(0);
return0;
}
returnDefWindowProc(hwnd,msg,wParam,lParam);
}
//函数:MyRegisterClass
//作用:注册窗口类
BOOLMyRegisterClass(HINSTANCEhInstance)
{
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);
returnRegisterClassEx(&wnd);
}
//函数:InitInstance
//作用:创建窗口
BOOLInitInstance(HINSTANCEhInstance,intiCmdShow)
{
HWNDhwnd;
hwnd=CreateWindow(szAppName,
"跟踪鼠标移动",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,
NULL,NULL,hInstance,NULL);
if(!hwnd)returnFALSE;
ShowWindow(hwnd,iCmdShow);
UpdateWindow(hwnd);
returnTRUE;
}
例题的主要功能是在窗口中某个位置单击鼠标右键时,程序保存捕获的鼠标点处的坐标。紧接着按下鼠标左键在窗口中拖动,程序会记录下鼠标运动的轨迹,并以刚才右击鼠标时确定的点为中心绘制一簇射线。本实例最多可以绘制256条射线。程序的另一目的是为了让读者进一步了解如何捕获鼠标与Ctrl键或Shift键组合时的复杂鼠标消息。如果在窗口中单击鼠标中键,程序会在窗口中央显示文本信息说明用户是否同时按下Ctrl键和Shift键。
源文件与本书前面所介绍的其他实例一样,都具有基本的WindowsAPI程序的结构。即以WinMain函数作为程序入口,调用MyRegisterClass函数和InitInstance函数注册窗口类和创建窗口,再进入消息循环。并在消息循环中调用WndProc函数处理鼠标消息。下面主要介绍WndProc函数处理鼠标消息的方法和技巧。
在例中WndProc函数能够处理的消息包括WM_MBUTTONDOWN、WM_RBUTTONDOWN、WM_MOUSEMOVE、WM_LBUTTONUP和WM_PAINT。
处理WM_MBUTTONDOWN消息的程序段如下:
caseWM_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,"CtrlandShift",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
}
else
{
DrawText(hdc,"CtrlOnly",-1,&rect,
DT_SINGLELINE|DT_CENTER|DT_VCENTER);
}
}
else
{
if(wParam&MK_SHIFT)
{
DrawText(hdc,"ShiftOnly",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
}
else
{
DrawText(hdc,"MiddleButtonofmouseonly",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
}
}
EndPaint(hWnd,&ps);
return0;
这种判断复杂鼠标操作的方法在基础知识部分已经详细介绍过,这里不再赘述。这一段程序的作用是根据在按下鼠标中键的同时,再按Ctrl键或Shift键的不同情况而在窗口中输出不同的信息。
处理WM_RBUTTONDOWN消息的程序段如下:
caseWM_RBUTTONDOWN://处理鼠标右键按下的消息
iCount=0;//重新初始化点数目
center.x=LOWORD(lParam);//保存新的中心点坐标
center.y=HIWORD(lParam);
InvalidateRect(hwnd,NULL,TRUE);//通知系统重画窗口
return0;
这一段程序的主要作用是将鼠标右击点的坐标保存在POINT结构体变量center中,再将计数变量iCount归零,为绘制一簇射线作准备。完成以上工作后,程序调用InvalidateRect函数通知系统重画窗口,将窗口中原有的射线族擦去。处理WM_MOUSEMOVE消息的程序段如下:
caseWM_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消息的程序段如下:
caseWM_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.x,points.y);
}
ShowCursor(FALSE);//隐藏鼠标
SetCursor(LoadCursor(NULL,IDC_ARROW));
//恢复原来的鼠标光标
EndPaint(hwnd,&ps);
return0;
以上程序的功能是实现射线族的绘制。程序使用了一个for循环,循环次数为iCoun变量记录的点数。在循环体中反复调用MoveToEx函数和LineTo函数绘制直线。MoveToEx函数使直线的起点回到有center变量记录的中点,LineTo函数实现由中点向points数组中的各点绘制直线。
在处理WM_PAINT消息的程序中还使用了SetCursor函数和ShowCursor函数。SetCursor函数能设定一个鼠标图标,ShowCursor函数能将设定好的图标显示出来。在本实例中,调用这两个函数的目的是当程序绘制射线族的时候将鼠标图标转换成沙漏图案,表示程序正在执行某次操作。当给制完成后,又重新设置鼠标图像为箭头图标。
程序运行后,首先生成一个窗口,等待用户执行鼠标操作。用户右击后,再按下鼠标左键并拖动,则程序会绘制出一簇美丽的射线。运行结果如图所示。
鼠标消息实例2
#include<windows.h>
intWINAPIWinMain(HINSTANCE,HINSTANCE,LPSTR,int);
LRESULTCALLBACKWndProc(HWND,UINT,WPARAM,LPARAM);
intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow)
{
WNDCLASSEXwcex;
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))returnFALSE;
intSW_XFS=GetSystemMetrics(SM_CXSCREEN);
intSW_YFS=GetSystemMetrics(SM_CYSCREEN);
HWNDhWnd;
hWnd=CreateWindowEx(WS_EX_CLIENTEDGE,
"SeeMouseMessage",
"TraceMouseOperation",
WS_OVERLAPPEDWINDOW,
0,
0,
SW_XFS,
SW_YFS-25,
NULL,
NULL,
hInstance,
NULL);
if(!hWnd)returnFALSE;
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
MSGmsg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
returnmsg.wParam;
}
LRESULTCALLBACKWndProc(HWNDhWnd,UINTmessage,WPARAMwParam,LPARAMlParam)
{
HDChDC;
PAINTSTRUCTps;
staticcharBuffer[256];
switch(message)
{
caseWM_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;
caseWM_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;
caseWM_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;
caseWM_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;
caseWM_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;
caseWM_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;
caseWM_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;
caseWM_PAINT:
hDC=BeginPaint(hWnd,&ps);
wsprintf(Buffer,"");
TextOut(hDC,20,20,Buffer,strlen(Buffer));
wsprintf(Buffer,"MessagewParamxy");
TextOut(hDC,20,20,Buffer,strlen(Buffer));
EndPaint(hWnd,&ps);
break;
caseWM_DESTROY:
PostQuitMessage(0);
break;
default:
returnDefWindowProc(hWnd,message,wParam,lParam);
}
return0;
}