天天看點

建立客戶區視窗,清單框之間項的拖拽操作

我寫了一個小類庫,其中包含一個類,CDragDropMgr,用這個類可以在自己的應用程式視窗間添加拖拽行為。我還寫了一個測試程式,DDTest,示範了如何使用 CDragDropMgr 類(參見 Figure 2)。Figure 3 是程式運作的畫面。DDTest 有兩個清單框和一個編輯框。你可以将第一個清單框中的項目拖拽到第二個清單框,或者編輯框。此外,你還能在第二個清單框裡通過拖拽重排項目。DDTest 就是使用 CDragDropMgr 來實作上述這些功能的。下面我首先示範如何使用 CDragDropMgr,然後在探讨它的工作原理。

建立客戶區視窗,清單框之間項的拖拽操作

  Figure 3 運作中的 DDTest

  為了使用拖拽管理器,首先要在主視窗或對話框中執行個體化 CDragDropMgr,然後用一個表對之進行初始化,就像下面的代碼這樣:

1

2

3

4

5

6

7

<code>static DRAGDROPWND MyDragDropWindows[] = {</code>

<code>  { IDC_LIST1, DDW_SOURCE },</code>

<code>  { IDC_LIST2, DDW_SOURCE|DDW_TARGET },</code>

<code>  { IDC_EDIT1, DDW_TARGET },</code>

<code>  { 0, 0 },</code>

<code>};</code>

<code>m_ddm.Install(this, MyDragDropWindows);  </code>

  我的專欄的愛好者們知道我程式設計的五大秘訣之一便是 一張表勝過一千行代碼。表的形式比長長的一串過程代碼更加簡練、優雅、可靠和可維護。在本文的例子中,表告訴拖拽管理器哪個子視窗是拖拽操作的源和/或目标。每一個表入口都有一個子視窗ID以及一個 DDW_SOURCE 和 DDW_TARGET 的組合标志。在 DDTest 中,第一個清單框是源,第二個清單框既可以是源,也可以是目标,編輯框隻能是目标。但是不管怎麼樣,不要忘了在表的末尾加上 NULL!

  一旦你用視窗表對拖拽管理器進行了初始化,下一步便是改寫主視窗的 PreTranslateMessage 函數以便将消息傳遞給拖拽管理器:

<code>BOOL CMyDlg::PreTranslateMessage(MSG* pMsg)</code>

<code>{</code>

<code>  return m_ddm.PreTranslateMessage(pMsg) ? TRUE :</code>

<code>   CDialog::PreTranslateMessage(pMsg);</code>

<code>}</code>

  一切準備就緒,當使用者試圖從一個視窗到另一個視窗實施拖拽操作時,拖拽管理器便會察覺到并通知應用程式要做相應的處理。CDragDropMgr 可以發送四個消息/通知:WM_DD_DRAGENTER、WM_DD_DRAGOVER、WM_DD_ DRAGDROP 和 WM_DD_DRAGABORT。收到這些消息/通知後,做什麼樣的處理由你來決定。WM_DD_DRAGENTER 和 WM_DD_DRAGDROP 是拖拽操作必須要做的處理。其它兩個可選。WM_DD_DRAGABORT 用來處理使用者取消操作時的清除工作。WM_DD_ DRAGOVER 使你能夠在使用者實施拖拽操作而移動滑鼠時進行連續不斷的處理。DDTest 這樣簡單的程式不需要處理這些消息。

  當拖拽管理器發送 WM_DD_DRAGENTER 消息時,它在 LPARAM 中傳遞一個 DRAGDROPINFO 結構。你的任務是将 DRAGDROPINFO::data 指向一個包含你想要拖拽資料的 CDragDropData 執行個體,然後傳回 TRUE。如果拖拽是不允許的(也許使用者單擊了清單框中的某個死區(dead area))則應該傳回 FALSE,并不要設定 DRAGDROPINFO::data,CDragDropData 類似 COM 的 IDataObject,但要簡單得多:它儲存拟拽動的資料。CDragDropData 有三個虛拟函數:OnGetDragSize 獲得一個拖拽圖像的綁定矩形,OnDrawData 繪制拖拽圖像,OnGetData 擷取資料本身。我在我的庫中提供了一個叫 CDragDropText 的類,它實作了這些函數,用來處理文本拖拽操作。它将文本儲存在一個 CString 中。OnGetData 傳回這個串,OnGetDragSize 計算該文本矩形,OnDrawData 則繪制該文本:

<code>void CDragDropText::OnDrawData(CDC&amp; dc, CRect&amp; rc)</code>

<code> dc.DrawText(m_text, &amp;rc, DT_LEFT|DT_END_ELLIPSIS);</code>

<code>}   </code>

  如果你想得到這個文本,你唯一需要調用的函數是 OnGetData,拖拽管理器需要時在其内部調用 OnGetDragSize 和 OnDrawData。

  那麼所有這些工作是如何實作的呢?當 DDTest 收到 WM_DD_DRAGENTER 消息,它調用一個内部函數 GetLBItemUnderPt 來确定光标下是哪個清單框(如果有的話)。然後 DDTest 以這一項的文本作為資料建立一個 CDragDropText 對象并将 DRAGDROPINFO 中的 data 指針指向該對象:

8

9

<code>// in CMyDlg::OnDragEnter</code>

<code>DRAGDROPINFO&amp; ddi = *(DRAGDROPINFO*)lp;</code>

<code>int item = GetLBItemUnderPt(...);</code>

<code>if (item&gt;=0) {</code>

<code> CString text = // get item text</code>

<code> ddi.data = new CDragDropText(text);</code>

<code> return TRUE;  // allow drag-drop</code>

<code>return FALSE; // nothing to drag   </code>

  由 CDragDropMgr 來做剩餘的工作。當使用者拖拽它時在周邊繪制文本并根據光标是否出于拖拽目的地上方而相應地改變滑鼠光标。

  當使用者松開滑鼠,CDragDropMgr 便給應用程式發送一個 WM_DD_DRAGDROP 消息。暗示資料已經拖拽完成。對于 DDTest 而言,這意味着如果滑鼠出于編輯框上方,則要設定編輯框中的文本,或者如果滑鼠是在清單框上方,則要将文本添加到清單框中。在真正實作中,DDTest 稍顯複雜,因為它可以讓使用者重新安排第二個清單框中的項目。DDTest 有代碼可以察覺是否需要添加文本或修改清單框中文本的位置。具體細節就留給你來做了,OnDragDrop 實作的基本要點都是一樣的:

<code>// OnDragDrop handler</code>

<code>void* data = ddi.data-&gt;OnGetData();</code>

<code>// do something with data</code>

<code>return 0;   </code>

  以上都是關于文本的操作,如果要拖拽其它類型的資料怎麼辦呢?為此,你必須通過 CDragDropData 派生并改寫三個基本函數來擴充我的庫。例如,為了拖拽圖像,你得派生一個 CDragDropImage 類,在這個類中,OnGetData 傳回 BITMAP 或 CBitmap,OnGetDragSize 傳回位圖的尺寸,OnDrawData 調用 BltBit 或其它什麼函數來繪制該位圖。

  我已經示範了 CDragDropMgr 的使用方法,但它是如何工作的呢?基本思路很簡單。CDragDropMgr::PreTranslateMessage 查找發送到拖拽源視窗之一的滑鼠消息并發送相應的通知到你的應用程式主視窗。CDragDropMgr 實作了一個典型的具有三種狀态的有限狀态機:NONE、CAPTURED 和 DRAGGING。當使用者按下滑鼠鍵,CDragDropMgr 進入 CAPTURED 狀态。當使用者移動滑鼠,則進入 DRAGGING 狀态。具體細節簡單直白。

  CDragDropMgr 使用 PreTranslateMessage 而不是子類化主視窗,因為它需要解釋發送到可能的拖拽源視窗之一滑鼠消息,該拖拽源視窗由前述的拖拽視窗表确定。MFC 的優點之一是它在主視窗中僅通過虛拟 PreTranslateMessage 方法便可以過濾所有子視窗消息。這使得 CDragDropMgr 可以僅在單一的函數中便可截獲發送到任何潛在拖拽源視窗的滑鼠消息,進而避免了必須子類化每一個視窗。當 CDragDropMgr::PreTranslateMessage 看到 WM_LBUTTONDOWN,它便查找該視窗句柄(HWND)以便檢查它是否被列入源視窗表。如果它是一個源視窗,則進行拖拽初始化,否則忽略該消息。

  拖拽資料的機制是很簡單直白的,甚至有些單調無趣,是以細節我就不再贅言。唯一一個亮點是 CDragDropData 使用 CImageList 來繪畫。如果你實作自己的拖拽管理器,我鼓勵你也這麼做,CImageList 包含如下幾個函數:BeginDrag、DragEnter、DragMove 和 EndDrag,用它們可以很快解決比特繪畫問題,它們使用特有的光栅操作使圖像呈半透明,從其以前位置擦除等等。

  沒有 CImageList,這些繪制細節冗長乏味。有了它,CDragDropMgr 隻要将拖拽圖像繪制到圖像清單位圖一次即可。當使用者初始化拖拽操作時,CDragDropMgr 通知主應用程式,該主應用程式将 DRAGDROPINFO::data 設定為一個 CDragDropData,正如我前面描述的那樣。然後拖拽管理器調用 CDragDropData:: CreateDragImage (參見 Figure 4),它建立一個包含要繪制的圖像清單。CreateDragImage 調用虛拟函數 CDragDropData::OnGetDragSize 來擷取拖拽圖像的尺寸,CDragDropData::OnDrawData 将資料繪制到圖像清單的位圖中。一旦完成了些工作,CDragDropMgr 調用圖像清單函數繪制拖拽期間的圖像。例如,每次使用者移動滑鼠,CDragDropMgr 都調用 CImageList::DragMove。還有比這更容易的嗎?其優美之處在于這個代碼完全是通用的。為了處理新的資料類型,你隻要實作 OnGetDragSize 和 OnDrawData 即可。

<a href="http://file.ddvip.com/2006_04/1144179134_ddvip_8313.rar">本文示例代碼或素材下載下傳</a>