Windows編程 第十回 鼠標的祕密

你平時使用的鼠標中藏着一個鮮爲人知祕密,它與鼠標名字的起源有關,這回我就給大家揭開這個祕密。請看圖:

哈哈,純粹娛樂一下。不過這回我們確實要探一探鼠標在Windows編程中的祕密。

鼠標估計大家天天都在用,我覺得沒有必要再給大家“掃盲”了,如果非要講點什麼的話,那我就提一下關於鼠標操作的常用術語:單擊、雙擊和拖拽,相信大家都很清楚。還是同講鍵盤一樣,講鼠標還是從與之相關的消息入手吧。

上一回我們瞭解到Windows只把鍵盤消息發送給擁有輸入焦點的窗口。而鼠標消息與此不同:只要在某窗口按下鼠標,那麼該窗口的窗口過程就會收到鼠標消息,而不管該窗口之前是否活動或者是否擁有輸入焦點。

好了,大家瞭解了一下窗口對於鍵盤消息與鼠標消息接收的不同之後,我們就來學習一下關於鼠標的22個消息。(好像有點多哦……)

客戶區鼠標消息

   由上一回我們得知Windows只把鍵盤消息發送給擁有輸入焦點的窗口,而鼠標消息與此不同:只要鼠標跨越窗口或者在某窗口下按下鼠標鍵,那麼窗口過程就會收到鼠標消息,不管該窗口是否活動或者是否擁有輸入焦點。

當在窗口的客戶區中按下或者釋放一個鼠標按鍵時,窗口過程會接收到下面這些消息:

按下

釋放

按下(雙鍵)

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDBLCLK

WM_MBUTTONDOWN

WM_MBUTTONUP

WM_MBUTTONDBLCLK

WM_RBUTTONDOWN

WM_RBUTTONUP

WM_RBUTTONDBLCLK

對於三鍵鼠標,窗口過程纔會收到MBUTTON消息(當然現在這種鼠標很不常見了,常見的是兩鍵一輪的鼠標,你會發現鼠標輪除了滾動還可以點擊,點擊鼠標輪就會產生三鍵鼠標點擊中鍵的效果,同樣會產生MBUTTON的消息)。

僅當定義的窗口類能接收DBLCLK(雙擊)消息之後,窗口過程才能接受這類消息。既然說到此我們就先來看一下關於鼠標雙擊的消息。鼠標雙擊是指在 短時間內單擊鼠標鍵兩次。要確定爲雙擊,這兩次單擊必須發生在其相互的物理位置十分接近的狀況下,並且發生在指定的時間間隔內。你可以在“控制面板”中改 變時間間隔。

一般窗口過程是接收不到雙擊鍵的鼠標消息的,如果希望窗口過程能夠收到這種消息,那麼在調用RegisterClass初始化窗口類結構時,必須在窗口風格中包含CS_DBLCLKS標識符:

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;

如果在窗口風格中未包含CS_DBLCLKS,而用戶在短時間內雙擊了鼠標鍵,那麼窗口過程會接收到下面這些消息:

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDOWN

WM_LBUTTONUP

如果你的窗口類別風格中包含了CS_DBLCLKS,那麼雙擊鼠標鍵時窗口過程將收到如下消息:

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDBLCLK

WM_LBUTTONUP

WM_LBUTTONDBLCLK消息簡單地替換了第二個WM_LBUTTONDOWN消息。

雙擊中的第一次單擊操作完成單擊的功能,第二次單擊操作(WM_LBUTTONDBLCLK消息)則完成第一次按鍵以外 的事情。看一個雙擊的“慢動作回放”:例如,看看“Windows 資源管理器”中是如何用鼠標來操作文件列表的。第一次單擊操作將選中文件,“Windows資源管理器”用反白顯示出被選擇的文件。第二次單擊操作則指示 “Windows 資源管理器”打開該文件。

別怪我囉嗦,再舉一個單擊的例子吧。如果你在非活動窗口的客戶區中按下鼠標左鍵,Windows將把活動窗口改爲在其中按下鼠標按鍵的這個窗口,然 後把WM_LBUTTONDOWN消息送到該窗口的窗口過程。當釋放鼠標左鍵時,則Windows再把WM_LBUTTONUP消息送到該窗口的窗口過 程。常規情況下,BUTTONUP與BUTTONDOWN消息會成對出現在一個窗口中,但也會有例外。如果鼠標按鍵在另一個窗口中被釋放,則這個窗口的窗 口過程只能接收到WM_LBUTTONDOWN消息,而沒有相應的WM_LBUTTONUP消息。同理,另一個窗口的窗口過程在未接收到 WM_LBUTTONDOWN消息的情況下卻先接收到了WM_LBUTTONUP消息。當然這個例子有點“極端”,但確實存在這種情況。

我們再來認識一個鼠標消息WM_MOUSEMOVE。當鼠標移過窗口的客戶區,窗口過程就會收到一系列此消息。當然在你 把鼠標移過客戶區時,Windows並不爲鼠標經過的每個可能的像素位置都產生一條WM_MOUSEMOVE消息。你的程序接收到 WM_MOUSEMOVE消息的次數依賴於鼠標硬件以及你的窗口過程處理鼠標移動消息的速度。

對於上面這10個消息來說,其lParam值均含有鼠標的位置:低位字爲x座標,高位字爲y座標,這兩個座標是相對於窗口客戶區左上角的位置。您可以用LOWORD和HIWORD宏來提取這些值:

x = LOWORD (lParam) ;

y = HIWORD (lParam) ;

wParam的值指示鼠標按鍵以及Shift和Ctrl鍵的狀態。你可以使用頭文件WINUSER.H中定義的與運算來測試wParam。(如果忘了lParam和wParam,就去複習一下第四回中消息結構體的講解)

MK_LBUTTON

按下左鍵

MK_MBUTTON

按下中鍵

MK_RBUTTON

按下右鍵

MK_SHIFT

按下Shift鍵

MK_CONTROL

按下Ctrl鍵

MK前綴代表“鼠標按鍵”。

例如,如果收到了WM_LBUTTONDOWN消息,而且值

wparam &MK_SHIFT

是TRUE(非0),你就知道當左鍵按下時也按下了Shift鍵。

再來看一個例子,如果在程序中一種功能的實現依賴於鼠標單擊和Shift、Ctrl鍵的組合,可以使用如下方法:

if (wParam & MK_SHIFT)
{
        if (wParam & MK_CONTROL)
           {
            //按下了Shift和Ctrl鍵
           }
        else
           {
                           //只按下了Shift鍵
            }
}
 else
    {
        if (wParam & MK_CONTROL)
            {
                           //只按下了Ctrl鍵
            }
        else
            {
                           //Shift和Ctrl鍵均未按下
            }
}

非客戶區鼠標消息

在窗口的客戶區內移動或按下鼠標按鍵時,將產生前面講的10種消息。如果鼠標在窗口的客戶區之外但還在窗口內,Windows就給窗口過程發送一條“非客戶區”鼠標消息。窗口非客戶區包括標題欄、菜單和窗口滾動條。

通常,我們不需要處理非客戶區鼠標消息,而是將這些消息傳給DefWindowProc,從而使Windows執行系統功能。就這方面來說,非客戶區鼠標消息類似於系統鍵盤消息WM_SYSKEYDOWN、WM_SYSKEYUP和WM_SYSCHAR。

非客戶區鼠標消息幾乎完全與顯示區域鼠標消息相對應。消息中含有字母“NC”以表示是非客戶區消息。

如果鼠標在窗口的非客戶區中移動,那麼窗口過程會接收到WM_NCMOUSEMOVE消息。

鼠標按鍵會產生如下表所示的消息。

按下

釋放

按下(雙擊)

WM_NCLBUTTONDOWN

WM_NCLBUTTONUP

WM_NCLBUTTONDBLCLK

WM_NCMBUTTONDOWN

WM_NCMBUTTONUP

WM_NCMBUTTONDBLCLK

WM_NCRBUTTONDOWN

WM_NCRBUTTONUP

WM_NCRBUTTONDBLCLK

對非客戶區的10個鼠標消息,wParam和lParam參數與客戶區的10個鼠標消息的wParam和lParam參數有一定的差別。

wParam參數指明移動或者按鼠標按鍵的非客戶區位置。它設定爲以HT開頭的標識符之一(HT表示 “命中測試”)。

lParam參數的低位字爲x座標,高位字爲y座標,但是,它們都是屏幕座標,而不是像客戶區鼠標消息那樣指的是客戶區座標。①

命中測試消息    

這個消息是WM_NCHITTEST,它代表“非客戶區命中測試”。從它的名字來看它應該在“非客戶區鼠標消息”中講纔對呀,既然我把它單拿出來,自然是有道理的。它的作用比較“特殊”,而且要講的內容比較多。

當光標移動或鼠標按下、釋放時,Windows系統就會發送此消息,並且此消息優先於其它的客戶區和非客戶區鼠標消息。這裏的“優先於”怎麼講?就是Windows 用WM_NCHITTEST消息產生所有其它的鼠標消息,這種由消息引出其它消息的思想在Windows中是很普遍的。(想一想我們在第四回是不是曾經遇 到過?)Windows用這個消息來做什麼? “HITTEST”就是“命中測試”的意思,WM_NCHITTEST消息用來獲取鼠標當前命中的位置。WM_NCHITTEST的消息響應函數會根據鼠 標當前的座標來判斷鼠標命中了窗口的哪個部位,消息響應函數的返回值指出了部位。再補充一點,此消息的lParam 參數含有鼠標位置的x和y屏幕座標,wParam參數沒有用。

爲了便於理解,我先描述一下Windows對鼠標鍵按下的響應流程:

1. 確定鼠標鍵點擊的是哪個窗口。Windows會用表記錄當前屏幕上各個窗口的區域座標,當鼠標驅動程序通知Windows鼠標鍵按下了,Windows根據鼠標的座標確定它點擊的是哪個窗口。

2. 確定鼠標鍵點擊的是窗口的哪個部位。Windows會向鼠標鍵點擊的窗口發送WM_NCHITTEST消息,來詢問鼠標鍵點擊的是窗口的哪個部位。窗口程 序通常把這個消息傳送給DefWindowProc默認處理,然後Windows用WM_NCHITTEST消息產生與鼠標位置相關的所有其它鼠標消息。 具體地說,就是在處理WM_NCHITTEST消息時,從DefWindowProc返回的值將成爲其它鼠標消息中的wParam參數。一般來 說,WM_NCHITTEST消息是系統來處理的,我們用戶一般不用去主動去處理它。

3. 根據鼠標鍵點擊的部位給窗口發送相應的消息。例如:如果WM_NCHITTEST的消息響應函數的返回值是HTCLIENT,表示鼠標點擊的是客戶區,同 時Windows將把屏幕座標轉換爲客戶區座標併產生相應的客戶區鼠標消息,在這裏就是Windows向窗口發送WM_LBUTTONDOWN消息;如果 WM_NCHITTEST的消息響應函數的返回值不是HTCLIENT(比如說是HTCAPTION),即鼠標點擊的是非客戶區,Windows就會向窗 口發送wParam等於HTCAPTION的WM_NCLBUTTONDOWN消息。

其返回值有很多,現在簡單列舉一部分吧,僅供參考。

   HTNOWHERE -不在窗口中

   HTCLIENT - 客戶區

   HTCAPTION - 標題

   HTSYSMENU - 系統菜單

   HTMENU - 菜單

   HTHSCROLL - 水平滾動條

   HTVSCROLL - 垂直滾動條

   HTMINBUTTON - 最小化按鈕

   HTMAXBUTTON - 最大化按鈕

   HTLEFT - 左邊界

   HTRIG - 右邊界

   HTTOP - 上邊界

   HTTOPLEFT - 左上角

   HTTOPRIG - 右上角

   HTBOTTOM - 下邊界

   HTBOTTOMLEFT - 左下角

   HTBOTTOMRIG- 右下角

   HTCLOSE- 關閉按鈕

再來看個實際例子吧,方便大家理解一下上面的內容。

大家想一下我們如何來屏蔽鼠標鍵的操作,讓其失效?我們上面講過“Windows用WM_NCHITTEST消息產生所有其它的鼠標消息”,我們可以在窗口過程中加入以下語句:

     case WM_NCHITTEST:

          return (LRESULT)HTNOWHERE;//由於窗口過程返回值是LRESULT類型的,這裏

//行了強制類型轉換

我們把第二回的代碼模板中“caseWM_CREATE”語段和“case WM_PAINT”語段(行59-68)刪掉,加入上面的語段,運行一下。是不是發現鼠標對窗口(包括客戶區和非客戶區)所有的點擊、拖動操作都失效了, 因爲我們截獲了WM_NCHITTEST消息,返回HTNOWHERE ,“欺騙”操作系統這時鼠標“不在”窗口中的,雖然鼠標確實在窗口中。再來講一個“欺騙”的例子吧。一般我們鼠標單擊一個窗口標題欄的時纔可拖動窗口移 動,如果我們要想實現鼠標只要單擊窗口任一個位置就可以拖動窗口的功能,這該怎麼辦呢?照葫蘆畫瓢即可:

case WM_NCHITTEST:

          return (LRESULT)HTCAPTION;

這樣只要你在窗口中任意位置單擊鼠標,系統就會由WM_NCHITTEST消息引發wParam 值標誌標題欄的WM_NCLBUTTONDOWN消息,這時當前窗口將處於 “拖拽狀態”(Windows內部記錄了每個窗口的狀態信息)。由於標識了“拖拽狀態”,則從此刻起到鼠標鍵放開之前,你的鼠標移動狀況完全由 Windows跟蹤。它根據鼠標的移動,使得窗口作“同步”移動。

注意,這個過程中,窗口不會收到WM_NCMOUSEMOVE消息,因爲窗口和鼠標是“同步”移動的,你的鼠標相對於窗口是靜止的。

注:但問題同時也出現了, 我想在右擊這個窗體客戶區的時候彈出一個菜單,當我完成 WM_RBUTTONDOWN 這個消息的時候,發現窗體收不到這個消息, 將WM_NCHITTEST消息的實現去掉就可以了,看了一原因是:

因爲你在WM_NCHITTEST中處理了鼠標消息,把它定位成 HTCAPTION,也就是鼠標在標題欄上,而標題欄屬於非客戶區(NC);非客戶區的事件消息都是以WM_NC開頭的。也就是說,當你的 WM_NCHITTEST返回HTCAPTION時,原來可以用WM_LBUTTONDOWN處理的消息,你只能用WM_NCLBUTTONDOWN來處 理。

解決方法:同時處理WM_NCHITTEST和WM_NCRBUTTONDOWN,而不處理WM_RBUTTONDOWN。

最後一個鼠標消息

    我們平時常用的鼠標輪來移動滾動條,當鼠標輪轉動時就會產生WM_MOUSEWHEEL消息。關於這個消息我將在以後和滾動條一起講。

①對屏幕座標,顯示器左上角的x和y的值爲0。當往右移時x的值增加,往下移時y的值增加。


你可以用兩個Windows函數將屏幕座標轉換爲客戶區座標或者反之:

ScreenToClient (hwnd, &pt) ;

ClientToScreen (hwnd, &pt) ;

這裏pt是POINT結構。這兩個函數轉換了保存在結構中的值,而且沒有保留以前的值。注意,如果屏幕座標點在窗口客戶區的上面或者左邊,客戶區座標x或y值就是負值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章