本文描述瞭如何正確處理自定義窗口和控件中的鼠標捕獲操作。
原文鏈接: http://www.codeproject.com/Tips/127813/Using-SetCapture…correctly.aspx
原作者: pasztorpisti
轉載請註明出處:http://www.imoldman.com/2010/11/30/ using-setcaptu…ture-correctly
鼠標捕獲是Windows的一項特性。即使光標不在某個窗口或者控件(帶HWND
句柄)內,它也可以將所有的鼠標消息和該指定的窗口相關聯。現假定你已經熟悉了這項特性及其相關API
(包括SetCapture()
函數, ReleaseCapture()
函數及WM_CAPTURECHANGED
消息),在這裡,我想告訴你一項開發人員經常犯的錯誤。
有些窗口和控件為了完成某項操作,有時需要捕獲鼠標。spy++程序上的窗口選擇器(即那個十字)是一個很好的例子,它允許你拖動一個十字光標到你桌面的某個窗口上,這樣你就可以選中它。
該文給出的示例程序創建了一個窗口,你可以拖動它的客戶區來移動它。只要在DefWindowProc()
響應WM_NCHITTEST
消息時返回HTCLIENT
,就可以達到這種效果,但是這樣主循環就不工作了,就好像是你在拖拽著它的標題欄一樣。某些程序(如媒體播放器,遊戲)通常自繪整個窗口,並且以該示例代碼中的方式提供拖拽窗口的功能。這樣做,程序的主循環可以一直在運作。這樣的窗口移動功能需要捕獲鼠標,那時因為如果你按下了鼠標左鍵,然後把鼠標從客戶區猛地拉到外面去,窗口仍然要能夠接收到WM_MOUSEMOVE
消息。
那麼這種“抓著客戶區移動窗口”操作的執行步驟是怎樣的呢?
WM_LBUTTONDOWN
,開始拖拽操作並且捕獲鼠標。WM_MOUSEMOVE
,隨著光標一起移動窗口。WM_LBUTTONUP
,僅僅釋放捕獲的鼠標。WM_CAPTURECHANGED
,結束拖拽操作。
這篇文章的重點就是你要在WM_LBUTTONUP
和WM_CAPTURECHANGED
消息響應中處理什麼。這裡重要的一點就是WM_LBUTTONUP
僅僅釋放鼠標,只有WM_CAPTURECHANGED
才會中止拖拽操作!!!之所以這樣,原因在於拖拽操作可以被其他操作終止掉,比如說你按了ALT+TAB組合鍵。這種情況下,你的程序根本接收不到WM_LBUTTONUP
消息,但是仍然失去了鼠標捕獲,此時,窗口會接收到WM_CAPTURECHANGED
消息,於是整個拖拽操作結束。
如果你把“拖拽操作結束”的相關代碼放在了WM_LBUTTONUP
消息響應裡,一旦用戶按了ALT+TAB,你的程序會失去鼠標捕獲,此時另外一個窗口會成為前景窗口,你整個窗口的邏輯狀態就會亂掉,因為它還認為它是在拖動過程中。
所以結論就是,總是將操作結束的處理代碼放在WM_CAPTURECHANGED
消息響應裡,並且在其他你想結束操作的地方調用ReleaseCapture()
,這可以發生在任何地方,比如在WM_LBUTTONUP
消息響應函數中,在WM_MOUSEMOVE
消息響應函數中,等等。
編譯運行我給出的代碼,在拖拽主窗口客戶區的過程中,使用ALT+TAB按鍵將一個大些的窗口提到前面,這樣示例程序的主窗口就會全部被蓋住。之後釋放鼠標按鍵並且切回到示例程序,將鼠標在示例窗口上移動,一切正常。然後將WM_LBUTTONUP
和WM_CAPTURECHANGED
消息處理代碼註釋掉,並且去掉有問題的代碼的註釋,再按照步驟試試ALT+TAB!這次,在ALT+TAB切換窗口、釋放鼠標按鍵並且使用ALT+TAB再切回我們的窗口後,將鼠標在示例窗口上移動,並且嘗試很快的速度移動光標,此時你會發現窗口的行為很瘋狂,除非你在窗口上單擊一下給它發個WM_LBUTTONUP
消息,它才能回歸正常。
#include <windows.h>
HINSTANCE g_hInstance = (HINSTANCE)GetModuleHandle(NULL);
HWND g_hMainWnd = NULL;
bool g_MovingMainWnd = false;
POINT g_OrigCursorPos;
POINT g_OrigWndPos;
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_LBUTTONDOWN:
// here you can add extra check and decide whether to start
// the window move or not
if (GetCursorPos(&g_OrigCursorPos))
{
RECT rt;
GetWindowRect(hWnd, &rt);
g_OrigWndPos.x = rt.left;
g_OrigWndPos.y = rt.top;
g_MovingMainWnd = true;
SetCapture(hWnd);
}
return 0;
// THE RIGHT WAY OF DOING IT:
//*
case WM_LBUTTONUP:
ReleaseCapture();
return 0;
case WM_CAPTURECHANGED:
g_MovingMainWnd = (HWND)lParam == hWnd;
return 0;
/**/
// THE WRONG WAY OF DOING IT:
/*
case WM_LBUTTONUP:
g_MovingMainWnd = false;
ReleaseCapture();
return 0;
// buggy programs usually do not handle WM_CAPTURECHANGED at all
case WM_CAPTURECHANGED:
break;
/**/
case WM_MOUSEMOVE:
if (g_MovingMainWnd)
{
POINT pt;
if (GetCursorPos(&pt))
{
int wnd_x = g_OrigWndPos.x +
(pt.x - g_OrigCursorPos.x);
int wnd_y = g_OrigWndPos.y +
(pt.y - g_OrigCursorPos.y);
SetWindowPos(hWnd, NULL, wnd_x,
wnd_y, 0, 0, SWP_NOACTIVATE|
SWP_NOOWNERZORDER|SWP_NOZORDER|
SWP_NOSIZE);
}
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
bool CreateMainWnd()
{
static const char CLASS_NAME[] = "MainWndClass";
WNDCLASS wc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hInstance = g_hInstance;
wc.lpfnWndProc = &MainWndProc;
wc.lpszClassName = CLASS_NAME;
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
if (!RegisterClass(&wc))
return false;
g_hMainWnd = CreateWindowEx(
0,
CLASS_NAME,
"Main Window",
WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT, 300, 200,
NULL,
NULL,
g_hInstance,
NULL
);
return true;
}
int main()
{
if (!CreateMainWnd())
return -1;
ShowWindow(g_hMainWnd, SW_SHOW);
UpdateWindow(g_hMainWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
ps:這篇文章是昨天看到的,當時感覺寫的挺好的(其實主要是因為短
![害羞](http://static.blog.csdn.net/xheditor/xheditor_emot/default/shy.gif)
就這樣吧,謝謝原作者pasztorpisti和CodeProjcet平台