⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 7. 鼠标.txt

📁 本书介绍了在Microsoft Windows 98、Microsoft Windows NT 4.0和Windows NT 5.0下程序写作的方法
💻 TXT
📖 第 1 页 / 共 5 页
字号:
        
                                  MoveToEx (hdc, pt[i].x, pt[i].y, NULL) ;
        
                                  LineTo   (hdc, pt[j].x, pt[j].y) ;
        
             }
        

                  ShowCursor (FALSE) ;
        
                  SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
        
                  EndPaint (hwnd, &ps) ;
        
                  return 0 ;
        
             
        
           case   WM_DESTROY:
        
                  PostQuitMessage (0) ;
        
                  return 0 ;
        
           }
        
           return DefWindowProc (hwnd, message, wParam, lParam) ;
        
}
        
CONNECT处理三个鼠标消息:

WM_LBUTTONDOWNCONNECT 清除显示区域。
  
WM_MOUSEMOVE如果按下左键,那么CONNECT就在显示区域中的鼠标位置处绘制一个黑点,并保存该坐标。
  
WM_LBUTTONUP CONNECT把显示区域中绘制的点与其它每个点连接起来。有时会产生一个漂亮的图形,有时则会是黑鸦鸦的一团糟(见图7-1)。
  

 



图7-1 CONNECT的屏幕显示
 

CONNECT的使用方法:把鼠标光标移动到显示区域中,按下左键,移动一下位置,释放左键。对几个构成曲线的点,CONNECT能处理得很好,方法是按住左键,快速移动鼠标,这样就可以绘制出该曲线图案。

CONNECT使用了三个简单的图形设备接口(GDI)函数,我在第五章讨论过这些函数。当鼠标左键按下时,SetPixel为每个WM_MOUSEMOVE消息绘制一个黑图素(对于高分辨率的显示器,图素几乎看不见)。画直线需要MoveToEx和LineTo函数。

如果您在释放鼠标按键之前把鼠标光标移到显示区域之外,那么CONNECT就不会连接这些点,因为它没有收到WM_LBUTTONUP消息。如果您把鼠标移回显示区域内并按下左键,那么CONNECT将清除显示区域。如果想在显示区域外释放左键后还继续进行画图,那么可以在显示区域外按下鼠标再移回显示区域中。

CONNECT最多可以保存1000个点。设点数为P,则CONNECT画的线数就等于P × (P - 1) / 2。如果有1000个点,则要绘制50万条直线,大约需要几分钟才能画完(时间的长短取决于您的硬设备)。由于Windows 98是一种优先权式多任务环境,因此您可以在这一段时间切换到别的程序中。但是,当程序正在忙的时候,您将无法对CONNECT程序做任何事(诸如移动或者缩放等)。在第二十章中,我们将讨论解决这一问题的方法。

因为CONNECT可能会花一些时间来绘制直线,因此在处理WM_PAINT消息时它将切换到沙漏光标,然后再恢复原状。这要求使用两个现有光标来呼叫SetCursor。CONNECT还呼叫两次ShowCursor,一次用TRUE参数,另一次用FALSE参数。我将在本章的后面,「使用键盘仿真鼠标」一节中更详细地讨论这些呼叫。

有时,我们使用「跟踪」这个词代表程序处理鼠标移动的方法。但是,跟踪并不意味着,程序在窗口消息处理程序中的某个循环里,不断跟随鼠标在显示器上的运动。实际上,窗口消息处理程序处理每条鼠标消息,然后迅速退出。

处理Shift键


当CONNECT接收到一个WM_MOUSEMOVE消息时,它把wParam和MK_LBUTTON进行位与(AND)运算,来确定是否按下了左键。wParam也可以用于确定Shift键的状态。例如,如果处理必须依赖于Shift和Ctrl键的状态,那么您可以使用如下所示的方法:

if (wParam & MK_SHIFT)
        
{
        
    if (wParam & MK_CONTROL)
        
           {
        
            //按下了Shift和Ctrl键
        
    }
        
    else
        
    {
        
                           //按下了Shift键
        
            }
        
    {
        
    else
        
    {
        
            if (wParam & MK_CONTROL)
        
            {
        
                           //按下了Ctrl键
        
            }
        
            else
        
            {
        
                           //Shift和Ctrl键均未按下
        
            }
        
}
        
如果您想在程序中同时使用左右键,同时如果您还希望只有单键鼠标的使用者也能使用您的程序,那么您可以这样来写作程序:Shift与左键的组合使用等效于右键。在这种情况下,对鼠标按键的处理可以采用如下所示的方法:

caseWM_LBUTTONDOWN:
        
    if (!(wParam & MK_SHIFT))
        
    {
        
                           //处理左键
        
                           return 0 ;
        
    }
        
                           // Fall through
        
case WM_RBUTTONDOWN:
        
    //处理右键
        
    return 0 ;
        
Windows函数GetKeyState(在第六章中介绍过)可以使用虚拟键码VK_LBUTTON、VK_RBUTTON、VK_MBUTTON、VK_SHIFT和VK_CONTROL来传回鼠标按键与Shift键的状态。如果GetKeyState传回负值,则说明已按下了鼠标按键或者Shift键。因为GetKeyState传回目前正在处理的鼠标按键或者Shift键的状态,所以全部状态信息与相应的消息都是同步的。但是,正如不能把GetKeyState用于尚未按下的键一样,您也不能为尚未按下的鼠标按键呼叫GetKeyState。请不要这样做:

while (GetKeyState (VK_LBUTTON) >= 0) ;  // WRONG !!!
        
只有在您呼叫GetKeyState期间处理消息时,而左键已经按下,才会报告键已经按下的消息。

双击鼠标按键


双击鼠标按键是指在短时间内单击两次。要确定为双击,则这两次单击必须发生在其相距的实际位置十分接近的状况下(内定范围是一个平均系统字体字符的宽,半个字符的高),并且发生在指定的时间间隔(称为「双击速度」)内。您可以在「控制台」中改变时间间隔。

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

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ;
        
如果在窗口风格中未包含CS_DBLCLKS,而使用者在短时间内双击了鼠标按键,那么窗口消息处理程序会接收到下面这些消息:

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDOWN

WM_LBUTTONUP

窗口消息处理程序可能在这些键的消息之前还收到了其它消息。如果您想实作自己的双击处理,那么您可以使用Windows函数GetMessageTime取得WM_LBUTTONDOWN消息之间的相对时间。第八章将更详细地讨论这个函数。

如果您的窗口类别风格中包含了CS_DBLCLKS,那么双击时窗口消息处理程序将收到如下消息:

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDBLCLK

WM_LBUTTONUP

WM_LBUTTONDBLCLK消息简单地替换了第二个WM_LBUTTONDOWN消息。

如果双击中的第一次键操作完成单击的功能,那么双击这一消息是很容易处理的。第二次按键(WM_LBUTTONDBLCLK消息)则完成第一次按键以外的事情。例如,看看Windows Explorer中是如何用鼠标来操作文件列表的。按一次键将选中文件,Windows Explorer用反白显示列指出被选择文件的位置。双击则实作两个功能:第一次是单击那个选中文件;第二次则指向Windows Explorer以打开该文件。执行方式相当简单,如果双击中的第一次按键不执行单击功能,那么鼠标处理方式会变得非常复杂。

非显示区域鼠标消息


在窗口的显示区域内移动或按下鼠标按键时,将产生10种消息。如果鼠标在窗口的显示区域之外但还在窗口内,Windows就给窗口消息处理程序发送一条「非显示区域」鼠标消息。窗口非显示区域包括标题列、菜单和窗口滚动条。

通常,您不需要处理非显示区域鼠标消息,而是将这些消息传给DefWindowProc,从而使Windows执行系统功能。就这方面来说,非显示区域鼠标消息类似于系统键盘消息WM_SYSKEYDOWN、WM_SYSKEYUP和WM_SYSCHAR。

非显示区域鼠标消息几乎完全与显示区域鼠标消息相对应。消息中含有字母「NC」以表示是非显示区域消息。如果鼠标在窗口的非显示区域中移动,那么窗口消息处理程序会接收到WM_NCMOUSEMOVE消息。鼠标按键产生如表7-2所示的消息。

表7-2
 


 按下
 释放
 按下(双击)
 
 WM_NCLBUTTONDOWN
 WM_NCLBUTTONUP
 WM_NCLBUTTONDBLCLK
 
 WM_NCMBUTTONDOWN
 WM_NCMBUTTONUP
 WM_NCMBUTTONDBLCLK
 
 WM_NCRBUTTONDOWN
 WM_NCRBUTTONUP
 WM_NCRBUTTONDBLCLK
 

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

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

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

ScreenToClient (hwnd, &pt) ;
        
ClientToScreen (hwnd, &pt) ;
        
这里pt是POINT结构。这两个函数转换了保存在结构中的值,而且没有保留以前的值。注意,如果屏幕坐标点在窗口显示区域的上面或者左边,显示区域坐标x或y值就是负值。


 



图7-2 屏幕坐标与客户显示区域坐标
 

命中测试消息


如果您数一下,就可以知道我们已经介绍了21个鼠标消息中的20个,最后一个消息是WM_NCHITTEST,它代表「非显示区域命中测试」。此消息优先于所有其它的显示区域和非显示区域鼠标消息。lParam参数含有鼠标位置的x和y屏幕坐标,wParam参数没有用。

Windows应用程序通常把这个消息传送给DefWindowProc,然后Windows用WM_NCHITTEST消息产生与鼠标位置相关的所有其它鼠标消息。对于非显示区域鼠标消息,在处理WM_NCHITTEST时,从DefWindowProc传回的值将成为鼠标消息中的wParam参数,这个值可以是任意非显示区域鼠标消息的wParam值再加上以下内容:

HTCLIENT

HTNOWHERE

HTTRANSPARENT

HTERROR
 显示区域

不在窗口中

窗口由另一个窗口覆盖

使DefWindowProc产生警示用的哔声
 

如果DefWindowProc在其处理WM_NCHITTEST消息后传回HTCLIENT,那么Windows将把屏幕坐标转换为显示区域坐标并产生显示区域鼠标消息。

如果您还记得我们如何通过拦截WM_SYSKEYDOWN消息来停用所有的系统键盘功能,那么您可能会想我们可否通过拦截鼠标消息完成类似的事情。完全可以!只要您在窗口消息处理程序中包含以下几条叙述:

case WM_NCHITTEST:
        
  return (LRESULT) HTNOWHERE ;
        
就可以有效地禁用您窗口中的所有显示区域和非显示区域鼠标消息。这样一来,当鼠标在您的窗口(包括系统菜单图标、缩放按钮以及关闭按钮)中时,鼠标按键将会失效。

从消息产生消息


Windows用WM_NCHITTEST消息产生所有其它鼠标消息,这种由消息引出其它消息的想法在Windows中是很普遍的。让我们来举个例子。您知道,如果您在一个Windows程序的系统菜单图标上双击一下,那么程序将会终止。双击产生一系列的WM_NCHITTEST消息。由于鼠标定位在系统菜单图标上,因此DefWindowProc将传回HTSYSMENU的值,并且Windows把wParam等于HTSYSMENU的WM_NCLBUTTONDBLCLK消息放在消息队列中。

窗口消息处理程序通常把鼠标消息传递给DefWindowProc,当DefWindowProc接收到wParam参数等于HTSYSMENU的WM_NCLBUTTONDBLCLK消息时,它就把wParam参数等于SC_CLOSE的WM_SYSCOMMAND消息放入消息队列中(这个WM_SYSCOMMAND消息是在使用者从系统菜单中选择「Close」时产生的)。同样地,窗口消息处理程序也把这个消息传给DefWindowProc。DefWindowProc通过给窗口消息处理程序发送WM_CLOSE消息来处理该消息。

如果一个程序在终止之前要求来自使用者的确认,那么窗口消息处理程序就需要拦截WM_CLOSE,否则,DefWindowProc呼叫DestroyWindow函数来处理WM_CLOSE。除了其它处理,DestroyWindow还给窗口消息处理程序发送一个WM_DESTROY消息。窗口消息处理程序通常用下列程序代码来处理WM_DESTROY消息:

caseWM_DESTROY:
        
    PostQuitMessage (0) ;
        
    return 0 ;
        
PostQuitMessage使得Windows把WM_QUIT消息放入消息队列中,此消息永远不会到达窗口消息处理程序,因为它使GetMessage传回0,并终止消息循环,从而也终止了程序。

程序中的命中测试


我在前面讨论了Windows Explorer如何响应鼠标的单击和双击。显然,程序(或者更精确的说,如同Windows Explorer般使用list view control)必须确定使用者鼠标所指向的是哪一个文件。

这叫做「命中测试」。正如DefWindowProc在处理WM_NCHITTEST消息时做一些命中测试一样,窗口消息处理程序经常必须在显示区域中进行一些命中测试。一般来说,命中测试中会使用x和y坐标值,它们由传到窗口消息处理程序的鼠标消息的lParam参数给出。

一个假想的例子


有这样一个例子。假设您的程序需要显示几列按字母排列的文件。通常,您可以使用list view control,他会帮您由于要做全部的命中测试工作。但我们假设您由于某种原因而不能使用,这时就需要自己来做了。让我们假定文件名保存在称为szFileNames的已排序字符串指针数组中。

让我们也假定文件列表开始于显示区域的顶端,显示区域为cxClient图素宽,cyClient图素高,每列为cxColWidth图素宽,每个字符高度为cyChar图素高。那么每栏可填入的文件数就是:

iNumInCol = cyClient / cyChar ;
        
接收到一个鼠标单击消息后,您就能从lParam获得cxMouse和cyMouse坐标。然后可以用下面的公式来计算使用者所指的是哪一列的文件名:

iColumn = cxMouse / cxColWidth ;
        
相对于列顶端的文件名位置为:

iFromTop = cyMouse / cyChar ;
        
现在您就可以计算szFileNames数组的下标:

iIndex = iColumn * iNumInCol + iFromTop ;
        
如果iIndex超过了数组中的文件数,则表示使用者是在显示器的空白区域内按鼠标按键。

在许多情况下,命中测试要比本例更加复杂。在显示一幅包含许多小图形的图像时,您必须决定要显示的每个小图形的坐标。在命中计算中,您必须从坐标找到对象。但这将在使用不确定字体大小的字处理程序中变得非常凌乱,因为您必须找到字符在字符串中的位置。

范例程序


程序7-2所示的CHECKER1程序展示了一些简单的命中测试,此程序把显示区域分为5×5的25个矩形。如果您在某个矩形中按下鼠标按键,那么在该矩形中将出现一个「X」。如果您再按一次,那么「X」将被删除。

程序7-2 CHECKER1 
        
CHECKER1.C
        
/*-------------------------------------------------------------------------
        
  CHECKER1.C --      Mouse Hit-Test Demo Program No. 1
        
                                                         (c) Charles Petzold, 1998
        
--------------------------------------------------------------------------*/

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -