拖動是界面編程頻繁使用的一個效果,在windows系統下可謂大行其道。縱觀時下的應用軟件幾乎各個都支持各種各樣拖動的效果,windows7更是把拖動做到了極致。其實說起來拖動的實現也很簡單,對於有句柄的對象都可以通過MoveWindow或SetWindowPos實現位置變動,而沒有句柄的對象實現拖動無非就是做些參數修改,說到底實現拖動就是在OnLButtonDown、OnMouseMove和OnLButtonUp中處理數據,當然你可以使用鼠標右鍵甚至中建消息來實現,基本原理是一樣的。
基本原理是不難,不過要想做到效果二字就要動一番腦筋了。讓我們來看看win7下的圖標拖放,鼠標會拖起一個半透明的圖標副本到你想要的位置,透過這個透明的圖標你可以看到其下面的情況,這樣的效果其實在windows的早期版本就已經實現了,它有着很好的用戶體驗。那么我們能不能輕松的實現類似的拖動效果呢?答案當然是肯定的!最近看到論壇里幾個討論拖動的帖子,正巧前一段時間自己也做了一些相關的工作,小研究了一下,於是就想把研究成果拿出來和大家分享,這樣才有利於交流和進步嘛。以前我寫博客沒貼過效果圖,以至於很多網友下載示例代碼之后發現不是自己想要的東西,這個確實不好,在此我向大家表示歉意。這次把效果圖貼上,如果覺得這個效果很一般或者不是你所需要的那就不要浪費你寶貴的時間閱讀文章和下載代碼了。
從圖中可以看出,我的小豬頭像是可以被拖動的,半透明的那個就是拖動的副本,截圖的時候鼠標沒有截到,呵呵。為了讓半透明效果能夠明顯的看出來我特意為對話框貼了張背景圖。被拖動的其實是一個picture ctrl,也就是一個靜態控件,當然通過后面的介紹大家會發現這個方法的擴展性比較強,可以應用於很多場合,甚至可以應用於非控件的拖動對象的情況。好了,效果就是這樣了,下面切入正題開始介紹實現方法。
對於熟悉拖動效果制作的朋友們都應該知道,實現拖動有一個很簡單的方法就是通過CImageList。CImageList提供了BeginDrag、DragEnter、DragMove、DragLeave、EndDrag系列函數,分別在OnLButtonDown、OnMouseMove和OnLButtonUp等消息中合理調用這些函數就可以輕松實現對CImageList的元素的拖動效果。那么我們要做的就是構造一個CImageList,使它的元素是我們想要拖動的圖片,這樣就大功告成了。那怎樣獲取圖像呢?答案也很簡單,就是到被拖動的對象的DC中將所要拖動的區域拷貝到一個內存位圖中即可。具體到我的這個例子,我是這樣實現的:
在OnLButtonDown中判斷鼠標是否在控件范圍內,如果在就將控件范圍內的DC內容拷貝到內存位圖中,然后創建CImageList將包含有控件內容的位圖添加進CImageList作為其元素,接着通過這個ImageList實現拖動。具體代碼如下
void CDragDemoDlg::OnLButtonDown(UINT nFlags, CPoint point) { CRect rectPic; POINT ptPut = point; GetDlgItem(IDC_STATIC_DEMO)->GetWindowRect(rectPic); ClientToScreen(&ptPut); if(rectPic.PtInRect(ptPut)) { CBitmap bitmapTemp, *pOldBitmap; CDC *pDC = GetDlgItem(IDC_STATIC_DEMO)->GetDC(), *pMemDC = new CDC; //創建位圖內存 bitmapTemp.CreateCompatibleBitmap(pDC, rectPic.Width(), rectPic.Height()); pMemDC->CreateCompatibleDC(pDC); pOldBitmap = pMemDC->SelectObject(&bitmapTemp); pMemDC->BitBlt(0, 0, rectPic.Width(), rectPic.Height(), pDC, 0, 0, SRCCOPY); pMemDC->SelectObject(pOldBitmap); delete pMemDC; ReleaseDC(pDC); m_bIsLButtonDown = TRUE; m_ptOffset.x = ptPut.x-rectPic.left; m_ptOffset.y = ptPut.y-rectPic.top; m_imgDrag.DeleteImageList(); m_imgDrag.Create(rectPic.Width(), rectPic.Height(), ILC_COLOR32|ILC_MASK, 0, 1); m_imgDrag.Add(&bitmapTemp, RGB(0, 0, 0)); m_imgDrag.BeginDrag(0, m_ptOffset); m_imgDrag.DragEnter(NULL, ptPut); SetCapture(); } CDialog::OnLButtonDown(nFlags, point); }
接下來是移動的處理,其實很簡單就是一個DragMove函數。他有一個參數,也是一個點,意義和DragEnter的ptPut參數相似。例子中我限制了圖標不能超出窗口范圍,也是通過修改這個參數實現的。理論上我們可以拖着圖標在屏幕范圍內任意移動,不過結合這個例子如果在窗口范圍以外釋放鼠標那控件就找不到了,所以我做了限制,同時也可以更好的理解m_ptMove參數的意義。具體實現可以參考以下代碼。
void CDragDemoDlg::OnMouseMove(UINT nFlags, CPoint point) { if(m_bIsLButtonDown) { CRect rtClient, rtPicture; m_ptMove = point; GetDlgItem(IDC_STATIC_DEMO)->GetWindowRect(rtPicture); GetClientRect(rtClient); ClientToScreen(&rtClient); ClientToScreen(&m_ptMove); if(rtClient.left>m_ptMove.x-m_ptOffset.x) m_ptMove.x = rtClient.left+m_ptOffset.x; if(rtClient.top>m_ptMove.y-m_ptOffset.y) m_ptMove.y = rtClient.top+m_ptOffset.y; if(rtClient.right-rtPicture.Width() m_ptMove.x = rtClient.right-rtPicture.Width()+m_ptOffset.x; if(rtClient.bottom-rtPicture.Height() m_ptMove.y = rtClient.bottom-rtPicture.Height()+m_ptOffset.y; CImageList::DragMove(m_ptMove); } CDialog::OnMouseMove(nFlags, point); }
void CDragDemoDlg::OnLButtonUp(UINT nFlags, CPoint point) { if(m_bIsLButtonDown) { CRect rectPic; CWnd* pPic = GetDlgItem(IDC_STATIC_DEMO); ScreenToClient(&m_ptMove); pPic->GetWindowRect(rectPic); pPic->MoveWindow(m_ptMove.x-m_ptOffset.x, m_ptMove.y-m_ptOffset.y, rectPic.Width(), rectPic.Height()); m_bIsLButtonDown = FALSE; CImageList::DragLeave(this); CImageList::EndDrag(); ReleaseCapture(); pPic->Invalidate(); } CDialog::OnLButtonUp(nFlags, point); }
再說一個題外話,本文是通過Windows Live Writer編輯並發布的,這個工具是論壇里的muzizongheng推薦的,確實很好用,在此也對muzizongheng表達一下謝意。本文只是對拖動效果的一個簡單實現,如果有更好的方法還望大家賜教,在此謝過
void CDragDemoDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) { CDialog::OnActivate(nState, pWndOther, bMinimized); if(nState==WA_INACTIVE)//當失去焦點后, { m_bIsLButtonDown = FALSE; CImageList::DragLeave(this); CImageList::EndDrag(); ReleaseCapture(); } }