📄 7. 鼠标.txt
字号:
鼠标
智能中国——游戏组 整理编译
--------------------------------------------------------------------------------
鼠标是有一个或多个键的定位设备。虽然也可以使用诸如触摸画面和光笔之类的输入设备,但是只有鼠标以及常用在膝上型计算机上的轨迹球等才是渗透了PC市场的唯一输入设备。
情况并非总是如此。当然,Windows的早期开发人员认为他们不应该要求使用者为了执行其产品而必须买只鼠标。因此,他们将鼠标作为一种选择性的附加设备,而为Windows中的所有操作以及applet提供一种键盘接口(例如,查看Windows小算盘程序的在线说明信息,可以看到每个按钮都提供了一个同等功效的键盘操作方式)。第三方软件开发人员使用键盘接口来提供与鼠标操作相同的功能,这本书以前的版本也是这么做的。
理论上来说,现在的Windows需要鼠标。至少,一些消息框是这样讲的。当然,您也可以拔下鼠标,而且Windows仍然可以执行良好(只有消息框会提示您没有连接鼠标)。试图不用鼠标来使用Windows就像用脚趾来弹钢琴一样(至少在最初的一段时间里是这样),但您依然可以这样做。正因为如此,我还是喜欢为鼠标功能提供键盘操作。打字员尤其喜欢让他们的手保持在键盘上,并且我认为每个人都有在杂乱的桌上找不到鼠标,或者鼠标移动不灵敏的经验。使用键盘通常不需要花费更多的精力和努力,并且为喜欢使用键盘的人提供更多的功能。
我们通常认为,键盘便于输入和操作文字数据,而鼠标则便于画图和操作图形对象。实际上,本章大多数的范例程序都画了一些图形,并且用到了我们在第五章所学到的知识。
鼠标基础
Windows 98能支持单键、双键或者三键鼠标,也可以使用摇杆或者光笔来仿真单键鼠标。早期,由于许多使用者都有单键鼠标,所以Windows应用程序总是避免使用双键或三键鼠标。不过,由于双键鼠标已经成为事实上的标准,因此不使用第二个键的传统已经不再合理了。当然,第二个鼠标按键是用于启动一个「快捷菜单」,亦即出现在普通菜单列之外的窗口中菜单,或者用于特殊的拖曳操作(拖曳将在后面加以解释)。然而,程序不能依赖双键鼠标。
理论上,您可以用我们的老朋友GetSystemMetrics函数来确认鼠标是否存在:
fMouse = GetSystemMetrics (SM_MOUSEPRESENT) ;
如果已经安装了鼠标,fMouse将传回TRUE(非0);如果没有安装,则传回0。然而,在Windows 98中,不论鼠标是否安装,此函数都将传回TRUE 。在Microsoft Windows NT中,它可以正常工作。
要确定所安装鼠标其上按键的个数,可使用
cButtons = GetSystemMetrics (SM_CMOUSEBUTTONS) ;
如果没有安装鼠标,那么函数将传回0。然而,在Windows 98下,如果没有安装鼠标,此函数将传回2。
习惯用左手的使用者可以使用Windows的「控制台」来切换鼠标按键。虽然应用程序可以通过在GetSystemMetrics中使用SM_SWAPBUTTON参数来确定是否进行了这种切换,但通常没有这个必要。由食指触发的键被认为是左键,即使事实上是位于鼠标的右边。不过,在一个教育训练程序中,您可能想在屏幕上画一个鼠标,在这种情况下,您可能想知道鼠标按键是否被切换过了。
您可以在「控制台」中设定鼠标的其它参数,例如双击速度。从Windows应用程序,通过使用SystemParametersInfo函数可以设定或获得此项信息。
一些简单的定义
当Windows使用者移动鼠标时,Windows在显示器上移动一个称为「鼠标光标」的小位图。鼠标光标有一个指向显示器上精确位置的单图素「热点」。当我提到鼠标光标在屏幕上的位置时,指的是热点的位置。
Windows支持几种预先定义的鼠标光标,程序可以使用这些光标。最常见的是称为IDC_ARROW的斜箭头(在WINUSER.H中定义)。热点在箭头的顶端。IDC_CROSS光标(在本章后面的BLOKOUT程序中有用到)的热点在十字交叉线的中心。IDC_WAIT光标是一个沙漏,通常用于指示程序正在执行。程序写作者也可以设计自己的光标。我们将在第十章学习设计方法。在定义窗口类别结构时指定特定窗口的内定光标,例如:
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
下面是一些描述鼠标按键动作的术语:
Clicking按下并放开一个鼠标按键。
Double-clicking快速按下并放开鼠标按键两次。
Dragging按住鼠标按键并移动鼠标。
对三键鼠标来说,三个键分别称为左键、中键、右键。在Windows表头文件中定义的与鼠标有关的标识符使用缩写LBUTTON、MBUTTON和RBUTTON。双键鼠标只有左键与右键,单键鼠标只有一个左键。
鼠标(Mouse)的复数
现在,为了展现我的勇气,我将面对输入设备最难辩的争论话题:什么是「mouse」的复数。虽然每个人都知道多只啮齿动物称为mice,似乎没有人对该如何称呼多个输入设备有最后的答案。不管「mice」或「mouse」听起来都不对劲。我惯常参考的《American Heritage Dictionary of the English Language》第三版则只字未提。
《Wired style:Principles of English Usage in the Digital Age》(HardWired, 1996)指出「mouse」比较好,以避免与啮齿动物搞混。在1964发明鼠标的Doug Engelbart对此争议也帮不上忙。我曾经问过他mouse的复数是什么,他说我不知道。
最后,高权威的Microsoft Manual of Style for Technical Publications告诉我们「避免使用复数mice。假如你必须提到多只mouse,使用mouse devices」。这听起来像是在逃避问题,但当一切听起来都不对劲时,它确实是个明智的忠告了。事实上,大部分需要mouse复数的句子都能重新修改来避开。例如,试着说"People use the almost as much as keyboard",而不是"Pople use mice almost as much as keyboards"。
显示区域鼠标消息
在前一章中您已经看到,Windows只把键盘消息发送给拥有输入焦点的窗口。鼠标消息与此不同:只要鼠标跨越窗口或者在某窗口中按下鼠标按键,那么窗口消息处理程序就会收到鼠标消息,而不管该窗口是否活动或者是否拥有输入焦点。Windows为鼠标定义了21种消息,不过,其中有11个消息和显示区域无关(下面称之为「非显示区域」消息),Windows程序经常忽略这些消息。
当鼠标移过窗口的显示区域时,窗口消息处理程序收到WM_MOUSEMOVE消息。当在窗口的显示区域中按下或者释放一个鼠标按键时,窗口消息处理程序会接收到下面这些消息:
表7-1
键
按下
释放
按下(双键)
左
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
中
WM_MBUTTONDOWN
WM_MBUTTONUP
WM_MBUTTONDBLCLK
右
WM_RBUTTONDOWN
WM_RBUTTONUP
WM_RBUTTONDBLCLK
只有对三键鼠标,窗口消息处理程序才会收到MBUTTON消息;只有对双键或者三键鼠标,才会接收到RBUTTON消息。只有当定义的窗口类别能接收DBLCLK(双击)消息,窗口消息处理程序才能接收到这些消息(请参见本章中「双击鼠标按键」一节)。
对于所有这些消息来说,其lParam值均含有鼠标的位置:低字组为x坐标,高字组为y坐标,这两个坐标是相对于窗口显示区域左上角的位置。您可以用LOWORD和HIWORD宏来提取这些值:
x = LOWORD (lParam) ;
y = HIWORD (lParam) ;
wParam的值指示鼠标按键以及Shift和Ctrl键的状态。您可以使用表头文件WINUSER.H中定义的位屏蔽来测试wParam。MK前缀代表「鼠标按键」。
MK_LBUTTON
按下左键
MK_MBUTTON
按下中键
MK_RBUTTON
按下右键
MK_SHIFT
按下Shift键
MK_CONTROL
按下Ctrl键
例如,如果收到了WM_LBUTTONDOWN消息,而且值
wparam & MK_SHIFT
是TRUE(非0),您就知道当左键按下时也按下了Shift键。
当您把鼠标移过窗口的显示区域时,Windows并不为鼠标的每个可能的图素位置都产生一个WM_MOUSEMOVE消息。您的程序接收到WM_MOUSEMOVE消息的次数,依赖于鼠标硬件,以及您的窗口消息处理程序在处理鼠标移动消息时的速度。换句话说,Windows不能用未处理的WM_MOUSEMOVE消息来填入消息队列。当您执行下面将描述的CONNECT程序时,您将会更了解WM_MOUSEMOVE消息处理的速率。
如果您在非活动窗口的显示区域中按下鼠标左键,Windows将把活动窗口改为在其中按下鼠标按键的窗口,然后把WM_LBUTTONDOWN消息送到该窗口消息处理程序。当窗口消息处理程序得到WM_LBUTTONDOWN消息时,您的程序就可以安全地假定该窗口是活动化的了。不过,您的窗口消息处理程序可能在未接收到WM_LBUTTONDOWN消息的情况下先接收到了WM_LBUTTONUP的消息。如果在一个窗口中按下鼠标按键,然后移动到使用者窗口释放它,就会出现这种情况。类似的情况,当鼠标按键在另一个窗口中被释放时,窗口消息处理程序只能接收到WM_LBUTTONDOWN消息,而没有相应的WM_LBUTTONUP消息。
这些规则有两个例外:
窗口消息处理程序可以「拦截鼠标」并且连续地接收鼠标消息,即使此时鼠标在该窗口显示区域之外。您将在本章的后面学习如何拦截鼠标。
如果正在显示一个系统模态消息框或者系统模态对话框,那么其它程序就不能接收鼠标消息。当系统模态消息框或者对话框活动时,禁止切换到其它窗口或者程序。一个显示系统模态消息框的例子,是当您关闭Windows时。
简单的鼠标处理:一个例子
程序7-1中所示的CONNECT程序能作一些简单的鼠标处理,使您对Windows如何向您的程序发送鼠标消息有一些体会。
程序7-1 CONNECT
CONNECT.C
/*--------------------------------------------------------------------------
CONNECT.C -- Connect-the-Dots Mouse Demo Program
(c) Charles Petzold, 1998
---------------------------------------------------------------------------*/
#include <windows.h>
#define MAXPOINTS 1000
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("Connect") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Connect-the-Points Mouse Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
static POINT pt[MAXPOINTS] ;
static int iCount ;
HDC hdc ;
in i, j ;
PAINTSTRUCT ps ;
switch (message)
{
case WM_LBUTTONDOWN:
iCount = 0 ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
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 ;
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 - 1 ; i++)
for (j = i + 1 ; j < iCount ; j++)
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -