拖動是界面編程頻繁使用的一個效果,在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); }這裡我說明兩個問題: 一是BeginDrag(0, m_ptOffset);的m_ptOffset參數,BeginDrag函數很容易理解了,就是進入拖動狀態,而m_ptOffset參數是拖動時鼠標相對於拖動圖標的偏移,注意是相對偏移。大家可以自己改一下這個參數,比如改成CPoint(0, 0)來感受一下這個設置的作用。二是DragEnter(NULL, ptPut);的ptPut這個參數指定了初始拖動時圖標出現的位置,這裡注意這個位置不是圖標左上角的位置,而是左上角加上偏移後的位置。這個位置應用的也不是相對坐標或客戶區坐標,而是屏幕坐標。
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); }到此拖動效果就實現了,最後再說一點,例子中有關於鼠標捕獲和釋放的操作,目的是為了當鼠標離開窗口范圍仍然可以響應,而且針對窗口有可能被其它程序搶奪焦點的情況,例程中專門處理了OnActivate消息,具體實現可以參考示例源碼,就不在這裡贅述了。應該說通過CImageList實現拖動操作是十分方便的,而且效果也很好。據說VS2008下的CImageList還支持PNG,那樣就可以做出更炫的拖動效果了。
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(); } }