原文鏈接: http://blog.sina.com.cn/s/blog_6288219501015dwa.html
移動標准窗口是通過用鼠標單擊窗口標題條來實現的,但對於沒有標題條的窗口,就需要用鼠標單擊窗口標題條以外區域來移動窗口。有兩種方法可以達到這一目標。
方法一:當窗口確定鼠標位置時,Windows向窗口發送WM_NCHITTEST消息,可以處理該消息,使得只要鼠標在窗口內,Windows便認為鼠標在標題條上。這需要重載CWnd類處理WM_NCHITTEST消息的OnNcHitTest函數,在函數中調用父類的該函數,如果返回HTCLIENT,說明鼠標在窗口客戶區內,使重載函數返回HTCAPTION,使Windows誤認為鼠標處於標題條上。
下例是使用該方法的實際代碼:
UINT CEllipseWndDlg::OnNcHitTest(CPoint point)
{
// 取得鼠標所在的窗口區域
UINT nHitTest = CDialog::OnNcHitTest(point);
// 如果鼠標在窗口客戶區,則返回標題條代號給Windows
// 使Windows按鼠標在標題條上類進行處理,即可單擊移動窗口
return (nHitTest==HTCLIENT) ? HTCAPTION : nHitTest;
}
方法二:當用戶在窗口客戶區按下鼠標左鍵時,使Windows認為鼠標是在標題條上,即在處理WM_LBUTTONDOWN消息的處理函數OnLButtonDown中發送一個wParam參數為HTCAPTION,lParam為當前坐標的WM_NCLBUTTONDOWN消息。
下面是使用該方法的實際代碼:
void CEllipseWndDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
// 調用父類處理函數完成基本操作
CDialog::OnLButtonDown(nFlags, point);
// 發送WM_NCLBUTTONDOWN消息
// 使Windows認為鼠標在標題條上
PostMessage(WM_NCLBUTTONDOWN,
HTCAPTION,
MAKELPARAM(point.x, point.y)); //或SendMessage(WM_SYSCOMMAND,0xF012,0); //0xF012 = SC_MOVE | HTCAPTION
}
首先,看看在正常情況下系統是怎樣來移動程序窗口的。當使用者在程序窗口標題欄區域(非工作區)內,按下鼠標左鍵時將會發生下列事情:
◆ 系統向該窗口過程函數發送WM_NCLBUTTONDOWN消息。
◆ WM_NCLBUTTONDOWN消息最終將傳送到窗口過程函數中的DefWindowProc()函數中去。
◆ DefWindowProc()函數將根據鼠標左鍵按下並移動,以及HTCAPTION標識所表示鼠標按下時的位置諸多信息,來執行該消息的缺省動作即窗口隨同鼠標光標一起移動的操作。
下面作為練習來測試一下,首先在窗口回調函數(即窗口過程函數)中設置下列語句:
caseWM_NCLBUTTONDOWN
return 0;
然后,同樣是在窗口標題欄內按住鼠標左鍵並移動鼠標,但此時窗口卻並不隨同鼠標一起移動了,這是怎么回事?這是因為上述語句中設有“return 0”語句的緣故。該語句使得WM_NCLBUTTONDOWN消息未能傳遞到DefWindowProc()函數,就在窗口過程函數中提前返回了,當然移動窗口的操作就無從進行了。這也從反面印證了一個事實,那就是,最后完成移動窗口的操作將是由DefWindowProc()函數來完成的。
通過上面的分析可以勾划出這樣一個操作過程:即用戶在窗口標題欄內按下鼠標左鍵→ 系統發送WM_NCLBUTTONDOWN消息 → DefWindowProc()函數接收消息 → 用戶移動鼠標 → DefWindowProc()函數執行窗口隨同鼠標一起移動的操作。
由此得出一個結論,那就是要想實現移動窗口的操作,必須具備兩個條件:一是要按下鼠標左鍵並移動(DefWindowProc()函數將檢測這個條件);二是在按下鼠標左鍵時能發送WM_NCLBUTTONDOWN消息並返回HTCAPTION標識。
下面就根據以上的分析,在沒有窗口標題欄的情況下,采取騙取DefWindowProc()函數的方式,來實現對無標題欄窗口實體的移動操作。
1、主動發送WM_NCLBUTTONDOWN消息
在窗口沒有標題欄的情況下,在窗口實體上按下鼠標左鍵時,系統是不會發送WM_NCLBUTTONDOWN消息的,這是因為鼠標光標是在窗口的工作區內被按下的,此時系統發送的是WM_LBUTTONDOWN消息。
但通過上述的分析,可以知道DefWindowProc()函數並不關心WM_NCLBUTTONDOWN消息是由誰發出的,而只是關心是否有該消息發出。這樣只要我們在按下鼠標左鍵的事件中,主動將WM_NCLBUTTONDOWN消息發出,豈不就可同時滿足這兩個條件嗎!下面的代碼就是根據這個思路來設計的。
caseWM_LBUTTONDOWN:
SendMessage(hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0);
break;
消息發送時,通過HTCAPTION參數給DefWindowProc()函數帶去一個信息,告訴它鼠標左鍵是在窗口非工作區內的標題欄處按下的。當然這是一個假情報,但DefWindowProc()函數會信以為真並根據這個信息來執行相應的操作。
2、主動發送WM_SYSCOMMAND消息
caseWM_LBUTTONDOWN:
SendMessage(hWnd,WM_SYSCOMMAND,SC_DRAGMOVE,0);
break;
能使用WM_SYSCOMMAND消息來移動窗口,得益於一個新近擴展的SC_DRAGMOVE風格標志,該標志從字面上就能看出是"拖曳移動"的意思。該標志在低版本的編譯器(VC 6.0中就沒有)中是找不到的,所以在引用該標志時應預先聲明:
#define SC_DRAGMOVE 0xF012
也可以直接使用其常量值:
SendMessage(hWnd,WM_SYSCOMMAND,0xF012,0);
上述無標題欄窗口移動窗體的機理與窗口有標題欄時是相似的,相同處最后都是由DefWindowProc()函數來完成實際的操作;不同處是發送消息的方式不同,一個是由系統隱含發送;另一個則是由程序公開發送。
采取上述兩種方法來移動窗口,同有標題欄移動窗口的視覺效果是一樣的,那就是在移動時,先移動一個指示框(一個虛線框),等確定好窗體移動后的新位置后,當松開鼠標左鍵時窗口的實體才被正真移動到虛線框所指向的位置處。那么,能否直接移動窗口實體而不出現虛線框呢?答案是肯定的。
實際上,操作系統准備了兩種移動窗口的方式,一種是有虛線框,另一種則是沒有虛線框,只是Windows系統默認的是有虛線框。不過,如果我們在上述示例代碼中再添加下列語句:
SystemParametersInfo(SPI_SETDRAGFULLWINDOWS,true,NULL,0);
即:
caseWM_LBUTTONDOWN:
SystemParametersInfo(SPI_SETDRAGFULLWINDOWS,true,NULL,0);
SendMessage(hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0);
break;
這樣在移動窗口時,虛線指示框將不會出現。注意,上述語句的位置順序不能錯,否則,在移動時虛線框還會出現。
不過,系統默認按有虛線指示框的方式來移動窗口是通過權衡利弊的。這是因為有虛線框方式移動窗口,在開始時並未正真地移動窗口,而是用一個線框來指定窗口將要到達的位置,然后一次性的將窗口移動到指定的位置(在指定的位置重新繪制)處。也就是說,在移動窗口的過程中,窗口實體只需要重繪一次即可。如果不用虛線框,而是直接移動窗口實體的話,在窗口的移動過程中,將會形成N次繪制窗口,增加系統處理圖形的負擔,而使窗口的繪制質量嚴重下降,造成不好的視覺效果。
為此,在編程實踐中可以這樣來安排:對異形窗口為了體現異形視覺效果,可以使用無虛線框的方式來移動窗口;而對於一般矩形窗口就可按有虛線框的方式來移動窗口,以求確保窗口的重繪質量。
誠然,上面設置的代碼可以使異形窗口無虛線框方式移動,但由於SystemParametersInfo()函數是系統級的,對它的調用將會影響電腦桌面上所有程序窗口都會按無虛線框方式移動,如果這樣的話。必將會使桌面整體的視覺效果大打折扣。如果不想影響其它窗口的移動效果,而只是要求在移動本異形窗口時不出現虛線框的話,則可在窗口過程函數中再增添下列代碼即可:
case WM_MOUSEMOVE:
SystemParametersInfo(SPI_SETDRAGFULLWINDOWS,false ,NULL,0);
break;
3、自編代碼
前兩種方法都有缺陷,會改變消息流,使OnLButtonDown,OnLButtonUp等不響應
自編代碼同樣可以實現無標題欄窗口的移動操作,而且更加靈活多樣。例如,操作可以由鼠標左鍵或鼠標右鍵來完成。下面是通過鼠標右鍵來完成窗口移動的實際代碼,在程序窗口的過程函數中添加下列代碼語句:
static POINT pt, pe;
static RECT rt, re;
case WM_RBUTTONDOWN:
SetCapture(hWnd); // 設置鼠標捕獲(防止光標跑出窗口失去鼠標熱點)
GetCursorPos(&pt); // 獲取鼠標光標指針當前位置
GetWindowRect(hWnd,&rt); // 獲取窗口位置與大小
re.right=rt.right-rt.left; // 保存窗口寬度
re.bottom=rt.bottom-rt.top; // 保存窗口高度
break;
case WM_RBUTTONUP:
ReleaseCapture(); // 釋放鼠標捕獲,恢復正常狀態
break;
case WM_MOUSEMOVE:
GetCursorPos(&pe); // 獲取光標指針的新位置
if(wParam==MK_RBUTTON) // 當鼠標右鍵按下
{
re.left=rt.left+(pe.x - pt.x); // 窗口新的水平位置
re.top =rt.top+(pe.y - pt.y); // 窗口新的垂直位置
MoveWindow(hWnd,re.left,re.top,re.right,re.bottom,true);// 移動窗口
}
break;
或:
OnLButtonDown() 中:
SetCapture();
CRect rW;
GetWindowRect(rW);
CPoint ptW = point;
ClientToScreen(&ptW);
m_ptCursorOffset.x = ptW.x - rW.left;
OnMouseMove()中:
if ((nFlags & MK_LBUTTON) && this == GetCapture())
{
CPoint ptW = point;
ClientToScreen(&ptW);
ptW.x -= m_ptCursorOffset.x;
ptW.y -= m_ptCursorOffset.y;
::SetWindowPos(m_hWnd, 0,ptW.x,ptW.y,0,0,SWP_NOSIZE);
}
m_ptCursorOffset.y = ptW.y - rW.top;
OnLButtonUp()中:
ReleaseCapture();