使用IDropTarget介面同時支援文字和檔案拖放 (轉)
使用IDropTarget介面同時支援文字和拖放
to:vcbear@163.com">vcbear
關於的外殼擴充套件,拖放是比較簡單的一種,在網上可以找到不少介紹這個技巧的文章。大部分是介紹使用MFC的COleDropTarget實現的,我覺得一般使用COleDropTarget已經很好了,但是我習慣在一些模組中,完全的不使用MFC,比如純SDK程式設計,還有用在ATL的時候,MFC是相當累贅的。所以COleDropTarget在這個意義上講不夠完美。
參考了MSDN以及的相關文章和程式碼(by Thomas Blenkers)之後,我發現拖放實際上主要使用了IDropTarget的介面方法,非常簡單,不妨直接面對原始IDropTarget實現自己的拖放類。
作為學習筆記,就有了這麼一篇文字,以拋磚引玉:
IDropTarget是留給支援拖放的客戶程式的一個純虛介面,事先沒有對介面的任何進行實現,而是讓透過實現介面函式來接管拖放的結果。IDropTarget介面有以下成員函式:
- 基本COM成員函式
QueryInterface
AddRef
Release
- 接管拖放事件的成員函式:
DragEnter
DragOver
DragLeave
Drop
也就是說,要在客戶程式裡實現以上7個函式的實體。
系統在檢測到拖放發生的時候,會在合適的時候依次客戶程式裡實現的IDropTarget介面相應函式,檢查使用者在這些函式里返回的標誌,決定滑鼠外觀表現和拖放結果。
實現IDropTarget介面
為此建立一個基類為IDropTarget的類:
class CDropTargetEx : public IDropTarget
IDropTarget介面在OLEIDL.h裡定義,為純虛介面。
在CDropTargetEx裡依次宣告介面所包含的7個函式,原形為:
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppv);
ULONG STDMETHODCALLTYPE AddRef(void);
ULONG STDMETHODCALLTYPE Release(void);
HRESULT STDMETHODCALLTYPE DragOver(D grfKeyState,
POINTL pt,
DWORD *pdwEffect);
HRESULT STDMETHODCALLTYPE DragEnter(IDataObject * pDataObject,
DWORD grfKeyState, POINTL pt,
DWORD * pdwEffect);
HRESULT STDMETHODCALLTYPE DragLeave(void);
HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObj,
DWORD grfKeyState,
POINTL pt,
DWORD __RPC_FAR *pdwEffect);
(為了實現Addref計數,還有一個ULONG tb_RefCount成員變數是必須的。QueryInterface,AddRef,Release這3個函式的實現是COM知識中最基本的,請參見附例)
在講解IDropTarget其他函式的具體實現之前,有必要介紹一下一個你可能永遠不會直接呼叫但是確實存在的函式:DoDragDrop函式.此函式在某資料來源的資料被拖動的時候就被呼叫,它負責
- 檢測目標視窗是否支援拖放,發現目標視窗的IDropTarget介面
- 隨時跟蹤滑鼠和鍵盤的狀態,根據狀態決定呼叫其DrageEnter,DragMove,Drop或DragLeave介面
- 從這些介面獲取客戶程式的返回值,根據這些值和使用者介面以及資料來源進行互動。
可以說DoDragDrop控制拖放的整個過程,我們要做的,只是將這個過程裡發生的事件,接管下來並得到相應的資訊,和DoDragDrop進行互動而已。瞭解了這一點有助於我們理解為什麼透過區區一個介面4個函式就可以實現了拖放的效果,因為系統為我們已經做了很多。
另一個非常重要的是RegisterDragDrop,這個函式的原形是這樣的:
WINOLEAPI RegisterDragDrop(
HWND hwnd,
IDropTarget * pDropTarget
);
不用被WINOLEAPI嚇到,這是一個宏:
#define STDAPI EXTERN_C HRESULT STDAPICALLTYPE
也就是表示一個標準的WIN API函式,返回一個HRESULT的值。
函式RegisterDragDrop的作用是告訴系統:某個視窗(hwnd引數指定)可以接受拖放,接管拖放的介面是pDropTarget。
記住在呼叫RegisterDragDrop之前,一定要先呼叫OleInitialize初始化OLE環境。
在類CDropTargetEx裡設計了一個函式
BOOL CDropTargetEx::DragDropRegister(HWND hWnd,
DWORD AcceptKeyState=|MK_LBUTTON)
{
if(!IsWindow(hWnd))return false;
HRESULT s = ::RegisterDragDrop (hWnd,this);
if(SUCCEEDED(s))
{
m_hTargetWnd = hWnd;
m_AcceptKeyState = AcceptKeyState;
return true;
}
else { return false; }
}
在這個函式里呼叫RegisterDragDrop,將this指標傳入,表示本類實現了IDropTarget.,由本類接管拖放事件。另外順便定義了一下拖放滑鼠和鍵盤特性常數,對這個類來說,我希望預設的只接受滑鼠左鍵的拖放,所以,預設的AcceptKeyState值是MK_LBUTTON。相關的鍵盤滑鼠常數還有MK_SHIFT,MK_ALT,MK_RBOTTON,MK_MBUTTON,MK_BOTTON等幾個,我想這個幾個常數從字面上就可以理解它的意思了。這些常數可以用“位與”的操作組合。
以下具體討論IDropTarget的拖放相關介面函式(4個),這裡的拖放以文字和檔案為主。
- DragEnter
當你用滑鼠選中了某一個檔案或一段文字,並且將滑鼠移到某個可以接受拖放(已經呼叫過RegisterDragDrop)的視窗裡,DragEnter將第一時間被呼叫。再看一下其原形:
HRESULT DragEnter( IDataObject * pDataObject,
DWORD grfKeyState,
POINTL pt,
DWORD * pdwEffect )
pDataobject 是從拖放的原資料中傳遞過來的一個IDataObject介面例項,包含資料物件的一些相關方法,可以透過此介面獲得資料。
grfKeyState 為DragEnter被呼叫時當前的鍵盤和滑鼠的狀態,包含上面介紹過的鍵盤滑鼠狀態常數。
pt 表示滑鼠所在的點。是以整個螢幕為參考座標的。
pdwEffect 是DoDragDrop提供的一個DWORD指標,客戶程式透過這個指標給DoDragDrop返回特定的狀態。有效的狀態包括:
DROPEFFECT_NONE=0 表示此視窗不能接受拖放。
DROPEFFECT_MOVE=1 表示拖放的結果將使源物件被刪除
DROPEFFECT_COPY=2 表示拖放將引起源物件的複製。
DROPEFFECT_LINK =4 表示拖放源物件建立了一個對自己的連線
DROPEFFECT_SCROLL=0x80000000表示拖放目標視窗正在或將要進行卷滾。此標誌可以和其他幾個合用
對於拖放物件來說,一般只要使用DROPEFFECT_NONE和DROPEFFECT_COPY即可。
在DragEnter裡要做什麼呢?主要是告知拖放已經進入視窗區域,並判斷是否支援某具體型別的拖放。
首先,要判斷鍵盤的狀態。在呼叫DragDropRegister時我傳入了一個AcceptKeyState並將其儲存在m_AcceptKeyState成員變數裡,現在可以拿它跟這裡得到的grfKeyState比較:
if(grfKeyState!=m_AcceptKeyState )
{
*pdwEffect = DROPEFFECT_NONE;
return S_OK;
}
如果鍵盤和滑鼠的狀態和我期望的不一樣,那麼pdwEffect裡返回DROPEFFECT_NONE表示不接受拖放。
然後,判斷拖放過來的IDataObject物件裡有沒有我感興趣的資料。
這裡要介紹的是兩個關鍵的結構體FORMATETC和STDMEDIUM
FORMATETC是OLE資料的一個關鍵結構,對某種裝置,資料,和相關做了格式上的描述。
其定義為
typedef struct tagFORMATETC
{
CLIPFORMAT cfFormat;
DVTARGETDEVICE *ptd;
DWORD dwect;
LONG lindex;
DWORD tymed;
}FORMATETC, *LPFORMATETC;
在這裡我們最感興趣的是cfFormat和tymed兩個資料。cfFormat是標準的“粘帖板”資料型別比如CF_TEXT之類。tymed表示資料所依附的媒介,比如,檔案,物件等等。其他的成員可以參見MSDN。
一個典型的FORMATETC結構變數定義如下:
FORMATETC cFmt = {(CLIPFORMAT) CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
IDataObject提供了一個GetData介面來獲取其例項裡包含的資料,比如:
STGMEDIUM stgMedium;
ret = pDataObject->GetData(&cFmt, &stgMedium);
GetData傳入cFmt,以指出所感興趣的資料,並將返回在stgMedium結構裡。
STGMEDIUM的定義如下1
typedef struct tagSTGMEDIUM
{
DWORD tymed;
[switch_type(DWORD), switch_is((DWORD) tymed)]
union {
[case(TYMED_GDI)] HBITMAP hBitmap;
[case(TYMED_MFPICT)] HMETAFILEPICT hMetaFilePict;
[case(TYMED_ENHMF)] HENHMETAFILE hEnhMetaFile;
[case(TYMED_HGLOBAL)] HGLOBAL hGlobal;
[case(TYMED_FILE)] LPWSTR lpszFileName;
[case(TYMED_ISTREAM)] IStream *pstm;
[case(TYMED_ISTORAGE)] IStorage *pstg;
[default] ;
};
[unique] IUnknown *pUnkForRelease;
}STGMEDIUM;
typedef STGMEDIUM *LPSTGMEDIUM;
看起來頗為複雜,其實主要是一系列控制程式碼或資料物件介面的聯合,根據資料具體的型別,使用其中之一即可。tymed和FORMATETC裡一樣,指出資料的載體型別(遺憾的是它不能指出具體的標準型別比如CF_TEXT或者其他)。至於pUnkForRelease,是源資料指定的一個介面,用來傳遞給ReleaseStgMedium函式,如果它不為NULL,則ReleaseStgMedium函式使用這個介面釋放資料。如果為NULL,則ReleaseStgMedium函式使用預設的IUnknown介面。對於常規的拖放來說,這個物件指標應該為NULL.
得到了控制程式碼或資料物件介面,也相當於得到了拖放的資料。
定義一個特定的FORMATETC結構例項傳遞給IDataObject的GetData,可以直接詢問和獲取某一種特定的資料。如果我們對我們想要的資料是非常確定的,這是比較有的方法。但是如果我們期望能夠對拖放的物件進行自適應的話,我們可以採取列舉IDataObject裡包含的所有資料型別的方案。這就要用到IEnumFORMATETC 介面了。
IEnumFORMATETC介面從IDataObject介面裡獲取:
IEnumFormatETC *pEnumFmt = NULL;
ret = pDataObject->EnumFormatEtc (DATADIR_GET,&pEnumFmt);
如果獲取成功,則可以透過IEnumFORMATETC介面的Next方法,來列舉所有的資料格式:
pEnumFmt->Reset ();
HRESULT Ret=S_OK
while(Ret!=S_OK)
{
Ret=pEnumFmt->Next(1,&cFmt,&Fetched);
if(SUCCEEDED(ret))
if( cFmt.cfFormat == CF_TEXT
||cFmt.cfFormat == CF_HDROP)
{
if(GetDragData(pDataObject,cFmt))
EnterResult = true;
}
}
第一個參數列示一次獲取的FORMATETC結構資料的數量,cFmt是一個FORMATETC指標,指向一個資料緩衝,用來返回FORMATETC資料。,Fetched是Next呼叫後得到的FORMATETC資料個數。一般一次獲取一個,直到Next返回不為S_OK。
我們可以對每個得到cFmt呼叫IDataObject->GetData方法,但是一般來說,一個資料物件包含的資料不止一種,而且一般有一些自定義的資料型別(關於自定義資料型別,參見:RegisterClipboardFormat,如果要自己實現Drag/Drop源資料,這個函式是有用的),對此我們不感興趣,因為這裡只要求處理文字和檔案的拖動,為此,只處理cfFormat為CF_TEXT和CF_HROP的資料:
GetDragData為CDropTargetEx類的一個成員函式:
///////////////////////////////////////////////////
//Get The DragData from IDataObject ,save in HANDEL
BOOL CDropTargetEx::GetDragData(IDataObject *pDataObject,FORMATETC cFmt)
{
HRESULT ret=S_OK;
STGMEDIUM stgMedium;
ret = pDataObject->GetData(&cFmt, &stgMedium);//GetData(CF_TEXT, &stgMedium);
if (FAILED(ret))
{
return FALSE;
}
if (stgMedium.pUnkForRelease != NULL)
{
return FALSE;
}
///////////////////////////////////////////
switch (stgMedium.tymed)
{
case TYMED_HGLOBAL:
{
LPDRAGDATA pData = new DRAGDATA;
pData->cfFormat = cFmt.cfFormat ;
memcpy(&pData->stgMedium,&stgMedium,sizeof(STGMEDIUM));
m_Array.push_back(pData);
return true;
break;
}
default:
// type not supported, so return error
{
::ReleaseStgMedium(&stgMedium);
}
break;
}
return false;
}
在這個成員函式里,根據cFmt,呼叫IDataObject->GetData函式獲得資料(對於CF_TEXT和CF_HROP來說,資料的媒介載體tymed都是HGLOBAL型別的)。
在具體實現的時候,我定義了一個結構:
typedef struct _DRAGDATA
{
int cfFormat;
STGMEDIUM stgMedium;
}DRAGDATA,*LPDRAGDATA;
將STGMEDIUM和資料型別(比如CF_TEXT,記錄在cfFormat)都記錄在DRAGDATA裡。並且使用了一個vector陣列,將這個結構儲存在陣列裡。對於不是我們想要的STGMEDIUM資料,我們馬上呼叫ReleaseStgMedium函式進行釋放,免得造成記憶體洩露。
這樣,DragEnter的工作就基本完成了,最後需要做的就是給DoDragDrop返回相應的狀態:如果我們獲得了想要的資料就給* pdwEffect賦值為DROPEFFECT_COPY,否則,就是DROPEFFECT_NONE;
如果支援拖放,滑鼠形狀將變成一個有接受意義的圖示,否則,是一個拒絕意義的圖示。
- DragOver
滑鼠拖動物件進入視窗之後,將會在視窗範圍內移動,這時DoDragDrop就會呼叫IDropTarget的DragOver介面。其原形為:
HRESULT DragOver(
DWORD grfKeyState
POINTL pt,
DWORD * pdwEffect
)
相對來說對於這個介面方法的實現可以簡單的多:只要根據grfKeyState判斷鍵盤和滑鼠的狀態是否符合要求,根據pt傳入的滑鼠點判斷該點是否支援拖放(比如將拖放區域限制在視窗的一部分的話),然後為*pdwEffect賦值為DROPEFFECT_COPY或DROPEFFECT_NONE.當然,還可以做一些你喜歡的事情,比如把滑鼠座標列印到螢幕上。不過為了和起見,建議不要做延時明顯的操作。
- DragLeave:
這個方法沒有傳入引數,相當簡單。
當拖動的滑鼠離開了視窗區域,這個方法將被呼叫,你可以在這裡寫一些清理記憶體的程式碼。在CDropTargetEx類裡,由於在DragEnter裡new了一些資料結構,並加到一個指標陣列裡,所以我必須在這裡對此資料進行清理,對此結構裡的STDMEDIUM呼叫ReleaseStgMedium然後Delete該結構。
另外,如果需要的話,可以通知使用者滑鼠指標已經離開了拖放區域。
- Drop
如果滑鼠沒有離開視窗,而是在視窗內釋放按紐,那麼拖放時間的“放”就在這時發生,IDropTarget介面的Drop方法被呼叫。其原形為
HRESULT Drop(
IDataObject * pDataObject,
DWORD grfKeyState,
POINTL pt,
DWORD * pdwEffect
)
有些資料建議在這裡才呼叫pDataObject->GetData方法獲取資料,在CDropTargetEx類裡,資料實際上已經在DragEnter裡獲取了。這樣做的理由是我希望一開始就獲得資料,從它本身進行判斷是否支援拖放,而不是在“放”的時候才判斷是否合法資料。
既然資料已經獲得,那麼我就可以從儲存資料的指標陣列裡提取出STGMEDIUM資料來,並根據資料的具體格式進行處理(最後一定要記住對STGMEDIUM進行ReleaseStgMedium)
對於CF_TEXT型別的資料,STGMEDIUM的成員hGlobal裡包含的是一段全域性記憶體資料。獲取這些資料的方法是:
TCHAR *pBuff = NULL;
pBuff=(LPSTR)GlobalLock(hText);
GlobalUnlock(hText);
則得到一個指向記憶體資料的指標pBuff。在我這個例子裡一般是一段""結尾的文字字串。這樣就實現了文字的拖放。
對於CF_HDROP型別的資料,STGMEDIUM成員hGlobal是一個HDROP型別的控制程式碼。透過這個控制程式碼,可以獲得拖放的檔案列表。如:
BOOL CDropTargetEx::ProcessDrop(HDROP hDrop)
{
UINT iFiles,ich =0;
TCHAR Buffer[MAX_PATH]="";
memset(&iFiles,0xff,sizeof(iFiles));
int Count = ::DragQueryFile(hDrop,iFiles,Buffer,0); //Get the Drag _Files Number.
if(Count)
for (int i=0;i { if(::DragQueryFile(hDrop,i,Buffer,sizeof(Buffer))) { //Got the FileName in Buffer } } ::DragFinish(hDrop); return true; } 獲得的Buffer是就是拖放的檔名,如果拖放的是多個檔案,在for迴圈裡可以依次獲取這些檔案的檔名。這樣就實現了檔案的拖放。
CDropTargetEx類使用非常簡單: 在客戶視窗的相關檔案中,定義一個CDropTargetEx例項:CDropTargetEx DropTarget; 在視窗建立之後,將視窗控制程式碼進行拖放註冊: DropTarget.DragDropRegister(hWnd); 或者 DropTarget.DragDropRegister(hWnd,MK_CONTROL|MK_LBUTTON); 表示滑鼠左鍵按下並且按住Ctrl鍵的拖放有效; 對於獲取拖放的結果,我使用的是回撥函式方式: 回撥原形 typedef VOID (_stdcall *DROPCALLBACK)(LPCSTR Buffer,int type); 在適當的地方(比如視窗的實現CPP裡)定義函式DropCallback: void _stdcall DropCallBack(LPCSTR Buffer,int type) 並且將其地址賦於DropTarget例項: DropTarget.SetCallBack(DropCallBack); 這樣,拖放文字到客戶視窗,回撥函式將被呼叫,引數Buffer為拖放的文字,format為CF_TEXT。而拖放檔案的時候,對每個被拖放的檔案都呼叫一次回撥函式,引數Buffer為檔案全路徑名,format為CF_HDROP。 示例的DropCallBack程式碼為: void _stdcall DropCallBack(LPCSTR Buffer,int format) { switch(format) { case CF_TEXT: { SetWindowText(hEdit,Buffer); break; } case CF_HDROP: { TCHAR Buf[2048]=""; sprintf(Buf,"File : is Drag and Drop to this Windows ,Open it?",Buffer); if(MessageBox(hMainWnd,Buf,"Question",MB_YESNO)==IDYES) { Execute(0,"open",Buffer,"","",SW_SHOW); } } default: break; } } 總結:使用IDropTarget實現通用的拖放,只要實現其7個介面,並且對得到的IDataObject用正確的格式(FORMATETC)呼叫正確的GetData獲取資料,返回DROPEFFECT決定拖放的特徵和結果,並處理拖放結果即可。 要注意的小問題是:
這個例子相當簡單,還可以簡化,比如取消vector,將獲得HGLOBAL控制程式碼作為成員變數儲存,或者將獲取資料的操作全部放到Drop方法裡。 對於拖放檔案,還有一個更簡單的方法:響應WM_DROPFILES 訊息。步驟是:
對於拖放的全面闡述,請參見MSDN->PlatformSDK Document->User Interface Services->Windows Shell裡關於“Tranerring Shell Objects with Drag-and-Drop and the Clipboard”一章。Windows Shell系統提供了很多介面,讓使用者利用和擴充這些介面,很方便的開發和使用豐富的shell服務,確實是一種很聰明的設計。 附/downfile.asp?fileid=61">例子和程式碼
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-991273/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 自制支援檔案拖放的VCL元件 (轉)元件
- Yoink for Mac(臨時檔案拖放助手)Mac
- 配置PHP使之能同時支援GIF和JPEG (轉)PHP
- Blazor 使用拖放(drag and drop)上傳檔案Blazor
- 機智使用elementUI呼叫一次介面同時上傳圖片和檔案,同時需要攜帶其他引數,實現呼叫後端介面UI後端
- tlistview使用--拖放操作 (轉)View
- 如何讓服務端同時支援WebSocket和SSL加密的WebSocket(即同時支援ws和wss)?服務端Web加密
- 實現檔案拖放的一種簡潔方法 (轉)
- C#讀取文字檔案和寫文字檔案C#
- 應用同時支援HTTP和HTTPSHTTP
- ASP中多檔案同時上傳解決方案 (轉)
- oracle 中使用批處理檔案,同時執行,多個.sql檔案。OracleSQL
- Html5下拉控制元件同時支援文字輸入和下拉程式碼HTML控制元件
- 使用純資源DLL檔案實現多語言選單、介面文字、Tooltips等 (轉)
- 如何同時複製、分類檔案
- perl檔案上傳程式,支援多檔案! (轉)
- PHP 使用檔案鎖 避免同時執行一個指令碼PHP指令碼
- 臨時檔案拖放暫存工具:Yoink for mac v3.6.89啟用版Mac
- 如何在 Unix 和 DOS 格式之間轉換文字檔案
- 同時使用資料庫鏈和序列時應注意的幾點(轉)資料庫
- 使用ln同步檔案內容,支援忽略檔案
- Nginx配置域名同時支援 https 和 http 訪問NginxHTTP
- 同時丟失控制檔案與資料檔案的恢復
- dwg、dxf檔案多行文字轉單行文字
- 使用PowerShell比較本地文字檔案與Web上的文字檔案是否相同Web
- VBA建立文字檔案、讀寫文字檔案
- 通過反射將物件轉化為檔案,同時反向將檔案還原為物件(適用配置檔案讀寫)反射物件
- 使用Junit 5時,如何同時使用 junit4和 PowerMockMock
- 將ASD光譜儀的.asd檔案轉為文字檔案
- 線上文字豎排排版工具--將橫版內容轉換為豎版,同時支援繁體和間隔符的自定義
- Blazor 拖放上傳檔案轉換格式並推送到瀏覽器下載Blazor瀏覽器
- PDF轉文字檔案的最簡單方法
- 什麼是介面?為什麼使用介面? 什麼時候使用介面?(轉)
- 一根Express Route同時支援ARM和ASM的VNETExpressASM
- 檔案排版(文字檔案讀寫)
- vue2.0和vue3.0同時使用Vue
- 【MATLAB】讀取和寫入文字檔案Matlab
- 一個專案同時支援VS2005和VS2008開啟