第六章

窗口消息处理程序通过拦截WM_SETFOCUSWM_KILLFOCUS消息来判定它的窗口何时拥有输入焦点。WM_SETFOCUS指示窗口正在得到输入焦点,WM_KILLFOCUS表示窗口正在失去输入焦点。我将在本章的后面详细说明这些消息。

WM_SYSKEYDOWNWM_SYSKEYUP中的「SYS」代表「系统」,它表示该按键对Windows比对Windows应用程序更加重要。WM_SYSKEYDOWNWM_SYSKEYUP消息经常由与Alt相组合的按键产生

WM_KEYDOWNWM_KEYUP消息通常是在按下或者释放不带Alt键的键时产生的,您的程序可以使用或者忽略这些消息,Windows本身并不处理这些消息。

对所有四类按键消息,wParam是虚拟键代码,表示按下或释放的键,而lParam则包含属于按键的其它数据。

虚拟键码

虚拟键码保存在WM_KEYDOWNWM_KEYUPWM_SYSKEYDOWNWM_SYSKEYUP消息的wParam参数中。此代码标识按下或释放的键。

lParam信息
重复计数  0-15

重复计数是该消息所表示的按键次数,大多数情况下,重复计数设定为1。不过,如果按下一个键之后,您的窗口消息处理程序不够快,以致不能处理自动重复速率(您可以在「控制台」的「键盘」中进行设定)下的按键消息,Windows就把几个WM_KEYDOWN或者WM_SYSKEYDOWN消息组合到单个消息中,并相应地增加重复计数。WM_KEYUPWM_SYSKEYUP消息的重复计数总是为1

因为重复计数大于1指示按键速率大于您程序的处理能力,所以您也可能想在处理键盘消息时忽略重复计数。几乎每个人都有文书处理或执行电子表格时画面卷过头的经验,因为多余的按键堆满了键盘缓冲区,所以当程序用一些时间来处理每一次按键时,如果忽略您程序中的重复计数,就能够解决此问题。

OEM扫描码   16-23

OEM扫描码是由硬件(键盘)产生的代码。这对中古时代的汇编程序写作者来说应该很熟悉,它是从PC相容机种的ROM BIOS服务中所获得的值(OEM指的是PC的原始设备制造商(Original Equipment Manufacturer)及其与「IBM标准」同步的内容)。在此我们不需要更多的信息。除非需要依赖实际键盘布局的样貌,不然Windows程序可以忽略掉几乎所有的OEM扫描码信息,

扩充键旗标 24

如果按键结果来自IBM增强键盘的附加键之一,那么扩充键旗标为1IBM增强型键盘有101102个键。功能键在键盘顶端,光标移动键从数字键盘中分离出来,但在数字键盘上还保留有光标移动键的功能)。对键盘右端的AltCtrl键,以及不是数字键盘那部分的光标移动键(包括InsertDelete键)、数字键盘上的斜线(/)和Enter键以及Num Lock键等,此旗标均被设定为1Windows程序通常忽略扩充键旗标。

内容代码  29

右按键时,假如同时压下ALT键,那么内容代码为1。对WM_SYSKEYUPWM_SYSKEYDOWN而言,此位总视为1;而对WM_SYSKEYUPWM_KEYDOW消息而言,此位为0。除了两个之外:

· 如果活动窗口最小化了,则它没有输入焦点。这时候所有的按键都会产生WM_SYSKEYUPWM_SYSKEYDOWN消息。如果Alt键未被按下,则内容代码字段被设定为0Windows使用WM_SYSKEYUPWM_SYSKEYDOWN消息,从而使最小化了的活动窗口不处理这些按键。
 

· 对于一些外国语文(非英文)键盘,有些字符是通过ShiftCtrl或者Alt键与其它键相组合而产生的。这时内容代码为1,但是此消息并非系统按键消息。

键的先前状态                  30

如果在此之前键是释放的,则键的先前状态为0,否则为1。对WM_KEYUP或者WM_SYSKEYUP消息,它总是设定为1;但是对WM_KEYDOWN或者WM_SYSKEYDOWN消息,此位可以为0,也可以为1。如果为1,则表示该键是自动重复功能所产生的第二个或者后续消息。

转换状态              31

如果键正被按下,则转换状态为0;如果键正被释放,则转换状态为1。对WM_KEYDOWN或者WM_SYSKEYDOWN消息,此字段为0;对WM_KEYUP或者WM_SYSKEYUP消息,此字段为1

位移状态

在处理按键消息时,您可能需要知道是否按下了位移键(ShiftCtrlAlt)或开关键(Caps LockNum LockScroll Lock)。通过呼叫GetKeyState函数,您就能获得此信息。例如:

iState = GetKeyState (VK_SHIFT) ;

如果按下了Shift,则iState值为负(即设定了最高位置位)。如果Caps Lock键打开,则从

iState = GetKeyState (VK_CAPITAL) ;

通常,您在使用GetKeyState时,会带有虚拟键码VK_SHIFTVK_CONTROLVK_MENU(在说明Alt键时呼叫)。使用GetKeyState时,您也可以用下面的标识符来确定按下的ShiftCtrlAlt键是左边的还是右边的:VK_LSHIFTVK_RSHIFTVK_LCONTROLVK_RCONTROLVK_LMENUVK_RMENU。这些标识符只用于GetKeyStateGetAsyncKeyState(下面将详细说明)。

请注意GetKeyState的使用,它并非实时检查键盘状态,而只是检查直到目前为止正在处理的消息的键盘状态。多数情况下,这正符合您的要求。如果您需要确定使用者是否按下了Shift-Tab,请在处理Tab键的WM_KEYDOWN消息时呼叫GetKeyState,带有参数VK_SHIFT。如果GetKeyState传回的值为负,那么您就知道在按下Tab键之前按下了Shift键。并且,如果在您开始处理Tab键之前,已经释放了Shift键也没有关系。您知道,在按下Tab键的时候Shift键是按下的。

插入符号函数

主要有五个插入符号函数:

· CreateCaret 建立与窗口有关的插入符号
 

· SetCaretPos 在窗口中设定插入符号的位置
 

· ShowCaret 显示插入符号
 

· HideCaret 隐藏插入符号
 

· DestroyCaret 撤消插入符号

另外还有取得插入符号目前位置(GetCaretPos)和取得以及设定插入符号闪烁时间(GetCaretBlinkTimeSetCaretBlinkTime)的函数。

通过处理WM_SETFOCUSWM_KILLFOCUS消息,程序就可以确定它是否有输入焦点。正如名称所暗示的,窗口消息处理程序在有输入焦点的时候接收到WM_SETFOCUS消息,失去输入焦点的时候接收到WM_KILLFOCUS消息。这些消息成对出现:窗口消息处理程序在接收到WM_KILLFOCUS消息之前将一直接收到WM_SETFOCUS消息,并且在窗口打开期间,此窗口总是接收到相同数量的WM_SETFOCUSWM_KILLFOCUS消息。

使用插入符号的主要规则很简单:窗口消息处理程序在WM_SETFOCUS消息处理期间呼叫CreateCaret,在WM_KILLFOCUS消息处理期间呼叫DestroyCaret

这里还有几条其它规则:插入符号刚建立时是隐蔽的。如果想使插入符号可见,那么您在呼叫CreateCaret之后,窗口消息处理程序还必须呼叫ShowCaret。另外,当窗口消息处理程序处理一条非WM_PAINT消息而且希望在窗口内绘制某些东西时,它必须呼叫HideCaret隐藏插入符号。在绘制完毕后,再呼叫ShowCaret显示插入符号。HideCaret的影响具有累积效果,如果多次呼叫HideCaret而不呼叫ShowCaret,那么只有呼叫ShowCaret相同次数时,才能看到插入符号。

第七章

理论上,您可以用我们的老朋友GetSystemMetrics函数来确认鼠标是否存在:

fMouse = GetSystemMetrics (SM_MOUSEPRESENT) ;

要确定所安装鼠标其上按键的个数,可使用

cButtons = GetSystemMetrics (SM_CMOUSEBUTTONS) ;

对于所有这些消息来说,其lParam值均含有鼠标的位置:低字组为x坐标,高字组为y坐标,这两个坐标是相对于窗口显示区域左上角的位置。您可以用LOWORDHIWORD宏来提取这些值:

x = LOWORD (lParam) ;

y = HIWORD (lParam) ;

wParam的值指示鼠标按键以及ShiftCtrl键的状态。

例如,如果收到了WM_LBUTTONDOWN消息,而且值wparam & MK_SHIFT TRUE(非0),您就知道当左键按下时也按下了Shift键。

如果希望您的窗口消息处理程序能够收到双按键的鼠标消息,那么在呼叫RegisterClass初始化窗口类别结构时,必须在窗口风格中包含CS_DBLCLKS标识符:

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ;

对非显示区域鼠标消息,wParamlParam参数与显示区域鼠标消息的wParamlParam参数不同。wParam参数指明移动或者按鼠标按键的非显示区域。它设定为WINUSER.H中定义的以HT开头的标识符之一(HT表示「命中测试」)。

lParam参数的低位wordx坐标,高位wordy坐标,但是,它们是屏幕坐标,而不是像显示区域鼠标消息那样指的是显示区域坐标。对屏幕坐标,显示器左上角的xy的值为0。当往右移时x的值增加,往下移时y的值增加(见图7-2)。

您可以用两个Windows函数将屏幕坐标转换为显示区域坐标或者反之:

ScreenToClient (hwnd, &pt) ;l

Getcursorpos(&pt);得到光标的位置。

Setcursorpos(x,y);

您应该能回忆起每一个子窗口有唯一的子窗口ID,该ID在呼叫CreateWindow建立窗口时定义。在CHECKER3中,此ID是矩形的xy位置的组合。一个程序可以通过下面的呼叫来获得一个特定子窗口的子窗口ID

idChild = GetWindowLong (hwndChild, GWL_ID) ;

下面的函数也有同样的功能:

idChild = GetDlgCtrlID (hwndChild) ;

正如函数名称所表示的,它主要用于对话框和控制窗口。如果您知道父窗口的句柄和子窗口ID,此函数也可以获得子窗口的句柄:

hwndChild = GetDlgItem (hwndParent, idChild) ;

拦截鼠标
case WM_LBUTTONDOWN:

SetCapture(hWnd);

flag=1;

GetCursorPos(&pt2);

ScreenToClient(hWnd,&pt2);    //捕获鼠标的得到的是屏幕坐标

拦截鼠标要比放置一个老鼠夹子容易一些,您只要呼叫:

SetCapture (hwnd) ;

在这个函数呼叫之后,Windows将所有鼠标消息发给窗口句柄为hwnd的窗口消息处理程序。之后收到鼠标消息都是以显示区域消息的型态出现,即使鼠标正在窗口的非显示区域。lParam参数将指示鼠标在显示区域坐标中的位置。不过,当鼠标位于显示区域的左边或者上方时,这些xy坐标可以是负的。当您想释放鼠标时,呼叫:

ReleaseCapture () ;    

ClientToScreen (hwnd, &pt) ;

第八章

定时器的使用:三种方法

方法一
这是最方便的一种方法,它让WindowsWM_TIMER消息发送到应用程序的正常窗口消息处理程序中,SetTimer呼叫如下所示:

SetTimer (hwnd, 1, uiMsecInterval, NULL) ;

您可以通过呼叫

KillTimer (hwnd, 1) ;在任何时刻停止WM_TIMER消息(即使正在处理WM_TIMER消息)。此函数的第二个参数是SetTimer呼叫中所用的同一个定时器ID。在终止程序之前,您应该响应WM_DESTROY消息停止任何活动的定时器。

当您的窗口消息处理程序收到一个WM_TIMER消息时,wParam参数等于定时器的ID值(上述情形为1),lParam参数为0。如果需要设定多个定时器,那么对每个定时器都使用不同的定时器IDwParam的值将随传递到窗口消息处理程序的WM_TIMER消息的不同而不同。

方法二

设定定时器的第一种方法是把WM_TIMER消息发送到通常的窗口消息处理程序,而第二种方法是让Windows直接将定时器消息发送给您程序的另一个函数。

接收这些定时器消息的函数被称为「callback」函数,这是一个在您的程序之中但是由Windows呼叫的函数。您先告诉Windows此函数的地址,然后Windows呼叫此函数。这看起来也很熟悉,因为程序的窗口消息处理程序实际上也是一种callback函数。当注册窗口类别时,要将函数的地址告诉Windows,当发送消息给程序时,Windows会呼叫此函数。

SetTimer (hwnd, iTimerID, iMsecInterval, TimerProc) ;

VOID CALLBACK TimerProc (  HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)         

{                     

处理WM_TIMER消息         

}

方法三
设定定时器的第三种方法类似于第二种方法,只是传递给SetTimerhwnd参数被设定为NULL,并且第二个参数(通常为定时器ID)被忽略了,最后,此函数传回定时器ID

iTimerID = SetTimer (NULL, 0, wMsecInterval, TimerProc) ;

如果没有可用的定时器,那么从SetTimer传回的iTimerID值将为NULL

KillTimer的第一个参数(通常是窗口句柄)也必须为NULL,定时器ID必须是SetTimer的传回值:

KillTimer (NULL, iTimerID) ;

传递给TimerProc定时器函数的hwnd参数也必须是NULL。这种设定定时器的方法很少被使用。如果在您的程序在不同时刻有一系列的SetTimer呼叫,而又不希望追踪您已经用过了那些定时器ID,那么使用此方法是很方便的。

第九章

通过呼叫GetParent,子窗口消息处理程序能确定其父窗口的窗口句柄:

hwndParent = GetParent (hwnd) ;

其中,hwnd是子窗口的窗口句柄。它可以向其父窗口消息处理程序发送消息:

SendMessage (hwndParent, message, wParam, lParam) ;

您可以呼叫一次CreateWindow来建立一个子窗口,并通过呼叫MoveWindow来调整子窗口的位置和尺寸。

用的是GetDialogBaseUnits函数来获得内定字体字符的宽度和高度。这是对话框用来获得文字尺寸的函数。此函数传回一个32位的值,其中低字组表示宽度,高字组表示高度。由于GetDialogBaseUnits传回的值与从GetTextMetrics获得的值大致上相同,但GetDialogBaseUnits有时使用起来会更方便些,而且能够与对话框控件更好地保持一致。

对每个子窗口,它的子窗口ID参数应该各不相同。在处理来自子窗口的WM_COMMAND消息时,ID帮助您的窗口消息处理程序识别出相应的子窗口。注意子窗口ID是作为CreateWindow的一个参数传递的,该参数通常用于指定程序的菜单,因此子窗口ID必须被强制转换为HMENU

id = GetWindowLong (hwndChild, GWL_ID) ;

id = GetDlgCtrlID (hwndChild) 

您可以通过给窗口发送BM_SETSTATE消息来仿真按钮闪动。以下的操作将导致按钮被按下:

SendMessage (hwndButton, BM_SETSTATE, 1, 0) ;      

下面的呼叫使按钮恢复正常:

SendMessage (hwndButton, BM_SETSTATE, 0, 0) ;

您也可以向按键发送BM_GETSTATE消息,子窗口控件传回按钮目前的状态:如果按钮被按下,则传回TRUE;如果按钮处于正常状态,则传回FALSE。但是,绝大多数应用并不需要这一消息。因为按

钮不保留任何开/关信息,所以BM_SETCHECK消息和BM_GETCHECK消息不会被用到

复选框最常用的两种样式是BS_CHECKBOXBS_AUTOCHECKBOX。在使用BS_CHECKBOX时,您需要自己向该控件发送BM_SETCHECK消息来设定勾选标记。wParam参数设1时设定勾选标记,设0时清除勾选标记。通过向该控件发送BM_GETCHECK消息,您可以得到该复选框的目前状态。在处理来自控件的WM_COMMAND消息时,您可以用如下的指令来翻转X标记:

SendMessage ((HWND) lParam, BM_SETCHECK, (WPARAM)!SendMessage ((HWND) lParam, BM_GETCHECK, 0, 0), 0) ;

发送BM_SETCHECK消息来初始化带勾选标记的BS_CHECKBOX复选框:

SendMessage (hwndButton, BM_SETCHECK, 1, 0) ;

当您需要按钮目前的状态时,可以向控件发送BM_GETCHECK消息:

iCheck = (int) SendMessage (hwndButton, BM_GETCHECK, 0, 0) ;

如果该按钮被选中,则iCheck的值为TRUE或者非零数;如果按钮末被选中,则iCheck的值为FALSE0

单选按钮的形状是一个圆圈,而不是方框,除此之外,它非常像复选框。圆圈内的加重圆点表示该单选按钮已经被选中。单选按钮有窗口样式BS_RADIOBUTTONBS_AUTORADIOBUTTON两种,但是后者只用于对话框。

当您收到来自单选按钮的WM_COMMAND消息时,应该向它发送wParam等于1BM_SETCHECK消息来显示其选中状态:

SendMessage (hwndButton, BM_SETCHECK, 1, 0) ;      

对同组中的其它所有单选按钮,您可以通过向它们发送wParam等于0BM_SETCHECK消息来显示其未选中状态:

SendMessage (hwndButton, BM_SETCHECK, 0, 0) ;

改变按钮文字

您可以通过SetWindowText来改变按钮(或者其它任何窗口)内的文字:

SetWindowText (hwnd, pszString) ;

您也可以取得窗口目前的文字:

iLength = GetWindowText (hwnd, pszBuffer, iMaxLength) ;      

iMaxLength指定复制到pszBuffer指向的缓冲区中的最大字符数。该函数传回复制的字符数。您可以首先通过下面的呼叫来获得特定文字的长度:

iLength = GetWindowTextLength (hwnd) ;

如果在建立子窗口时,您没有将WS_VISIBLE包含在窗口类别中,那么直到呼叫ShowWindow时子窗口才会被显示出来:

ShowWindow (hwndChild, SW_SHOWNORMAL)       

如果您将WS_VISIBLE包含在窗口类别中,就没有必要呼叫ShowWindow。但是,您可以通过呼叫ShowWindow将子窗口隐藏起来:

ShowWindow (hwndChild, SW_HIDE) ;   

您可以通过下面的呼叫来确定子窗口是否可见:

IsWindowVisible (hwndChild) ;     

您也可以使子窗口被启用或者不被启用。在内定情况下,窗口是被启用的。您可以通过下面的呼叫使窗口不被启用:

EnableWindow (hwndChild, FALSE)      

对于按钮控件,这具有使按钮字符串变成灰色的作用。按钮将不再对鼠标输入和键盘输入做出响应,这是表示按钮选项目前不可用的最好方法。

您可以通过下面的呼叫使子窗口再次被启用:

EnableWindow (hwndChild, TRUE) ;      

您还可以使用下面的呼叫来确定子窗口是否被启用:

IsWindowEnabled (hwndChild) ;

您需要使用SetTextColorSetBkColor将文字和文字背景的颜色改变为系统颜色。您可以在获得设备内容句柄之后这么做:

SetBkColor (hdc, GetSysColor (COLOR_BTNFACE))       

SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT))       

这样,显示区域背景、文字背景和文字的颜色都与按钮的颜色一致了。但是,如果当您的程序执行时,使用者改变了系统颜色,您可能要改变文字背景颜色和文字颜色。这时您可以使用下面的程序代码:

case        WM_SYSCOLORCHANGE: 

           InvalidateRect (hwnd, NULL, TRUE)  

           break ;

WM_CTLCOLORBTN消息
更好的方法(同样也只是理论上)是处理WM_CTLCOLORBTN消息,这是当子窗口即将为其显示区域着色时,由按钮控件发送给其父窗口消息处理程序的一个消息。父窗口可以利用这个机会来改变子窗口消息处理程序将用来着色的颜色

当父窗口消息处理程序收到WM_CTLCOLORBTN消息时,wParam消息参数是按钮的设备内容句柄,lParam是按钮的窗口句柄。当父窗口消息处理程序得到这个消息时,按钮控件已经获得了它的设备内容。当您的窗口消息处理程序处理一个WM_CTLCOLORBTN消息时,您必须完成以下三个动作:

· 使用SetTextColor选择设定一种文字颜色。
 

· 使用SetBkColor选择设定一种文字背景颜色。
 

· 将一个画刷句柄传回给子窗口。