Windows 剪貼板不僅能傳遞文字、圖片,更支援檔案物件的跨程式傳遞。本文詳細解析如何在 VC++ 應用程式中:
核心在於理解剪貼板中 CF_HDROP 格式與 Preferred DropEffect 格式的資料結構與操作流程。
剪貼板不存檔名字串,而是存一個 HDROP 控制碼,指向內部 DROPFILES 結構:
typedef struct _DROPFILES {
DWORD pFiles; // 檔案清單相對結構起始位移 (通常 = sizeof(DROPFILES))
POINT pt; // 滑鼠座標 (客戶區/螢幕座標由 fNC 決定)
BOOL fNC; // TRUE=客戶區座標, FALSE=螢幕座標
BOOL fWide; // TRUE=Unicode 檔名, FALSE=ANSI
} DROPFILES;
緊接著結構體之後是雙重空字元結尾的檔名陣列 (Unicode 時為 WCHAR):檔名1\0檔名2\0...\0\0。
非標準格式,需動態註冊:
UINT uDropEffect = RegisterClipboardFormat(_T("Preferred DropEffect"));
資料為單一 DWORD 值,定義於 <oleidl.h>:
#define DROPEFFECT_COPY 1 // 複製
#define DROPEFFECT_MOVE 2 // 移動 (剪切即為移動)
#define DROPEFFECT_LINK 4 // 建立連結
#define DROPEFFECT_SCROLL 0x80000000 // 捲動
#include <Shellapi.h>
#include <Oleidl.h>
void CheckClipboardFiles(HWND hWnd)
{
UINT uDropEffect = RegisterClipboardFormat(_T("Preferred DropEffect"));
if (!OpenClipboard(hWnd)) return;
HDROP hDrop = (HDROP)GetClipboardData(CF_HDROP);
if (hDrop)
{
// 取得操作類型
DWORD* pdwEffect = (DWORD*)GetClipboardData(uDropEffect);
DWORD dwEffect = pdwEffect ? *pdwEffect : DROPEFFECT_COPY;
// 列舉檔案
UINT cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
TCHAR szFile[MAX_PATH];
CString strList;
for (UINT i = 0; i < cFiles; ++i)
{
DragQueryFile(hDrop, i, szFile, MAX_PATH);
strList += szFile;
strList += _T("\n");
}
// 依類型執行複製或移動
if (dwEffect & DROPEFFECT_MOVE)
{
// MoveFileEx(...)
MessageBox(NULL, strList, _T("Move Files"), MB_OK);
}
else if (dwEffect & DROPEFFECT_COPY)
{
// CopyFile(...)
MessageBox(NULL, strList, _T("Copy Files"), MB_OK);
}
}
CloseClipboard();
}
需準備兩組全域記憶體物件 (HGLOBAL) 並 SetClipboardData:
#include <Shellapi.h>
#include <Shlobj.h>
#include <Oleidl.h>
VOID CutOrCopyFiles(LPCSTR lpBuffer, UINT uBufLen, BOOL bCopy)
{
// 1. 註冊格式
UINT uDropEffect = RegisterClipboardFormat(_T("Preferred DropEffect"));
// 2. 準備 DropEffect 資料
HGLOBAL hGblEffect = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, sizeof(DWORD));
DWORD* pdwEffect = (DWORD*)GlobalLock(hGblEffect);
*pdwEffect = bCopy ? DROPEFFECT_COPY : DROPEFFECT_MOVE;
GlobalUnlock(hGblEffect);
// 3. 準備 HDROP (DROPFILES + 檔名清單)
DROPFILES df = {0};
df.pFiles = sizeof(DROPFILES);
df.pt.x = df.pt.y = 0;
df.fNC = FALSE; // 螢幕座標
df.fWide = TRUE; // Unicode
// 計算緩衝區大小:結構 + 雙倍字元長度 (Unicode) + 雙重結尾
UINT uGblLen = sizeof(DROPFILES) + uBufLen * 2 + 8;
HGLOBAL hGblFiles = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, uGblLen);
LPBYTE pData = (LPBYTE)GlobalLock(hGblFiles);
// 複製 DROPFILES 結構
memcpy(pData, &df, sizeof(DROPFILES));
LPWSTR pFileList = (LPWSTR)(pData + sizeof(DROPFILES));
// ANSI/UTF-8 轉 Unicode (lpBuffer 為 "path1\0path2\0" 格式)
MultiByteToWideChar(CP_ACP, MB_COMPOSITE, lpBuffer, uBufLen, pFileList, uBufLen);
GlobalUnlock(hGblFiles);
// 4. 寫入剪貼板
if (OpenClipboard(NULL))
{
EmptyClipboard();
SetClipboardData(CF_HDROP, hGblFiles);
SetClipboardData(uDropEffect, hGblEffect);
CloseClipboard();
}
// 注意:剪貼板接管 HGLOBAL 所有權,不可在此釋放
}
// 複製兩個檔案
char szFiles[] = "c:\\1.txt\0c:\\2.txt\0";
CutOrCopyFiles(szFiles, sizeof(szFiles), TRUE); // TRUE = Copy
// 剪切兩個檔案
CutOrCopyFiles(szFiles, sizeof(szFiles), FALSE); // FALSE = Cut (Move)
SetClipboardData 成功後,記憶體由系統管理,程式不得釋放。原始碼包含 CutCopy.exe:
編譯環境:VC6.0 + Platform SDK (無需 MFC) / Windows 2000 測試通過。