目录:
- 滚动条
- 键盘
- 鼠标
滚动条ScrollBar
发送消息:WM_VSCROLL和WM_HSCROLL
参数wParam:wParam消息参数被分为一个低字组和一个高字组。wParam的低字组是一个数值,它指出了鼠标对滚动条进行的操作。这个数值被看作一个「通知码」。通知码的值由以SB(代表「scroll bar(滚动条)」)开头的标识符定义。wParam的高字组存当前滚动条的位置信息。
参数lParam: 对于来自作为窗口的一部分而建立的滚动条消息,您可以忽略lParam;它只用于作为子窗口而建立的滚动条(通常在对话框内)。
也带有wParam和lParam消息参数。
以下是在WINUSER.H中定义的通知码:
#define SB_LINEUP 0 #define SB_LINELEFT 0 #define SB_LINEDOWN 1 #define SB_LINERIGHT 1 #define SB_PAGEUP 2 #define SB_PAGELEFT 2 #define SB_PAGEDOWN 3 #define SB_PAGERIGHT 3 #define SB_THUMBPOSITION 4 #define SB_THUMBTRACK 5 #define SB_TOP 6 #define SB_LEFT 6 #define SB_BOTTOM 7 #define SB_RIGHT 7 #define SB_ENDSCROLL 8
使用示范代码:
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { //...... hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 2"), WS_OVERLAPPEDWINDOW | WS_VSCROLL, CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; //...... } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: // Initialization SetScrollRange (hwnd, SB_VERT, 0, NUMLINES - 1, FALSE) ; SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ; return 0 ; case WM_VSCROLL: switch (LOWORD (wParam)) { case SB_LINEUP:iVscrollPos++;break;
case SB_PAGEUP:break;
case SB_THUMBPOSITION:break;
//...... } //...... } }
键盘
发送消息:【按键消息】和【字符消息】
【按键消息】:
键按下 | 键释放 | |
非系统键 | WM_KEYDOWN | WM_KEYUP |
系统键 | WM_SYSKEYDOWN | WM_SYSKEYUP |
(系统键:WM_SYSKEYDOWN和WM_SYSKEYUP消息经常由与Alt相组合的按键产生,这些按键启动程序菜单或者系统菜单上的选项,或者用于切换活动窗口等系统功能(Alt-Tab或者Alt-Esc),也可以用作系统菜单快捷键(Alt键与一个功能键相结合,例如Alt-F4用于关闭应用程序)。)
【字符消息】:TranslateMessage把字符消息放入消息队列中。此字符消息将是GetMessage从消息队列中得到的按键消息之后的下一个消息。
字符 | 死字符 | |
非系统字符 | WM_CHAR | WM_DEADCHAR |
系统字符 | WM_SYSCHAR | WM_SYSDEADCHAR |
参数wParam:对所有四类按键消息,wParam是虚拟键代码,表示按下或释放的键,而lParam则包含属于按键的其它数据。
对于字符消息,wParam是字符的ASCII编码等,lParam 同上
参数lParam:如下图:
重复计数
重复计数是该消息所表示的按键次数,大多数情况下,重复计数设定为1。不过,如果按下一个键之后,您的窗口消息处理程序不够快,以致不能处理自动重复速率(您可以在「控制台」的「键盘」中进行设定)下的按键消息,Windows就把几个WM_KEYDOWN或者WM_SYSKEYDOWN消息组合到单个消息中,并相应地增加重复计数。WM_KEYUP或WM_SYSKEYUP消息的重复计数总是为1。
因为重复计数大于1指示按键速率大于您程序的处理能力,所以您也可能想在处理键盘消息时忽略重复计数。几乎每个人都有文书处理或执行电子表格时画面卷过头的经验,因为多余的按键堆满了键盘缓冲区,所以当程序用一些时间来处理每一次按键时,如果忽略您程序中的重复计数,就能够解决此问题。不过,有时可能也会用到重复计数,您应该尝试使用两种方法执行程序,并从中找出一种较好的方法。
OEM扫描码
OEM扫描码是由硬件(键盘)产生的代码。这对中古时代的汇编程序写作者来说应该很熟悉,它是从PC相容机种的ROM BIOS服务中所获得的值(OEM指的是PC的原始设备制造商(Original Equipment Manufacturer)及其与「IBM标准」同步的内容)。在此我们不需要更多的信息。除非需要依赖实际键盘布局的样貌,不然Windows程序可以忽略掉几乎所有的OEM扫描码信息,参见第二十二章的程序KBMIDI。
扩充键旗标
如果按键结果来自IBM增强键盘的附加键之一,那么扩充键旗标为1(IBM增强型键盘有101或102个键。功能键在键盘顶端,光标移动键从数字键盘中分离出来,但在数字键盘上还保留有光标移动键的功能)。对键盘右端的Alt和Ctrl键,以及不是数字键盘那部分的光标移动键(包括Insert和Delete键)、数字键盘上的斜线(/)和Enter键以及Num Lock键等,此旗标均被设定为1。Windows程序通常忽略扩充键旗标。
内容代码
右按键时,假如同时压下ALT键,那么内容代码为1。对WM_SYSKEYUP与WM_SYSKEYDOWN而言,此位总视为1;而对WM_SYSKEYUP与WM_KEYDOW消息而言,此位为0。除了两个之外:
- 如果活动窗口最小化了,则它没有输入焦点。这时候所有的按键都会产生WM_SYSKEYUP和WM_SYSKEYDOWN消息。如果Alt键未被按下,则内容代码字段被设定为0。Windows使用WM_SYSKEYUP和WM_SYSKEYDOWN消息,从而使最小化了的活动窗口不处理这些按键。
- 对于一些外国语文(非英文)键盘,有些字符是通过Shift、Ctrl或者Alt键与其它键相组合而产生的。这时内容代码为1,但是此消息并非系统按键消息。
键的先前状态
如果在此之前键是释放的,则键的先前状态为0,否则为1。对WM_KEYUP或者WM_SYSKEYUP消息,它总是设定为1;但是对WM_KEYDOWN或者WM_SYSKEYDOWN消息,此位可以为0,也可以为1。如果为1,则表示该键是自动重复功能所产生的第二个或者后续消息。
转换状态
如果键正被按下,则转换状态为0;如果键正被释放,则转换状态为1。对WM_KEYDOWN或者WM_SYSKEYDOWN消息,此字段为0;对WM_KEYUP或者WM_SYSKEYUP消息,此字段为1。
位移状态
在处理按键消息时,您可能需要知道是否按下了位移键(Shift、Ctrl和Alt)或开关键(Caps Lock、Num Lock和Scroll Lock)。通过呼叫GetKeyState函数,您就能获得此信息。例如:
iState = GetKeyState (VK_SHIFT) ; //如果按下了Shift,则iState值为负(即设定了最高位置位)。如果Caps Lock键打开,则从 iState = GetKeyState (VK_CAPITAL) ; //传回的值低位被设为1。此位与键盘上的小灯保持一致。 /* 您在使用GetKeyState时,会带有虚拟键码VK_SHIFT、VK_CONTROL和VK_MENU(在说明Alt键时呼叫)。使用GetKeyState时,您也可以用下面的标识符来确定按下的Shift、Ctrl或Alt键是左边的还是右边的:VK_LSHIFT、VK_RSHIFT、VK_LCONTROL、VK_RCONTROL、VK_LMENU、VK_RMENU。这些标识符只用于GetKeyState和GetAsyncKeyState 使用虚拟键码VK_LBUTTON、VK_RBUTTON和VK_MBUTTON,您也可以获得鼠标键的状态。 注意GetKeyState的使用,它并非实时检查键盘状态,而只是检查直到目前为止正在处理的消息的键盘状态。多数情况下,这正符合您的要求。如果您需要确定使用者是否按下了Shift-Tab,请在处理Tab键的WM_KEYDOWN消息时呼叫GetKeyState,带有参数VK_SHIFT。如果GetKeyState传回的值为负,那么您就知道在按下Tab键之前按下了Shift键。并且,如果在您开始处理Tab键之前,已经释放了Shift键也没有关系。您知道,在按下Tab键的时候Shift键是按下的。 */
使用示范代码:
//对于 IParam 的读取代码 (片段) //...... TCHAR * szFormat[2] = { TEXT ("%-13s %3d %-15s%c%6u %4d %3s %3s %4s %4s"), TEXT ("%-13s 0x%04X%1s%c %6u %4d %3s %3s %4s %4s") } ; //...... TextOut (hdc, 0, (cyClient / cyChar - 1 - i)*cyChar,szBuffer, wsprintf (szBuffer, szFormat [iType], szMessage [pmsg[i].message - WM_KEYFIRST], pmsg[i].wParam, (PTSTR) (iType ? TEXT (" ") : szKeyName), (TCHAR) (iType ? pmsg[i].wParam : ' '), LOWORD (pmsg[i].lParam), HIWORD (pmsg[i].lParam) & 0xFF, 0x01000000 & pmsg[i].lParam ? szYes : szNo, 0x20000000 & pmsg[i].lParam ? szYes : szNo, 0x40000000 & pmsg[i].lParam ? szDown : szUp, 0x80000000 & pmsg[i].lParam ? szUp:szDown));
//......
鼠标:
发送消息:鼠标定义了21种消息,不过,其中有11个消息和显示区域无关(下面称之为「非显示区域」消息),Windows程序经常忽略这些消息。
【显示区域相关】:WM_MOUSEMOVE +
键 | 按下 | 释放 | 按下(双键) |
左 | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_LBUTTONDBLCLK |
中 | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_MBUTTONDBLCLK |
右 | WM_RBUTTONDOWN | WM_RBUTTONUP | WM_RBUTTONDBLCLK |
【显示区域无关】:WM_NCMOUSEMOVE +
键 | 按下 | 释放 | 按下(双击) |
左 | WM_NCLBUTTONDOWN | WM_NCLBUTTONUP | WM_NCLBUTTONDBLCLK |
中 | WM_NCMBUTTONDOWN | WM_NCMBUTTONUP | WM_NCMBUTTONDBLCLK |
右 | WM_NCRBUTTONDOWN | WM_NCRBUTTONUP | WM_NCRBUTTONDBLCLK |
+ WM_NCHITTEST
参数wParam:【显示区域】wParam的值指示鼠标按键以及Shift和Ctrl键的状态
【非显示区域】wParam参数指明移动或者按鼠标按键的非显示区域。它设定为WINUSER.H中定义的以HT开头的标识符之一(HT表示「命中测试」)。
参数lParam: 【显示区域】Client鼠标的位置:低字组为x坐标,高字组为y坐标
【非显示区域】屏幕鼠标的位置:低字组为x坐标,高字组为y坐标
使用小妙招(1):
wParam的值指示鼠标按键以及Shift和Ctrl键的状态。您可以使用表头文件WINUSER.H中定义的位屏蔽来测试wParam。MK前缀代表「鼠标按键」。
MK_LBUTTON | 按下左键 |
MK_MBUTTON | 按下中键 |
MK_RBUTTON | 按下右键 |
MK_SHIFT | 按下Shift键 |
MK_CONTROL | 按下Ctrl键 |
wparam & MK_SHIFT 是TRUE(非0),您就知道当左键按下时也按下了Shift键。
妙招实例:
//实现 左键 拖动时 画点 case WM_MOUSEMOVE: if (wParam & MK_LBUTTON && iCount < 1000) { pt[iCount ].x = LOWORD (lParam) ; pt[iCount++].y = HIWORD (lParam) ; hdc = GetDC (hwnd) ; SetPixel(hdc,LOWORD(lParam),HIWORD(lParam), 0) ; ReleaseDC (hwnd, hdc) ; } return 0 ;
使用小妙招(2):
Windows函数GetKeyState(在“指导书”第六章中介绍过)可以使用虚拟键码VK_LBUTTON、VK_RBUTTON、VK_MBUTTON、VK_SHIFT和VK_CONTROL来传回鼠标按键与Shift键的状态。
使用小妙招(3):
检测双击事件需要在创建窗口时指定参数: wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ;
使用小妙招(4):
对于非显示区域的鼠标事件、以及下文将提到的“鼠标拦截”,得到的是整个屏幕的图素坐标,要得到其对应的可以直接使用的Client坐标,
您可以用两个Windows函数将屏幕坐标转换为显示区域坐标或者反之:
ScreenToClient (hwnd, &pt) ; ClientToScreen (hwnd, &pt) ;
使用小妙招(5):
21个鼠标消息中的20个,最后一个消息是WM_NCHITTEST,它代表「非显示区域命中测试」。此消息优先于所有其它的显示区域和非显示区域鼠标消息。lParam参数含有鼠标位置的x和y屏幕坐标,
wParam参数没有用。
Windows应用程序通常把这个消息传送给DefWindowProc,然后Windows用WM_NCHITTEST消息产生与鼠标位置相关的所有其它鼠标消息。对于非显示区域鼠标消息,在处理WM_NCHITTEST时,从DefWindowProc传回的值将成为鼠标消息中的wParam参数,这个值可以是任意非显示区域鼠标消息的wParam值再加上以下内容:
HTCLIENT HTNOWHERE HTTRANSPARENT HTERROR | 显示区域 不在窗口中 窗口由另一个窗口覆盖 使DefWindowProc产生警示用的哔声 |
【猜测】【windows 来处理的话,自动处理了坐标转换等等;其返回的HT_... 就可以决定接下来产生的下一个消息:
“相当”于:
if(inClient) SendClientMessage( hwnd,MOUSETYPE(MK_TEST),wParam(Shift...),lParam(PosTranslate));
else SendClientMessage( hwnd,MOUSETYPE2(MK_TEST),wParam(HTClient...),lParam(PosOrigon));】
所以:
case WM_NCHITTEST: return (LRESULT) HTNOWHERE ;
就可以有效地禁用您窗口中的所有显示区域和非显示区域鼠标消息。
使用小妙招(6):
对于一些有需要的应用,可以使用 鼠标拦截
SetCapture (hwnd) ; ReleaseCapture () ; /* 在32位的Windows中,鼠标拦截要比在以前的Windows版本中有多一些限制。特别是,如果鼠标被拦截,而鼠标按键目前并未被按下,并且鼠标光标移到了另一个窗口上,
那么将不是由拦截鼠标的那个窗口,而是由光标下面的窗口来接收鼠标消息。对于防止一个程序在拦截鼠标之后不释放它而引起整个系统的混乱,这是必要的。 */