傳統剪貼板操作

【轉】http://blog.sina.com.cn/s/blog_7d2ec95b0101iz4b.html

剪貼板函數

Function Description
OpenClipboard 打開剪貼板
CloseClipboard 關閉剪切板
EmptyClipboard 刪除剪貼板上的內容
GetClipboardData 從剪貼板獲取數據
SetClipboardData 向剪貼板傳遞數據

將數據放入剪貼板需要四個步驟:

調用::OpenClipboard打開剪貼板。
調用::EmptyClipboard清空剪貼板。
調用::SetClipboardData向剪貼板傳遞全局內存塊或其它包含剪貼板數據的對象的句柄。
調用::CloseClipboard關閉剪貼板。

全局內存塊是調用::GlobalAlloc分配的內存塊,它返回一個HGLOBAL類型的句柄,在Win32應用中可以視爲通用句柄。一個名爲::GlobalLock的相關函數使用HGLOBAL,並返回指向該內存塊的指針。Windows程序員不經常使用::GlobalAlloc,在Win32中被::HeapAlloc取代。但是,它還是非常有用的,因爲剪貼板需要的是內存句柄,而不是指針。

下面的代碼,先將字符串複製到全局內存塊,再將內存塊交給剪貼板:

char szText[] = “Hello World!”;
if ( ::OpenClipboard (m_hWnd) {
::EmptyClipboard ();
HANDLE hData = ::GlobalAlloc (GMEM_MOVEABLE, ::lstrlen (szText) + 1);
LPSTR pData = (LPSTR) ::GlobalLock (hData);
::lstrcpy (pData, szText);
::GlobalUnlock (hData);
::SetClipboardData (CF_TEXT, hData);
::CloseClipboard ();
}

全局內存塊傳一旦交給剪貼板,分配該內存塊的應用既不應該使用它也不應該刪除它。剪貼板擁有該內存塊,下次調用::EmptyClipboard時,釋放內存塊。

傳遞給::OpenClipboard的唯一參數是打開剪貼板時,擁有剪貼板的窗口的句柄。在MFC應用中,CWnd的窗口句柄可以通過數據成員m_hWnd獲得。如果其它應用已經打開了剪貼板,調用::OpenClipboard就會失敗。Windows使用這種先打開再使用的方法,同步對共享資源的訪問,並且可以保證使用過程中,內容不會被修改。

從剪貼板獲取數據同樣簡單,步驟是:

調用::OpenClipboard打開剪貼板。
調用::GetClipboardData獲取全局內存塊或其它包含剪貼板數據的對象的句柄。
將數據從全局內存塊複製到局部變量。
調用::CloseClipboard關閉剪貼板。

下面的代碼獲取前一個例子放在剪貼板上的字符串:

char szText[BUFLEN];
if (::OpenClipboard (m_hWnd)) {
HANDLE hData = ::GetClipboardData (CF_TEXT);
if (hData != NULL) {
LPCSTR pData = (LPCSTR) ::GlobalLock (hData);
if (::lstrlen (pData) < BUFLEN)
::lstrcpy (szText, pData);
::GlobalUnlock (hData);
}
::CloseClipboard ();
}

19.1.1 剪貼板格式

::SetClipboardData和::GetClipboardData都用一個整數值設置剪貼板格式,它是傳遞過程中數據類型的標識符。前一節例子中使用的是CF_TEXT,表示數據是ANSI文本。Unicode文本使用另一種格式ID。

經常使用的剪貼板格式

Format Data Type
CF_BITMAP Windows位圖
CF_DIB 設備無關位圖
CF_ENHMETAFILE GDI增強型元文件
CF_METAFILEPICT 舊的GDI元文件,帶有大小和映射模式附加信息
CF_HDROP HDROP 格式的文件名列表
CF_PALETTE GDI調色板
CF_TEXT 8位ANSI字符組成的文本
CF_TIFF TIFF格式的位圖
CF_UNICODETEXT 16位Unicode字符組成的文本
CF_WAVE WAV格式的音頻數據

可以使用預定義的剪貼板格式像傳遞文本一樣傳遞位圖、調色板、增強型元文件等對象。例如,m_bitmap是CBitmap的一個數據成員,它保存一個位圖,可以按照下面的方法複製這個位圖,並將其放在剪貼板上:

if (::OpenClipboard (m_hWnd)) {
// Make a copy of the bitmap.
BITMAP bm;
CBitmap bitmap;
m_bitmap.GetObject (sizeof (bm), &bm);
bitmap.CreateBitmapIndirect (&bm);
CDC dcMemSrc, dcMemDest;
dcMemSrc.CreateCompatibleDC (NULL);
CBitmap* pOldBitmapSrc = dcMemSrc.SelectObject (&m_bitmap);
dcMemDest.CreateCompatibleDC (NULL);
CBitmap* pOldBitmapDest = dcMemDest.SelectObject (&bitmap);
dcMemDest.BitBlt (0, 0, bm.bmWidth, bm.bmHeight, &dcMemSrc, 0, 0, SRCCOPY);
HBITMAP hBitmap = (HBITMAP) bitmap.Detach ();
dcMemDest.SelectObject (pOldBitmapDest);
dcMemSrc.SelectObject (pOldBitmapSrc);
// Place the copy on the clipboard.
::EmptyClipboard ();
::SetClipboardData (CF_BITMAP, hBitmap);
::CloseClipboard ();
}

從剪貼板獲取位圖時,調用::GetClipboardData,並傳遞一個CF_BITMAP參數:

if (::OpenClipboard (m_hWnd)) {
HBITMAP hBitmap = (HBITMAP) ::GetClipboardData (CF_BITMAP);
if (hBitmap != NULL) {
// Make a local copy of the bitmap.
}
::CloseClipboard ();
}

注意這裏使用的模式。將數據放入剪貼板時,要告訴Windows數據的類型。獲取數據時,要詢問該類型的數據是否存在。如果不存在,::GetClipboardData返回NULL。

CF_HDROP剪貼板格式

最有意思的一種格式是CF_HDROP。使用該格式從剪貼板獲取數據時,得到的是HDROP,它實際上是一個全局內存塊的句柄。內存塊內部是一個文件名的列表。讀取文件名時,不必解析內存塊的內容,可以使用::DragQueryFile。下面的代碼從剪貼板獲取HDROP,並將文件名寫入列表框:

if (::OpenClipboard (m_hWnd)) {
HDROP hDrop = (HDROP) ::GetClipboardData (CF_HDROP);
if (hDrop != NULL) {
// Find out how many file names the HDROP contains.
int nCount = ::DragQueryFile (hDrop, (UINT) - 1, NULL, 0);
// Enumerate the file names.
if (nCount)
TCHAR szFile[MAX_PATH];
for (int i=0; i

ifdef UNICODE

pDropFiles->fWide = TRUE;

else

pDropFiles->fWide = FALSE;

endif

LPBYTE pData = (LPBYTE) pDropFiles sizeof (DROPFILES);
::CopyMemory (pData, szFiles, sizeof (szFiles));
::GlobalUnlock (hData);
::SetClipboardData(CF_HDROP, hData);
::CloseClipboard();
}

傳遞給::GlobalAlloc的參數GHND是GMEM_MOVEABLE和GMEM_ZEROINIT的組合。GMEM_ZEROINIT告訴::GlobalAlloc將內存塊的所有字節初始化爲0,這可以將沒有被初始化的DROPFILES成員設置爲0。在Win32環境中不再需要GMEM_MOVEABLE。

19.1.2 私用剪貼板格式

雖然預定義的剪貼板格式包含了很多種數據類型,但是它們不可能包括應用中所需的全部數據類型。Winodws允許註冊私有的剪貼板格式,並使用它們代替標準的剪貼板格式。

假設正在編寫一個Widget應用。你想讓用戶可以剪切或複製飾件到剪貼板,然後粘貼到文檔的其它位置。要實現這樣的功能,就要調用Win32的API函數::RegisterClipboardForamt爲飾件註冊一個私有剪貼板格式:

UINT nID = ::RegisterClipboardForamt (_T(“Widget”));

nID是私有剪貼板格式的ID。要複製飾件到剪貼板,就要複製定義飾件所需的全部數據,然後用nID和內存句柄調用::SetClipboardData:

::SetClipboardData (nID, hData);

要從剪貼板獲取飾件,可以向::GetClipboardData傳遞nID:

HANDLE hData = ::GetClipboardData (nID);

然後鎖定內存塊獲取指針,用內存塊的數據重建飾件。這裏的關鍵點是,如果10個不同的應用(或同一應用的10個不同實例)用相同的格式名調用::RegisterClipboardFormat,它們會得到相同的剪貼板格式ID。這樣,只要應用A和應用B調用::RegisterClipboardForamt時使用相同的格式名,應用B就可以獲取應用A複製到剪貼板上的飾件。

19.1.3 以多種格式提供數據

在剪貼板中放置多個項目是完全合法的,只要它們具有不同的格式。應用程序總是這麼做。這也是向多個應用提供數據的有效方法,即使有些應用並不理解私有剪貼板格式。

微軟的Excel就是一個例子。在Excel中選擇一定範圍的電子表格單元複製到剪貼板時,Excel最多可以在剪貼板上放置30個項目。其中之一是私有剪貼板格式,表示Excel自己的電子表格數據。另一個是表格單元格的CF_BITMAP譯文。Windows的Paint應用並不理解Excel的私有剪貼板格式,但是卻可以粘貼Excel電子表格單元到一個位圖。至少看上去Paint可以粘貼表格單元。實際上,粘貼的只是這些單元的位圖圖像,不是真的表格單元。甚至可以粘貼Excel數據到記事本,因爲格式中包含CF_TEXT。提供多種格式的表格數據,Excel提高了它的剪貼板數據的可移植性。

爲每一種格式調用::SetClipboardData就可以在剪貼板上放置多個項目:

::SetClipboardData (nID, hPrivateData);
::SetClipboardData (CF_BITMAP, hBitmap);
::SetClipboardData (CF_TEXT, hTextData);

19.1.4 查詢有效數據格式

查詢某種格式的剪貼板數據是否有效,可以調用::GetClipboardData,並查看返回值是否等於NULL。有時需要知道所有有效的格式,並從中選擇一個最需要的。可以使用下面的函數:

Function Description
CountClipboardFormats 返回可用格式的個數
EnumClipboardFormats 枚舉可用的格式
IsClipboardFromatAvailable 指示某一格式是否有效
GetPriorityClipboardFormat 獲取一個具有優先級別的列表,表示哪一個最先有效

::IsClipboardFormatAvailable是最簡單的一個。要查看CF_TEXT格式是否有效,可以這樣調用:

if (::IsClipboardFormatAvailable (CF_TEXT)) {
// Yes, it’s available
}
else {
// No, it’s not available
}

這個函數常用於實現Edit菜單Paste命令的更新處理函數。

即使剪貼板沒有打開,::IsClipboardFormatAvailable也可以工作。但是不要忘了,這時剪貼板很容易被改變。不要這樣編寫代碼:

if (::IsClipboardFormatAvailable (CF_TEXT)) {
if (::OpenClipboard (m_hWnd)) {
HANDLE hData = ::GetClipboardData (CF_TEXT);
LPCSTR pData = (LPCSTR) ::GlobalLock (hData);

::CloseClipboard ();
}
}

這個代碼是有問題的。在多任務環境中,::IsClipboardFormatAvailable執行後,調用::GetClipboardData之前,雖然機會很小,但是確實有機會改變剪貼板的數據。因此,要打開剪貼板之後再調用這個函數:

if (::OpenClipboard (m_hWnd)) {
if (::IsClipboardFormatAvailable (CF_TEXT)) {
HANDLE hData = ::GetClipboardData (CF_TEXT);
LPCSTR pData = (LPCSTR) ::GlobalLock (hData);

}
::CloseClipboard ();
}

這段代碼就不會出現什麼問題,因爲只有打開剪貼板的應用才能改變剪貼板的內容。

可以使用::EnumClipboardFormats循環訪問有效剪貼板格式的列表。例如:

if (::OpenClipboard (m_hWnd)) {
UINT nFormat = 0; // Must be 0 to start the iteration.
while (nFormat = ::EnumClipboardFormats (nFormat)) {
// Next Clipboard format is in nFormat.
}
::CloseClipboard ();
}

到達表尾時::EnumClipboardFormats返回0,因此獲取最後一個有效模式後,循環結束。如果只是想知道有效模式的個數,調用::CountClipboardFormats。

::GetPriorityClipboardFormat簡化了查詢多個剪貼板格式的過程。假設你的應用可以以私有格式nID、CF_TEXT、CF_BITMAP格式粘貼數據。你希望使用nID,如果無效,用CF_TEXT代替,如果也無效,再用CF_BITMAP代替。不要這樣編寫代碼:

if (::OpenClipboard (m_hWnd)) {
if (::IsClipboardFormatAvailable (nID)) {
// Perfect!
}
else if (::IsClipboardFormatAvailable (CF_TEXT)) {
// Not the best, but I’ll take it.
}
else if (::IsClipboardFormatAvailable (CF_BITMAP)) {
// Better than nothing.
}
::CloseClipboard ();
}

需要這樣編寫代碼:

UINT nFormats[3] = {
nID, // First choice
CF_TEXT, // Second choice
CF_BITMAP // Third choice
};
if (::OpenClipboard (m_hWnd)) {
UINT nFormat = ::GetPriorityClipboardFormat (nFormats, 3);
if (nFormat > 0) {
// nFormat holds nID, CF_TEXT, or CF_BITMAP.
}
::CloseClipboard ();
}

::GetPriorityClipboardFormat的返回值是有效格式列表中第一個格式的ID。如果沒有可用的格式,則返回-1;如果剪貼板爲空,則返回0。

19.1.5 延遲再現

傳統剪貼板有一個侷限性,剪貼板上的所有數據都要保存在內存上。對於文本字符串和其它簡單數據類型,可以快速有效地傳遞。但是,假設複製了一個10MB的位圖到剪貼板。清空剪貼板之前,位圖都要佔用10MB的內存。而如果沒有人粘貼這個位圖,給它分配的內存就毫無用處。

爲了避免這種浪費,Windows支持延遲提交。即直到需要的時候纔將數據複製到剪貼板。它是怎樣工作的?首先,用有效的剪貼板格式和NULL數據句柄調用::SetClipboardData。然後,響應WM_RENDERFORMAT消息,調用::SetClipboardData將數據真正地放入剪貼板。應用調用::GetClipboardData請求獲取指定格式的數據時,就會發送WM_RENDERFORMAT消息。如果沒有人請求數據,就不會傳遞這條消息,就無需分配10MB的內存。要注意,該消息的處理函數不應該調用::OpenClipboard和::CloseClipboard,因爲接收該消息的窗口,收到消息時就佔有了剪貼板。

處理WM_RENDERFORMAT消息的應用還必須處理WM_RENDERALLFORMATS消息。當應用終止而剪貼板擁有應用放置的NULL數據句柄時,就會發送這條消息。該消息處理函數的任務是打開剪貼板、傳遞應用承諾提供的數據、關閉剪貼板。將數據放入剪貼板,保證使用延遲提交的應用終止後,其它應用可以使用這些數據。

第三個剪貼板消息是WM_DESTROYCLIPBOARD,也在延遲提交中使用。這條消息通知應用不需要再提供延遲提交數據。當其它應用調用::EmptyClipboard時,發送該消息。在WM_RENDERALLFORMATS消息之後也發送該消息。如果你擁有響應WM_RENDERFORMAT和WM_RENDERALLFORMATS所需的資源,可以在收到WM_DESTROYCLIPBOARD消息時安全釋放它們。

下面給出一個MFC使用延遲提交複製位圖到剪貼板的方法:
// In CMyWindow’s message map
ON_COMMAND (ID_EDIT_COPY, OnEditCopy)
ON_WM_RENDERFORMAT ()
ON_WM_RENDERALLFORMATS ()

// Elsewhere in CMyWindow
void CMyWindow::OnEditCopy ()
{
::SetClipboardData (CF_BITMAP, NULL);
}
void CMyWindow::OnRenderFormat (UINT nFormat)
{
if (nFormat == CF_BITMAP) {
// Make a copy of the bitmap, and store the handle in hBitmap.

::SetClipboardData (CF_BITMAP, hBitmap);
}
}
void CMyWindow::OnRenderAllFormats ()
{
::OpenClipboard (m_hWnd);
OnRenderFormat (CF_BITMAP);
::CloseClipboard ();
}

這個例子不完全實用,因爲複製位圖到剪貼板和從剪貼板獲取位圖之間,位圖存在改變的可能性,OnEditCopy將被迫複製位圖。但是,想一下,如果OnEditCopy複製了位圖,延遲提交就失去了意義。延遲提交是節省內存的工具,但是,如果應用被迫複製每一項,爲什麼不直接複製?

不必如此,可以保存到硬盤上。下面是修改後的程序:

// In CMyWindow’s message map
ON_COMMAND (ID_EDIT_COPY, OnEditCopy)
ON_WM_RENDERFORMAT ()
ON_WM_RENDERALLFORMATS ()
ON_WM_DESTROYCLIPBOARD ()

// Elsewhere in CMyWindow
void CMyWindow::OnEditCopy ()
{
// Save the bitmap to a temporary disk file.

::SetClipboardData (CF_BITMAP, NULL);
}
void CMyWindow::OnRenderFormat (UINT nFormat)
{
if (nFormat == CF_BITMAP) {
// Re-create the bitmap from the data in the temporary file.

::SetClipboardData (CF_BITMAP, hBitmap);
}
}
void CMyWindow::OnRenderAllFormats ()
{
::OpenClipboard (m_hWnd);
OnRenderFormat (CF_BITMAP);
::CloseClipboard ();
}
void CMyWindow::OnDestroyClipboard ()
{
// Delete the temporary file.
}

發佈了21 篇原創文章 · 獲贊 1 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章