C++學習之:關於Windows剪貼板問題

一.本文將向讀者介紹下面兩個問題的解決方案:
1,用戶在資源管理器(Windows Explorer)中剪切/拷貝(Cut/Copy)文件,然後在自己的應用程序中進行粘貼(Paste)操作;
2.用戶在自己的應用程序中剪切/拷貝文件,在資源管理其中粘貼操作。

二.本文中的代碼編寫工具及測試環境:
1,VC6.0, Platform SDK(無須MFC);
2.Windows 2000。

三.概述
    我們知道,在Windows中可以通過剪貼板(Clipboard)來共享和傳遞數據,比如在資源管理器(Windows Explorer)中可以剪切/拷貝/粘貼文件。同樣我們也可以在自己的應用程序中通過剪貼板來完成這些工作,從而提高我們自己的應用程序與Windows操作系統之間的互操作性。但我們如何才能與資源管理器之類的應用程序共享和傳遞數據呢?本文提供的方法是:使用Windows本身提供的一些數據結構和API,通過剪貼板來實現數據共享和傳遞。 

四.實現方法
    首先,Windows在剪切/拷貝文件時並不是把文件名稱寫入剪貼板,而是在剪貼板中放入了一個DragAndDrop文件對象,並寫入了一個狀態值來標識操作類型(移動/拷貝,剪切其實就是移動,如果你剪切之後並沒有粘貼,那麼該文件依然存在而不會被刪除)。依據這個知識,我們首先來看看在應用程序中如何識別出Windows 資源管理器的剪切/拷貝動作。

在使用剪貼板前,我們首先要打開它:

  1. BOOL OpenClipboard(HWND hWnd);
  2. 參數 hWnd 是打開剪貼板的窗口句柄,成功返回TRUE,失敗返回FALSE。      
複製代碼

之後,可以用GetClipboardData來得到剪貼板中的數據:

  1. HANDLE GetClipboardData(UINT uFormat);
複製代碼

uFormat是所需要數據的格式,例如本文拖放對象的格式爲CF_HDROP。而表明該拖放對象類型(Move/Copy)的數據格式並不是Windows標準的剪貼板數據結構,而是一個簡單的DWORD指針。我們可以通過下面的語句來註冊一下數據類型 :

  1. UINT uDropEffect=RegisterClipboardFormat("Preferred DropEffect");
複製代碼

這裏返回的uDropEffect就是我們將要代入GetClipboardData函數的該數據結構的代碼, 
GetClipboardData函數返回是一個句柄,這只是Windows爲了統一性而做的工作,我們可以根據需要來轉換成相應的數據形式,比如我們的uDropEffect就 是一個DWORD指針。 
前面我已經說過在剪貼板中放的是一個拖放對象,因此我們可以通過如下語句得到該對象:

  1. HDROP hDrop = HDROP( GetClipboardData( CF_HDROP));
複製代碼

如果確實存在一個hDrop對象,我們應該取得uDropEffect的數據,以便我們處理後面的文件:

  1. DWORD dwEffect=*((DWORD*)(GetClipboardData( uDropEffcet)));
複製代碼

關於這個值的含義,我們只要包含一下"OLEIDL.H"頭文件即可,在該頭文件中5種狀態的定義而本文只關注:

  1. #define        DROPEFFECT_COPY        ( 1 )
  2. #define        DROPEFFECT_MOVE        ( 2 )
複製代碼

因此,我們可以通過

  1. if(dwEffect & DROPEFFECT_COPY)
  2.   CopyFile(....);
  3. else (dwEffect & DROPEFFECT_MOVE)
  4.   MoveFile(...);
複製代碼

來完成剪切/拷貝操作。 
在我們取得uDropEffect狀態之後,我們需要得到文件列表,得到拖放對象中的文件列表可以通過DragQueryFile來實現:

  1. UINT DragQueryFile(HDROP hDrop, UINT iFile,LPTSTR lpszFile,UINT cch);
複製代碼

第二個參數是文件序列號,可以通過將iFile置爲-1的方法來得到文件數量。 
最後我們給出完整的例子: #include <Shellapi.h>

  1. #include <oleidl.h>
  2.  
  3. ....
  4.  
  5.   UINT uDropEffect=RegisterClipboardFormat("Preferred DropEffect");
  6.  
  7.         if( OpenClipboard( hWnd)) {
  8.                 HDROP hDrop = HDROP( GetClipboardData( CF_HDROP));
  9.                 if( hDrop) {
  10.                         DWORD dwEffect,*dw;
  11.                         dw=(DWORD*)(GetClipboardData( uDropEffect));
  12.                         if(dw==NULL)
  13.                                 dwEffect=DROPEFFECT_COPY;
  14.                         else
  15.                                 dwEffect=*dw;
  16.             
  17.                         char Buf[4096];
  18.                         Buf[0] = 0;
  19.                         UINT cFiles = DragQueryFile( hDrop, (UINT) -1, NULL, 0);
  20.                         POINT Point;
  21.                         char szFile[ MAX_PATH];
  22.                         for( UINT count = 0; count < cFiles; count++ ) {
  23.                             DragQueryFile( hDrop, count, szFile, sizeof( szFile));
  24.                                 lstrcat(Buf,szFile);
  25.                                 lstrcat(Buf,"\n");
  26.                         }
  27.         
  28.                         if(dwEffect & DROPEFFECT_MOVE) {
  29.                                 MessageBox(NULL,Buf,"Move Files",MB_OK);
  30.                         } else        if(dwEffect & DROPEFFECT_COPY) {
  31.                                         MessageBox(NULL,Buf,"Copy Files",MB_OK);
  32.                         }
  33.  
  34.                         CloseClipboard();
  35.                 }
  36.         }
複製代碼

在這個例子中,我並沒有進行文件操作,只是簡單的顯示一個消息框,實際應用時,需要使用MoveFile和CopyFile函數來完成,本文不做討論。 
    知道如何識別其他程序的剪切/拷貝 文件的動作後,我們對該操作的數據結構已經很瞭解了,要想讓其他程序能識別我們的剪切/拷貝 文件動作其實就是將以上數據結構放入剪貼板的過程。 
在我們這個例子中,往剪貼板中放的數據必須是內存對象:HGLOBAL。這個對象可以通過GlobalAlloc來生成。然後使用GlobalLock就可以得到該對象的內存地址,繼而往裏面寫 數據。實際上在Win32中由於進程擁有獨立的內存空間,因而常規的內存分配已經不需要GlobalLock了,看看MSDN就知道該函數主要就是爲DDE和剪貼板服務的。 
    根據前面的知識,要想讓其他程序識別出我們的剪切/拷貝動作我們必須往剪貼板中放兩項數據,現在就讓我們來爲DropEffect準備數據吧,同樣我們需要先註冊該數據格式:

  1. uDropEffect=RegisterClipboardFormat("Preferred DropEffect");
複製代碼

然後分配內存對象並得到指針:

  1. hGblEffect=GlobalAlloc(GMEM_ZEROINIT|GMEM_MOVEABLE|GMEM_DDESHARE,sizeof(DWORD));
  2. dwDropEffect=(DWORD*)GlobalLock(hGblEffect);
複製代碼

注意往剪貼板中放的數據必須使用GMEM_MOVEABLE標誌,最後我們設置數據並解除鎖定:

  1. if(COPY)
  2.   *dwDropEffect=DROPEFFECT_COPY;
  3. else 
  4. *dwDropEffect=DROPEFFECT_MOVE;
  5. GlobalUnlock(hGblEffect);
複製代碼

這樣我就爲DropEffect準備還數據了,等一會兒我們連同文件拖放對象一起放入剪貼板。建立文件拖放對象的方法與DropEffect基本相同,只是文件拖放對象有特殊的數據結構 而不象DropEffect那樣簡單,該對象數據結構如下:

  1. +----------------------------+
  2. |  DROPFILES  |  Files List  |
  3. +----------------------------+
複製代碼

DROPFILES是拖放對象的頭數據,該結構在shlobj.h中定義:

  1. typedef struct _DROPFILES {
  2.     DWORD pFiles; 
  3.     POINT pt; 
  4.     BOOL fNC; 
  5.     BOOL fWide; 
  6. } DROPFILES, FAR * LPDROPFILES;
複製代碼

pFiles指針是以對象首地址爲參照的文件列表(上圖中的Files List項)的offset量。通常該值等於DROPFILES結構的長度(我還沒見過例外);pt表明文件拖放的位置座標,在這個例子裏我們忽略爲0; fNC表明pt值是否爲客戶區座標(FALSE表明是屏幕座標);fWide表明Files List是否包含unicode,作爲中國人,我們當然要設其爲TRUE。DROPFILES結構之後緊跟Files List,Files List是一組寬字符串,之間以0相隔,比如:"文件1\0文件2\0..." 
我們可以通過MultiByteToWideChar函數將常規的字符串轉換成寬字符串。下面就是生成拖放對象的代碼:

  1. uDropFilesLen=sizeof(DROPFILES);
  2. dropFiles.pFiles =uDropFilesLen;
  3. dropFiles.pt.x=0;
  4. dropFiles.pt.y=0;
  5. dropFiles.fNC =FALSE;
  6. dropFiles.fWide =TRUE;
  7.  
  8. uGblLen=uDropFilesLen+uBufLen<<1+8;
  9. //uBufLen是文件名字符傳組的長度,由於要轉換成寬字符,因此長度要乘2
  10.  
  11. hGblFiles= GlobalAlloc(GMEM_ZEROINIT|GMEM_MOVEABLE|GMEM_DDESHARE, uGblLen);
  12. szData=(char*)GlobalLock(hGblFiles);
  13. memcpy(szData,(LPVOID)(&dropFiles),uDropFilesLen);
  14. //將DROPFILES copy到頭部
  15.  
  16. szFileList=szData+uDropFilesLen;
  17. //得到存放文件列表的首地址
  18.  
  19. MultiByteToWideChar(CP_ACP,MB_COMPOSITE,
  20.         lpBuffer,uBufLen,(WCHAR *)szFileList,uBufLen);
  21.         
  22. GlobalUnlock(hGblFiles);
複製代碼

現在我們就可以將上面兩組數據放入剪貼板中了,注意在寫數據前應先清空剪貼板。爲了方便大家使用,下面我給出實現此功能的獨立的函數:

  1. VOID CutOrCopyFiles(char * lpBuffer,UINT uBufLen,BOOL bCopy)
複製代碼

lpBuffer是包括所有準備剪切/拷貝的文件名稱的緩衝區;uBufLen是lpBuffer的長度;bCopy決定該操作是Copy還是Cut,TRUE爲Copy,FALSE爲Cut。例如我們可以這樣調用該函數:

  1. char szFiles[]="c:\\1.txt\0c:\\2.txt\0";
  2. CutOrCopyFiles(szFiles,sizeof(szFiles),FALSE);
複製代碼

來剪切文件,或者使用:

  1. CutOrCopyFiles(szFiles,sizeof(szFiles),TRUE);
複製代碼

來拷貝文件。

  1. #include <Shellapi.h>
  2. #include <Shlobj.h>
  3. #include <oleidl.h>
複製代碼

......

  1. VOID CutOrCopyFiles(char *lpBuffer,UINT uBufLen,BOOL bCopy)
  2. {
  3.         UINT uDropEffect;
  4.         DROPFILES dropFiles;
  5.         UINT uGblLen,uDropFilesLen;
  6.         HGLOBAL hGblFiles,hGblEffect;
  7.         char *szData,*szFileList;
  8.  
  9.         DWORD *dwDropEffect;
  10.  
  11.         uDropEffect=RegisterClipboardFormat("Preferred DropEffect");
  12.         hGblEffect=GlobalAlloc(GMEM_ZEROINIT|GMEM_MOVEABLE|GMEM_DDESHARE,sizeof(DWORD));
  13.         dwDropEffect=(DWORD*)GlobalLock(hGblEffect);
  14.         if(bCopy)
  15.                 *dwDropEffect=DROPEFFECT_COPY;
  16.         else 
  17.                 *dwDropEffect=DROPEFFECT_MOVE;
  18.         GlobalUnlock(hGblEffect);
  19.  
  20.         uDropFilesLen=sizeof(DROPFILES);
  21.         dropFiles.pFiles =uDropFilesLen;
  22.         dropFiles.pt.x=0;
  23.         dropFiles.pt.y=0;
  24.         dropFiles.fNC =FALSE;
  25.         dropFiles.fWide =TRUE;
  26.  
  27.         uGblLen=uDropFilesLen+uBufLen*2+8;
  28.         hGblFiles= GlobalAlloc(GMEM_ZEROINIT|GMEM_MOVEABLE|GMEM_DDESHARE, uGblLen);
  29.         szData=(char*)GlobalLock(hGblFiles);
  30.         memcpy(szData,(LPVOID)(&dropFiles),uDropFilesLen);
  31.         szFileList=szData+uDropFilesLen;
  32.  
  33.         MultiByteToWideChar(CP_ACP,MB_COMPOSITE,
  34.                         lpBuffer,uBufLen,(WCHAR *)szFileList,uBufLen);
  35.         
  36.         GlobalUnlock(hGblFiles);
  37.  
  38.         if( OpenClipboard(NULL) )
  39.         {
  40.                 EmptyClipboard();
  41.                 SetClipboardData( CF_HDROP, hGblFiles );
  42.                 SetClipboardData(uDropEffect,hGblEffect);
  43.                 CloseClipboard();
  44.         }
  45. }
複製代碼

希望以上內容對你有所幫助。 
    本文附上一個Demo工程,編譯後生成CutCopy.exe程序,該程序的使用方法如下: 
    啓動程序後,可使用Windows 資源管理器等程序剪切/拷貝文件,然後點程序中的[CheckClipboard],Demo程序將分析剪貼板中的內容,並彈出消息框告知是Copy Files還是Cut Files,並給出文件列表.用戶點[OK]關閉消息框後,文件列表 將被放入文本框中,此時用戶可以通過[Cut]/[Copy]按鈕來改變剪貼板中的屬性。 
    同時,用戶可以通過[Browser]來選擇若干文件到文本框中,然後點[Cut]/[Copy]進行操作,之後,用戶既可以通過[CheckClipboard]檢查剪貼板中的內容也可以通過在Windows 資源管理器等程序中進行粘貼(Paste)來檢查其是否正確。

轉載於:http://www.cctry.com/thread-67916-1-1.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章