Duilib中讓彈出視窗整體能被拖動的兩種方法

吳尼瑪發表於2017-12-19

第一種方法:

基礎知識:滑鼠在視窗內移動,點選或者釋放時都會產生WM_NCHITTEST訊息,響應函式OnNcHitTest會返回一個列舉值,系統會根據這個列舉值進行相應的處理。當返回值為HTCAPTION時,系統會認為此時滑鼠位於標題欄上,因而當滑鼠按下並移動時就會執行拖動操作。

  • 在Duilib中在設定caption高度就能能讓使用者拖動視窗,其實就是當滑鼠按下時在OnNcHitTest訊息響應裡面返回HTCAPTION,讓系統預設為此時滑鼠位於標題欄。
LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	POINT pt;
	RECT rcClient;
	RECT rcCaption;

	rcCaption = m_pm.GetCaptionRect();
	GetClientRect(m_pm.GetPaintWindow(), &rcClient);
	pt.x = GET_X_LPARAM(lParam);
	pt.y = GET_Y_LPARAM(lParam);
	::ScreenToClient(m_pm.GetPaintWindow(), &pt);

    //xml中設定bottom為-1時,整個視窗區域都可以拖動  
	if (-1 == rcCaption.bottom) 
	{
		rcCaption.bottom = rcClient.bottom;
	}

	if ((pt.x >= rcClient.left)
		&& (pt.x < rcClient.right)
		&& (pt.y >= rcCaption.top)
		&& (pt.y < rcCaption.bottom))
	{
			return HTCAPTION;
	}

	return __super::OnNcHitTest(uMsg, wParam, lParam, bHandled);
}
複製程式碼
  • 最後,在視窗xml中指定caption="0,0,0,-1",不管視窗大小如何變,整個視窗就可以拖動了。其實這種方法也相當於把caption的bottom設定成視窗的高度。
  • 但是,這樣做有個明顯的缺點,就是這個視窗的其他事件訊息都無法處理了。如果視窗中有一個編輯框就無法編輯了。

第二種方法

基礎知識:我們可以模擬在win32中視窗移動的函式處理過程。簡單的說,我們只需要在自己的視窗中對WM_LBUTTONDOWN、WM_MOUSEMOVE、WM_LBUTTONUP這三個訊息進行處理即可。在WM_LBUTTONDOWN中記錄滑鼠左鍵被按下時的資訊,WM_MOUSEMOVE中記錄滑鼠移動距離,WM_LBUTTONUP記錄滑鼠左鍵彈起。

LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	//記錄滑鼠按下
	is_lbutton_down_ = true;

	//滑鼠按下時的座標
	start_point_.x = GET_X_LPARAM(lParam);
	start_point_.y = GET_Y_LPARAM(lParam);

	bHandled = TRUE;
	return 0;
}

LRESULT OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	//左鍵彈起,改變滑鼠狀態
	is_lbutton_down_ = false;
	bHandled = TRUE;
	return 0;
}

LRESULT OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) 
{
	if (is_lbutton_down_ == true)
	{
		POINT point;

		//獲取當前滑鼠的位置
		::GetCursorPos(&point);
		::ScreenToClient(m_pm.GetPaintWindow(), &point);

		//獲取新的位置
		int Dx = point.x - start_point_.x;
		int Dy = point.y - start_point_.y;

		start_rect_.left += Dx;
		start_rect_.right += Dx;
		start_rect_.top += Dy;
		start_rect_.bottom += Dy;

		//將視窗移到新的位置  
		SetWindowPos(m_hWnd, HWND_TOP, start_rect_.left, start_rect_.top, 0, 0, SWP_NOSIZE);
	}

	bHandled = TRUE;
	return 0;
}
複製程式碼
  • 但是,這種方法也存在一個明顯的bug,當你拖動視窗一直到工作列,然後鬆開滑鼠左鍵,這時視窗就會自動跟著滑鼠移動。

  • 解決這個bug的方法就需要響應WM_MOUSELEAVE訊息,在該訊息中記錄滑鼠已經移出視窗。

  • 預設情況下,視窗是不響應WM_MOUSELEAVE和WM_MOUSEHOVER訊息的,所以要使用_TrackMouseEvent函式來啟用這兩個訊息。呼叫這個函式後,當滑鼠在指定視窗上停留超過一定時間或離開視窗後,該函式會Post這兩個訊息到指定視窗。

MSDN:The _TrackMouseEvent function posts messages when the mouse pointer leaves a window or hovers over a window for a specified amount of time. This function calls TrackMouseEvent if it exists, otherwise it emulates it.

  • 具體方法如下:

    • 在視窗類中定義一個變數來標識是否追蹤當前滑鼠狀態,之所以要這樣定義是要避免滑鼠已經在窗體之上時,一移動滑鼠就不斷重複產生WM_MOUSEHOVER訊息。
    BOOL is_mouse_track_=TRUE ;
    複製程式碼
    • 在OnMouseMove中呼叫_TrackMouseEvent函式
     if (is_mouse_track_)
     {
          TRACKMOUSEEVENT csTME;
          csTME.cbSize = sizeof (csTME);
          csTME.dwFlags = TME_LEAVE|TME_HOVER;
          csTME.hwndTrack = m_hWnd ;
          csTME.dwHoverTime = 10;  // 滑鼠在按鈕上停留超過10ms ,才認為狀態 HOVER
          ::_TrackMouseEvent (&csTME);
        
          is_mouse_track_=FALSE ; 
     }
    複製程式碼
    • 在 OnMouseLeave 中再次允許追蹤滑鼠狀態
    is_mouse_track_=TRUE ;
    複製程式碼

相關文章