12. 剪貼簿

Post date: 2012/3/23 上午 05:39:33

12. 剪貼簿

Microsoft Windows剪貼簿允許把資料從一個程式傳送到另一個程式中。它的原理相對而言比較簡單,把資料存放到剪貼簿上的程式或從剪貼簿上取出資料的程式都無須太多的負擔。Windows 98和Microsoft Windows NT都提供了剪貼簿瀏覽程式,該程式可以顯示剪貼簿的目前內容。

許多處理檔案或者其他資料的程式都包含一個「Edit」功能表,其中包括「Cut」、「Copy」和「Paste」選項。當使用者選擇「Cut」或者「Copy」時,程式將資料傳送給剪貼簿。這個資料使用某種格式,如文字、點陣圖(一種按位元排列的矩形陣列,其中的位元與平面顯示的圖素相對應)或者metafile(用二進位元數值內容表示的繪圖命令集)等。當使用者從功能表中選擇「Paste」時,程式檢查剪貼簿中包含的資料,看看使用的是否是程式可以接受的一種格式。如果是,那麼資料將從剪貼簿傳送到程式中。

如果使用者不發出明確的指令,程式就不能把資料送入或移出剪貼簿。例如,在某個程式中執行剪下或複製(或者按Ctrl-X及Ctrl-C)操作的使用者,應該能夠假定資料將儲存在剪貼簿上,直到下次剪下或複製操作為止。

回憶一下 第十第十一章 所示的POPPAD程式的修訂版中,我們加上了「Edit」功能表,但是在那邊這功能表的作用只是發送訊息給編輯控制項而已。多數情況下,處理剪貼簿並不方便,您必須自己呼叫剪貼簿傳輸函式。

本章集中討論將文字傳入和移出剪貼簿。在後面的章節裏,我將向您展示如何用剪貼簿處理點陣圖( 第十四十五十六章 )和metafile( 第十八章 )。

剪貼簿的簡單使用

我們由分析把資料傳送到剪貼簿(剪下或複製)和存取剪貼簿資料(粘貼)的程式碼開始。

標準剪貼簿資料格式

Windows支援不同的預先定義剪貼簿格式,這些格式在WINUSER.H定義成以CF為字首的識別字。

首先介紹三種能夠儲存在剪貼簿上的文字資料型態,以及一個與剪貼簿格式相關的資料型態:

  • CF_TEXT 以NULL結尾的ANSI字元集字串。它在每行末尾包含一個carriage return和linefeed字元,這是最簡單的剪貼簿資料格式。傳送到剪貼簿的資料存放在整體記憶體塊中,並且是利用記憶體塊代號進行傳送的(我將簡短地討論此項概念)。這個記憶體塊專供剪貼簿使用,建立它的程式不應該繼續使用它。
  • CF_OEMTEXT 含有文字資料(與CF_TEXT類似)的記憶體塊。但是它使用的是OEM字元集。通常Windows程式不必關心這一點;它只有與在視窗中執行MS-DOS程式一起使用剪貼簿時才會使用。
  • CF_UNICODETEXT 含有Unicode文字的記憶體塊。與CF_TEXT類似,它在每一行的末尾包含一個carriage return和linefeed字元,以及一個NULL字元(兩個0位元組)以表示資料結束。CF_UNICODETEXT只支援Windows NT。
  • CF_LOCALE 一個國家地區識別字的代號。表示剪貼簿文字使用的國別地區設定。

下面是兩種附加的剪貼簿格式,它們在概念上與CF_TEXT格式相似(也就是說,它們都是文字資料),但是它們不需要以NULL結尾,因為格式已經定義了資料的結尾。現在已經很少使用這些格式了:

  • CF_SYLK 包含Microsoft 「符號連結」資料格式的整體記憶體塊。這種格式用在Microsoft的Multiplan、Chart和Excel程式之間交換資料,它是一種ASCII碼格式,每一行都用carriage return和linefeed結尾。
  • CF_DIF 包含資料交換格式(DIF)之資料的整體記憶體塊。這種格式是由Software Arts公司提出的,用於把資料送到VisiCalc試算表程式中。這也是一種ASCII碼格式,每一行都使用carriage return和linefeed結尾。

下面三種剪貼簿格式與點陣圖有關。所謂點陣圖就是資料位元的矩形陣列,其中的資料位元與輸出設備的圖素相對應。 第十四第十五章 將詳細討論點陣圖以及這些點陣圖剪貼簿的格式:

  • CF_BITMAP 與裝置相關的點陣圖格式。點陣圖是通過點陣圖代號傳送給剪貼簿的。同樣,在把這個點陣圖傳送給剪貼簿之後,程式不應該再繼續使用這個點陣圖。
  • CF_DIB 定義一個裝置無關點陣圖(在 第十五章 中描述)的記憶體塊。這種記憶體塊是以點陣圖資訊結構開始的,後面跟著可用的顏色表和點陣圖資料位元。
  • CF_PALETTE 調色盤代號。它通常與CF_DIB配合使用,以定義與裝置相關的點陣圖所使用的顏色調色盤。

在剪貼簿中,還有可能以工業標準的TIFF格式儲存的點陣圖資料:

下面是兩個metafile格式,我將在 第十八章 詳細討論。一個metafile就是一個以二進位格式儲存的畫圖命令集:

最後介紹幾個混合型的剪貼簿格式:

  • CF_TIFF 含有標號圖像檔案格式(TIFF)資料的整體記憶體塊。這種格式由Microsoft、Aldus公司和Hewlett-Packard公司以及一些硬體廠商推薦使用。這一格式可從Hewlett-Packard的網站上獲得。
  • CF_METAFILEPICT 以舊的metafile格式存放的「圖片」。
  • CF_ENHMETAFILE 增強型metafile(32位元Windows支援的)代號。
  • CF_PENDATA 與Windows的筆式輸入擴充功能聯合使用。
  • CF_WAVE 聲音(波形)檔案。
  • CF_RIFF 使用資源交換檔案格式(Resource Interchange File Format)的多媒體資料。
  • CF_HDROP 與拖放服務相關的檔案列表。

記憶體配置

程式向剪貼簿傳輸一些資料的時候,必須配置一個記憶體塊,並且將這塊記憶體交給剪貼簿處理。在本書早期的程式中需要配置記憶體時,我們只需使用標準C執行時期程式庫所支援的malloc函式。但是,由於在Windows中執行的應用程式之間必須要共用剪貼簿所儲存的記憶體塊,這時malloc函式就有些不適任這項任務了。

實際上,我們必須把早期Windows所開發的記憶體配置函式再拿出來使用,那時的作業系統在16位元的實際模式記憶體結構中執行。現在的Windows仍然支援這些函式,您還可以使用它們,但不是必須使用這些函式就是了。

要用Windows API來配置一個記憶體塊,可以呼叫:

此函式有兩個參數:一系列可能的旗標和記憶體塊的位元組大小。函式傳回一個HGLOBAL型態的代號,稱為「整體記憶體塊代號」或「整體代號」。傳回值為NULL表示不能配置足夠的記憶體。

雖然GlobalAlloc的兩個參數略有不同,但它們都是32位元的無正負號整數。如果將第一個參數設定為0,那麼您就可以更有效地使用旗標GMEM_FIXED。在這種情況下,GlobalAlloc傳回的整體代號實際是指向所配置記憶體塊的指標。

如果不喜歡將記憶體塊中的每一位元都初始化為0,那麼您也能夠使用旗標GMEM,_ZEROINIT。在Windows表頭檔案中,簡潔的GPTR旗標定義為GMEM_FIXED和GMEM_ZEROINIT旗標的組合:

下面是一個重新配置函式:

如果記憶體塊擴大了,您可以用GMEM_ZEROINIT旗標將新的位元組設為0。

下面是獲得記憶體塊大小的函式:

釋放記憶體塊的函式:

在早期16位元的Windows中,因為Windows不能在實體記憶體中移動記憶體塊,所以禁止使用GMEM_FIXED旗標。在32位元的Windows中,GMEM_FIXED旗標很常見。這是因為它將傳回一個虛擬位址,並且作業系統也能夠通過改變記憶體頁映射表在實體記憶體中移動記憶體塊。因此為16位元的Windows寫程式時,GlobalAlloc推薦使用GMEM_MOVEABLE旗標。在Windows的表頭檔案中還定義了一個簡寫識別字,用此識別字可以在可移動的記憶體之外填0:

GMEM_MOVEABLE旗標允許Windows在虛擬記憶體中移動一個記憶體塊。這不是說將在實體記憶體中移動記憶體塊,只是應用程式用於讀寫這塊記憶體的位址可以被變動。

儘管GMEM_MOVEABLE是16位元Windows的通則,但是它的作用現在已經少得多了。如果您的應用程式頻繁地配置、重新配置以及釋放不同大小的記憶體塊,應用程式的虛擬位址空間將會變得支離破碎。可以想像得到,最後虛擬記憶體位址空間就會被用完。如果這是個可能會發生的問題,那麼您將希望記憶體是可移動的。下面就介紹如何讓記憶體塊成為可搬移位置的。

首先定義一個指標(例如,一個int型態的)和一個GLOBALHANDLE型態的變數:

然後配置記憶體。例如:

與處理其他Windows代號一樣,您不必擔心數字的實際意義,只要照著作就好了。需要存取記憶體塊時,可以呼叫:

此函式將代號轉換為指標。在記憶體塊被鎖定期間,Windows將固定虛擬記憶體中的位址,不再移動那塊記憶體。存取結束後呼叫:

這將使Windows可以在虛擬記憶體中移動記憶體塊。要真正確保此程序正常運作(體驗早期Windows程式寫作者的痛苦經歷),您應該在單一個訊息處理期間鎖定和解鎖記憶體塊。

在釋放記憶體時,呼叫GlobalFree應使用代號而不是指標。如果您現在不能存取代號,可以使用下面的函式:

在解鎖之前,您能夠多次鎖定一個記憶體塊。Windows保留一個鎖定次數,而且在記憶體塊可被自由移動之前,每次鎖定都需要相對應的解鎖。當Windows在虛擬記憶體中移動一個記憶體塊時,不需要將位元組從一個位置複製到另一個,只需巧妙地處理記憶體頁映射表。通常,讓32位元Windows為您的程式配置可移動的記憶體塊,其唯一確實的理由只是避免虛擬記憶體的空間碎裂出現。使用剪貼簿時,也應該使用可移動記憶體。

為剪貼簿配置記憶體時,您應該以GMEM_MOVEABLE和GMEM_SHARE旗標呼叫GlobalAlloc函式。GMEM_SHARE旗標使得其他應用程式也可以使用那塊記憶體。

將文字傳送到剪貼簿

讓我們想像把一個ANSI字串傳送到剪貼簿上,並且我們已經有了指向這個字串的指標(pString)。現在希望傳送這個字串的iLength字元,這些字元可能以NULL結尾,也可能不以NULL結尾。

首先,通過使用GlobalAlloc來配置一個足以儲存字串的記憶體塊,其中還包括一個終止字元NULL:

如果未能配置到記憶體塊,hGlobal的值將為NULL 。如果配置成功,則鎖定這塊記憶體,並得到指向它的一個指標:

將字串複製到記憶體塊中:

由於GlobalAlloc的GHND旗標已使整個記憶體塊在配置期間被清除為零,所以不需要增加結尾的NULL 。以下敘述為記憶體塊解鎖:

現在就有了表示以NULL結尾的文字所在記憶體塊的記憶體代號。為了把它送到剪貼簿中,打開剪貼簿並把它清空:

利用CF_TEXT識別字把記憶體代號交給剪貼簿,關閉剪貼簿:

工作告一段落。

下面是關於此過程的一些規則:

hGlobal = GlobalAlloc (uiFlags, dwSize) ;

#define GPTR (GMEM_FIXED | GMEM_ZEROINIT)

hGlobal = GlobalReAlloc (hGlobal, dwSize, uiFlags) ;

dwSize = GlobalSize (hGlobal) ;

GlobalFree (hGlobal) ;

#define GHND (GMEM_MOVEABLE | GMEM_ZEROINIT)

int * p ; GLOBALHANDLE hGlobal ;

hGlobal = GlobalAlloc (GHND, 1024) ;

p = (int *) GlobalLock (hGlobal) ;

GlobalUnlock (hGlobal) ;

hGlobal = GlobalHandle (p) ;

hGlobal = GlobalAlloc (GHND | GMEM_SHARE, iLength + 1) ;

pGlobal = GlobalLock (hGlobal) ;

for (i = 0 ; i < wLength ; i++) *pGlobal++ = *pString++ ;

GlobalUnlock (hGlobal) ;

OpenClipboard (hwnd) ; EmptyClipboard () ;

SetClipboardData (CF_TEXT, hGlobal) ; CloseClipboard () ;

  • 在處理同一個訊息的過程中呼叫OpenClipboard和CloseClipboard。不需要時,不要打開剪貼簿。
  • 不要把鎖定的記憶體代號交給剪貼簿。
  • 當呼叫SetClipboardData後,請不要再繼續使用該記憶體塊。它不再屬於使用者程式,必須把代號看成是無效的。如果需要繼續存取資料,可以製作資料的副本,或從剪貼簿中讀取它(如下節所述)。您也可以在SetClipboardData呼叫和CloseClipboard呼叫之間繼續使用記憶體塊,但是不要使用傳遞給SetClipboardData函式的整體代號。事實上,此函式也傳回一個整體代號,必需鎖定這些代碼以存取記憶體。在呼叫CloseClipboard之前,應先為此代號解鎖。

從剪貼簿上取得文字

從剪貼簿上取得文字只比把文字傳送到剪貼簿上稍微複雜一些。您必須首先確定剪貼簿是否含有CF_TEXT格式的資料,最簡單的方法是呼叫

如果剪貼簿上含有CF_TEXT資料,這個函式將傳回TRUE(非零)。我們在第十章的POPPAD2程式中已使用了這個函式,用它來確定「Edit」功能表中「Paste」項是被啟用還是被停用的。IsClipboardFormatAvailable是少數幾個不需先打開剪貼簿就可以使用的剪貼簿函式之一。但是,如果您之後想再打開剪貼簿以取得這個文字,就應該再做一次檢查(使用同樣的函式或其他方法),以便確定CF_TEXT資料是否仍然留在剪貼簿中。

為了傳送出文字,首先打開剪貼簿:

會得到代表文字的記憶體塊代號:

如果剪貼簿不包含CF_TEXT格式的資料,此代號就為NULL。這是確定剪貼簿是否含有文字的另一種方法。如果GetClipboardData傳回NULL,則關閉剪貼簿,不做其他任何工作。

從GetClipboardData得到的代號並不屬於使用者程式-它屬於剪貼簿。僅在GetClipboardData和CloseClipboard呼叫之間這個代號才有效。您不能釋放這個代號或更改它所引用的資料。如果需要繼續存取這些資料,必須製作這個記憶體塊的副本。

這裏有一種將資料複製到使用者程式中的方法。首先,配置一塊與剪貼簿資料塊大小相同的記憶體塊,並配置一個指向該塊的指標:

再次呼叫hGlobal ,而hGlobal是從GetClipboardData呼叫傳回的整體代號。現在鎖定代號,獲得一個指向剪貼簿塊的指標:

現在就可以複製資料了:

或者,您可以使用一些簡單的C程式碼:

在關閉剪貼簿之前先解鎖記憶體塊:

現在您有了一個叫做pText的指標,以後程式的使用者就可以用它來複製文字了。

打開和關閉剪貼簿

在任何時候,只有一個程式可以打開剪貼簿。呼叫OpenClipboard的作用是當一個程式使用剪貼簿時,防止剪貼簿的內容發生變化。OpenClipboard傳回BOOL值,它說明是否已經成功地打開了剪貼簿。如果另一個應用程式沒有關閉剪貼簿,那麼它就不能被打開。如果每個程式在回應使用者的命令時都儘快地、遵守規範地打開然後關閉剪貼簿,那麼您將永遠不會遇到不能打開剪貼簿的問題。

但是,在不遵守規範程式和優先權式多工環境中,總會發生一些問題。即使在您的程式將某些東西放入剪貼簿和使用者啟動一個「Paste」選項期間,您的程式並沒有失去輸入焦點,但是您也不能假定您放入的東西仍然在那裏,一個背景程式有可能已經在這段期間存取過剪貼簿了。

而且,請留意一個與訊息方塊有關的更微妙問題:如果不能配置足夠的記憶體來將內容複製到剪貼簿,那麼您可能希望顯示一個訊息方塊。但是,如果這個訊息方塊不是系統模態的,那麼使用者可以在顯示訊息方塊期間切換到另一個應用程式中。您應該使用系統模態的訊息方塊,或者在您顯示訊息方塊之前關閉剪貼簿。

如果您在顯示一個對話方塊時將剪貼簿保持為打開狀態,那麼您還可能遇到其他問題,對話方塊中的編輯欄位會使用剪貼簿進行文字的剪貼。

剪貼簿和Unicode

迄今為止,我只討論了用剪貼簿處理ANSI文字(每個字元對應一個位元組)。我們用CF_TEXT識別字時就是這種格式。您可能對CF_OEMTEXT和CF_UNICODETEXT還不熟悉吧。

我有一些好消息:在處理您所想要的文字格式時,您只需呼叫SetClipboardData和GetClipboardData,Windows將處理剪貼簿中所有的文字轉換。例如,在Windows NT中,如果一個程式用SetClipboardData來處理CF_TEXT剪貼簿資料型態,程式也能用CF_OEMTEXT呼叫GetClipboardData。同樣地,剪貼簿也能將CF_OEMTEXT資料轉換為CF_TEXT。

在Windows NT中,轉換發生在CF_UNICODETEXT、CF_TEXT和CF_OEMTEXT之間。程式應該使用對程式本身而言最方便的一種文字格式來呼叫SetClipboardData 。同樣地,程式應該用程式需要的文字格式來呼叫GetClipboardData。我們已經知道,本書附上的程式在編寫時可以帶有或不帶UNICODE識別字。如果您的程式也依此編寫,那麼在定義了UNICODE識別字之後,程式將執行帶有CF_UNICODETEXT參數的SetClipboardData以及GetClipboardData呼叫,而不是CF_TEXT。

CLIPTEXT程式,如程式12-1所示,展示了一種可行的方法。

bAvailable = IsClipboardFormatAvailable (CF_TEXT) ;

OpenClipboard (hwnd) ;

hGlobal = GetClipboardData (CF_TEXT) ;

pText = (char *) malloc (GlobalSize (hGlobal)) ;

pGlobal = GlobalLock (hGlobal) ;

strcpy (pText, pGlobal) ;

while (*pText++ = *pGlobal++) ;

GlobalUnlock (hGlobal) ; CloseClipboard () ;

程式12-1 CLIPTEXT CLIPTEXT.C /*------------------------------------------------------------------------- CLIPTEXT.C -- The Clipboard and Text (c) Charles Petzold, 1998 --------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; #ifdef UNICODE #define CF_TCHAR CF_UNICODETEXT TCHAR szDefaultText[] = TEXT ("Default Text - Unicode Version") ; TCHAR szCaption[] = TEXT ("Clipboard Text Transfers - Unicode Version") ; #else #define CF_TCHAR CF_TEXT TCHAR szDefaultText[] = TEXT ("Default Text - ANSI Version") ; TCHAR szCaption[] = TEXT ("Clipboard Text Transfers - ANSI Version") ; #endif int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("ClipText") ; HACCEL hAccel ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, szCaption, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; hAccel = LoadAccelerators (hInstance, szAppName) ; while (GetMessage (&msg, NULL, 0, 0)) { if (!TranslateAccelerator (hwnd, hAccel, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } } return msg.wParam ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static PTSTR pText ; BOOL bEnable ; HGLOBAL hGlobal ; HDC hdc ; PTSTR pGlobal ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_CREATE: SendMessage (hwnd, WM_COMMAND, IDM_EDIT_RESET, 0) ; return 0 ; case WM_INITMENUPOPUP: EnableMenuItem ((HMENU) wParam, IDM_EDIT_PASTE, IsClipboardFormatAvailable (CF_TCHAR) ? MF_ENABLED : MF_GRAYED) ; bEnable = pText ? MF_ENABLED : MF_GRAYED ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_CUT, bEnable) ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_COPY, bEnable) ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_CLEAR, bEnable) ; break ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_EDIT_PASTE: OpenClipboard (hwnd) ; if (hGlobal = GetClipboardData (CF_TCHAR)) { pGlobal = GlobalLock (hGlobal) ; if (pText) { free (pText) ; pText = NULL ; } pText = malloc (GlobalSize (hGlobal)) ; lstrcpy (pText, pGlobal) ; InvalidateRect (hwnd, NULL, TRUE) ; } CloseClipboard () ; return 0 ; case IDM_EDIT_CUT: case IDM_EDIT_COPY: if (!pText) return 0 ; hGlobal = GlobalAlloc (GHND | GMEM_SHARE, (lstrlen (pText) + 1) * sizeof (TCHAR)) ; pGlobal = GlobalLock (hGlobal) ; lstrcpy (pGlobal, pText) ; GlobalUnlock (hGlobal) ; OpenClipboard (hwnd) ; EmptyClipboard () ; SetClipboardData (CF_TCHAR, hGlobal) ; CloseClipboard () ; if ( LOWORD (wParam) == IDM_EDIT_COPY) return 0 ; // fall through for IDM_EDIT_CUT case IDM_EDIT_CLEAR: if (pText) { free (pText) ; pText = NULL ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case IDM_EDIT_RESET: if (pText) { free (pText) ; pText = NULL ; } pText = malloc ((lstrlen (szDefaultText) + 1) * sizeof (TCHAR)) ; lstrcpy (pText, szDefaultText) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; if (pText != NULL) DrawText (hdc, pText, -1, &rect, DT_EXPANDTABS | DT_WORDBREAK) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if ( pText) free (pText) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

CLIPTEXT.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu CLIPTEXT MENU DISCARDABLE BEGIN POPUP "&Edit" BEGIN MENUITEM "Cu&t\tCtrl+X", IDM_EDIT_CUT MENUITEM "&Copy\tCtrl+C", IDM_EDIT_COPY MENUITEM "&Paste\tCtrl+V", IDM_EDIT_PASTE MENUITEM "De&lete\tDel", IDM_EDIT_CLEAR MENUITEM SEPARATOR MENUITEM "&Reset", IDM_EDIT_RESET END END ///////////////////////////////////////////////////////////////////////////// // Accelerator CLIPTEXT ACCELERATORS DISCARDABLE BEGIN "C", IDM_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT "V", IDM_EDIT_PASTE, VIRTKEY, CONTROL, NOINVERT VK_DELETE, IDM_EDIT_CLEAR, VIRTKEY, NOINVERT "X", IDM_EDIT_CUT, VIRTKEY, CONTROL, NOINVERT END

RESOURCE.H(摘錄) // Microsoft Developer Studio generated include file. // Used by ClipText.rc #define IDM_EDIT_CUT 40001 #define IDM_EDIT_COPY 40002 #define IDM_EDIT_PASTE 40003 #define IDM_EDIT_CLEAR 40004 #define IDM_EDIT_RESET 40005

這是在Windows NT下執行Unicode版和ANSI版程式的概念,而且可以看到,剪貼簿是如何在兩種字元集之間轉換的。注意CLIPTEXT.C頂部的#ifdef敘述。如果定義了UNICODE識別字,那麼CF_TCHAR(我命名的一種常用的剪貼簿格式)就等於CF_UNICODETEXT;否則,它就等於CF_TEXT。程式後面呼叫的IsClipboardFormatAvailable、GetClipboardData和SetClipboardData函式都使用CF_TCHAR來指定資料型態。

在程式的開始部分(以及您從「Edit」功能表中選擇「Reset」選項時),靜態變數pText包含一個指標,在Unicode版的程式中,指標指向Unicode字串「Default Text -Unicode version」;在非Unicode版的程式中,指標指向「Default Text - ANSI version」。您可以用「Cut」或「Copy」命令將字串傳遞給剪貼簿,用「Cut」或「Delete」命令從程式中刪除字串。「Paste」命令將剪貼簿中的文字內容複製到pText。在WM_PAINT訊息處理期間,pText將字串顯示在程式的顯示區域。

如果您先在Unicode版的CLIPTEXT中選擇了「Copy」命令,然後在非Unicode版中選擇「Paste」命令,那麼您就能看到文字已經從Unicode轉換成了ANSI。類似地,如果您執行相反的操作,那麼文字就會從ANSI轉換成Unicode。

複雜的剪貼簿用法

我們已經看到,在將資料準備好之後,從剪貼簿傳輸資料時需要四個呼叫:

OpenClipboard (hwnd) ; EmptyClipboard () ; SetClipboardData (iFormat, hGlobal) ; CloseClipboard () ;

存取這些資料需要三個呼叫

OpenClipboard (hwnd) ; hGlobal = GetClipboardData (iFormat) ; 其他行程式 CloseClipboard () ;

在GetClipboardData和CloseClipboard呼叫之間,可以複製剪貼簿資料或以其他方式來使用它。很多應用程式都需要採用這種方法,但也可以用更複雜的方式來使用剪貼簿。

利用多個資料項目

當打開剪貼簿並把資料傳送給它時,必須先呼叫EmptyClipboard,通知Windows釋放或刪除剪貼簿上的內容。不能在現有的剪貼簿內容中附加其他東西。所以,從這種意義上說,剪貼簿每次只能保留一個資料項目。

但是,可以在EmptyClipboard和CloseClipboard呼叫之間多次呼叫SetClipboardData,每次都使用不同的剪貼簿格式。例如,如果想在剪貼簿中儲存一個很短的文字字串,可以把這個文字寫入metafile,也可以把這個文字寫入點陣圖。把點陣圖選進記憶體裝置內容中,並把這個字串寫進點陣圖中。利用這種方法可以使字串不僅能為從剪貼簿上讀取文字的程式所使用,也可以為從剪貼簿上讀取點陣圖和metafile的程式所使用。當然,這些程式並不能知道metafile或點陣圖實際上包含了一個字串。

如果想把一些代號寫到剪貼簿上,對每個代號均可以呼叫 SetClipboardData:

OpenClipboard (hwnd) ; EmptyClipboard () ; SetClipboardData (CF_TEXT, hGlobalText) ; SetClipboardData (CF_BITMAP, hBitmap) ; SetClipboardData (CF_METAFILEPICT, hGlobalMFP) ; CloseClipboard () ;

當這三種格式的資料同時位於剪貼簿上時,用CF_TEXT、CF_BITMAP或CF_METAFILEPICT參數呼叫IsClipboardFormatAvailable將傳回TRUE。通過下列呼叫程式可以存取這些代碼:

下一次程式呼叫EmptyClipboard時,Windows將釋放或刪除剪貼簿上保留的所有三個代號。

在將不同的文字格式、不同的點陣圖格式或者不同的metafile格式添加到剪貼簿時,不要使用這種技術。只使用一種文字格式、一種點陣圖格式以及一種metafile格式。就像我所說的那樣,Windows將在CF_TEXT、CF_OEMTEXT和CF_UNICODETEXT之間轉換,也可以在CF_BITMAP和CF_DIB之間,以及在CF_METAFILEPICT和CF_ENHMETAFILE之間進行轉換。

透過首先打開剪貼簿,然後呼叫EnumClipboardFormats,程式可以確定剪貼簿儲存的所有格式。開始時設定變數iFormat為0:

現在從0值開始逐次進行連續的EnumClipboardFormats呼叫。函式將為目前在剪貼簿中的每種格式傳回一個正的iFormat值。當函式傳回0時,表示完成:

hGlobalText = GetClipboardData (CF_TEXT) ;

hBitmap = GetClipboardData (CF_BITMAP) ;

hGlobalMFP = GetClipboardData (CF_METAFILEPICT) ;

iFormat = 0 ; OpenClipboard (hwnd) ;

while (iFormat = EnumClipboardFormats (iFormat)) { 各個iFormat值的處理方式 } CloseClipboard () ;

您可以通過下面的呼叫來取得目前在剪貼簿中之不同格式的個數:

延遲提出

當把資料放入剪貼簿中時,一般來說要製作一份資料的副本,並將包含這份副本的記憶體塊代號傳給剪貼簿。對非常大的資料項目來說,這種方法會浪費記憶體空間。如果使用者不想把資料粘貼到另一個程式裏,那麼,在被其他內容取代之前,它將一直佔據著記憶體空間。

通過使用一種叫做「延遲提出」的技術可以避免這個問題。實際上,直到另一個程式需要資料,程式才提供這份資料。為此,不將資料代號傳給Windows,而是在SetClipboardData呼叫中使用NULL:

iCount = CountClipboardFormats () ;

OpenClipboard (hwnd) ; EmptyClipboard () ; SetClipboardData (iFormat, NULL) ; CloseClipboard () ;

可以有多個使用不同iFormat值的SetClipboardData呼叫,對其中某些呼叫可使用NULL值。而對其他一些則使用實際的代號值。

前面的過程比較簡單,以下的過程就要稍微複雜一些了。當另一個程式呼叫GetClipboardData時,Windows將檢查那種格式的代號是否為NULL。如果是,Windows將給「剪貼簿所有者」(您的程式)發送一個訊息,要求取得資料的實際代號,這時您的程式必須提供這個代號。

更具體地說,「剪貼簿所有者」是將資料放入剪貼簿的最後一個視窗。當一個程式呼叫OpenClipboard時,Windows儲存呼叫這個函式時所用的視窗代號,這個代號標示打開剪貼簿的視窗。一旦收到一個EmptyClipboard呼叫,Windows就使這個視窗作為新的剪貼簿所有者。

使用延遲提出技術的程式在它的視窗訊息處理程式中必須處理三個訊息:WM_RENDERFORMAT、WM_RENDERALLFORMATS和WM_DESTROYCLIPBOARD。當另一個程式呼叫GetClipboardData時,Windows給視窗訊息處理程式發送一個WM_RENDERFORMAT訊息,wParam的值是所要求的格式。在處理WM_RENDERFORMAT訊息時,不要打開或清空剪貼簿。為wParam所指定的格式建立一個整體記憶體塊,把數據傳給它,並用正確的格式和相應代號呼叫SetClipboardData。很明顯地,為了在處理WM_RENDERFORMAT時正確地構造出此資料,需要在程式中保留這些資訊。當另一個程式呼叫EmptyClipboard時,Windows給您的程式發送一個WM_DESTROYCLIPBOARD訊息,告訴您不再需要構造剪貼簿資料的資訊。您的程式不再是剪貼簿的所有者。

如果程式在它自己仍然是剪貼簿所有者的時候就要終止執行,並且剪貼簿上仍然包含著該程式用SetClipboardData設定的NULL資料代號,它將收到WM_RENDERALLFORMATS訊息。這時,應該打開剪貼簿,清空它,把資料載入記憶體塊中,並為每種格式呼叫SetClipboardData,然後關閉剪貼簿。WM_RENDERALLFORMATS訊息是視窗訊息處理程式最後收到的訊息之一。它後面跟有WM_DESTROYCLIPBOARD訊息(由於已經提出了所有資料),然後是正常的WM_DESTROY訊息。

如果您的程式只能向剪貼簿傳輸一種格式的資料(例如文字),那麼您可以把WM_RENDERALLFORMATS和WM_RENDERFORMAT處理結合在一起。這些程式碼應該類似下面這樣:

case WM_RENDERALLFORMATS : OpenClipboard (hwnd) ; EmptyClipboard () ; // fall through case WM_RENDERFORMAT : // 將文字放入整體記憶體塊 SetClipboardData (CF_TEXT, hGlobal) ; if (message == WM_RENDERALLFORMATS) CloseClipboard () ; return 0 ;

如果您的程式使用好幾種剪貼簿格式,那麼您可能想為wParam所要求的格式處理WM_ RENDERFORMAT。除非程式在存放構造資料所需的資訊時遇到困難,否則不需要處理WM_DESTROYCLIPBOARD訊息。

自訂資料格式

到目前為止,我們僅處理了Windows定義的標準剪貼簿資料格式。但是,您可能想用剪貼簿來儲存「自訂資料格式」。許多文書處理程式使用這種技術來儲存包含著字體和格式化資訊的文字。

初看之下,這個概念似乎是沒有意義的。如果剪貼簿的作用是在應用程式之間傳送資料,那麼,為什麼剪貼簿中要含有只有一個應用程式才能理解的資料呢?答案很簡單:剪貼簿允許在同一個程式的內部(或者可能在一個程式中的不同執行實體之間)傳送資料。很明顯地,這些執行實體能理解它們自己的自訂資料格式。

有幾種使用自訂資料格式的方法。最簡單的方法用到一種表面上是標準剪貼簿格式(文字、點陣圖或metafile)的資料,可是該資料實際上只對您的程式有意義。這種情況下,在SetClipboardData和GetClipboardData呼叫中可使用下列wFormat值:CF_DSPTEXT、CF_DSPBITMAP、CF_DSPMETAFILEPICT或CF_DSPENHMETAFILE(字母DSP代表「顯示器」)。這些格式允許Windows按文字、點陣圖或metafile來瀏覽或顯示資料。但是,另一個使用常規的CF_TEXT、CF_BITMAP、CF_DIB、CF_METAFILEPICT或CF_ENHMETAFILE格式呼叫GetClipboardData的程式將不能取得這個資料。

如果用其中一種格式把資料放入剪貼簿中,則必須使用同樣的格式讀出資料。但是,如何知道資料是來自程式的另一個執行實體,還是來自使用其中某種資料格式的另一個程式呢?這裏有一種方法,可以透過下列呼叫首先獲得剪貼簿所有者:

然後可以得到此視窗代號的視窗類別名稱:

hwndClipOwner = GetClipboardOwner () ;

TCHAR szClassName [32] ; //其他行程式 GetClassName (hwndClipOwner, szClassName, 32) ;

如果類別名稱與程式名稱相同,那麼資料是由程式的另一個執行實體傳送到剪貼簿中的。

使用自訂資料格式的第二種方法涉及到CF_OWNERDISPLAY旗標。SetClipboardData的整體記憶體代號是NULL:

這是某些文書處理程式在Windows的剪貼簿瀏覽器的顯示區域中顯示格式化文字時所採用的方法。很明顯地,剪貼簿瀏覽器不知道如何顯示這種格式化文字。當一個文書處理程式指定CF_OWNERDISPLAY格式時,它也就承擔起在剪貼簿瀏覽器的顯示區域中繪圖的責任。

由於整體記憶體代號為NULL,所以用CF_OWNERDISPLAY格式(剪貼簿所有者)呼叫SetClipboardData的程式必須處理由Windows發往剪貼簿所有者的延遲提出訊息、以及5條附加訊息。這5個訊息是由剪貼簿瀏覽器發送到剪貼簿所有者的:

SetClipboardData (CF_OWNERDISPLAY, NULL) ;

  • WM_ASKCBFORMATNAME 剪貼簿瀏覽器把這個訊息發送到剪貼簿所有者,以得到資料格式名稱。lParam參數是指向緩衝區的指標,wParam是這個緩衝區能容納的最大字元數目。剪貼簿所有者必須把剪貼簿資料格式的名字複製到這個緩衝區中。
  • WM_SIZECLIPBOARD 這個訊息通知剪貼簿所有者,剪貼簿瀏覽器的顯示區域大小己發生了變化。wParam參數是剪貼簿瀏覽器的代號,lParam是指向包含新尺寸的RECT結構的指標。如果RECT結構中都是0,則剪貼簿瀏覽器退出或最小化。儘管Windows的剪貼簿瀏覽器只允許它自己的一個執行實體執行,但其他剪貼簿瀏覽器也能把這個訊息發送給剪貼簿所有者。應付多個剪貼簿瀏覽器並非不可能(假定wParam標識特定的瀏覽器),但剪貼簿所有者處理起來也不容易。
  • WM_PAINTCLIPBOARD 這個訊息通知剪貼簿所有者修改剪貼簿瀏覽器的顯示區域。同時,wParam是剪貼簿瀏覽器視窗的代號,lParam是指向PAINTSTRUCT結構的整體指標。剪貼簿所有者可以從此結構的hdc欄中得到剪貼簿瀏覽器裝置內容的代號。
  • WM_HSCROLLCLIPBOARD和WM_VSCROLLCLIPBOARD 這兩個訊息通知剪貼簿所有者,使用者已經捲動了剪貼簿瀏覽器的捲動列。wParam參數是剪貼簿瀏覽器視窗的代號,lParam的低字組是捲動請求,並且,如果低字組是SB_THUMBPOSITION,那麼lParam的高字組就是滑塊位置。

處理這些訊息比較麻煩,看來並不值得這樣做。但是,這種處理對使用者來說是有益的。當從文書處理程式把文字複製到剪貼簿時,使用者在剪貼簿瀏覽器的顯示區域中看見文字還保持著格式時心裏會舒坦些。

使用私有剪貼簿資料格式的第三種方法是註冊自己的剪貼簿格式名。您向Windows提供格式名,Windows給程式提供一個序號,它可以用作SetClipboardData和GetClipboardData的格式參數。一般來說,採用這種方法的程式也要以一種標準格式把資料複製到剪貼簿。這種方法允許剪貼簿瀏覽器在它的顯示區域中顯示資料(沒有與CF_OWNERDISPLAY相關的衝突),並且允許其他程式從剪貼簿上複製資料。

例如,假定我們已經編寫了一個以點陣圖格式、metafile格式和自己的已註冊的剪貼簿格式把資料複製到剪貼簿中的向量繪圖程式。剪貼簿瀏覽器將顯示metafile或者點陣圖,其他從剪貼簿上讀取點陣圖和metafile的程式將獲得這幾種格式。但是,當我們的向量繪圖程式需要從剪貼簿上讀數據時,它會按照自己已註冊的格式複製資料,這是因為這種格式可能包含著比點陣圖檔案或者metafile更多的資訊。

程式透過下面的呼叫來註冊一個新的剪貼簿格式:

iFormat的值介於0xC000和0xFFFF之間。剪貼簿瀏覽器(或一個通過呼叫EnumClipboardFormats取得目前所有剪貼簿資料格式的程式)可以取得這種資料格式的ASCII名稱,這是通過下面呼叫實作的:

Windows將多達iMaxCount個字元複製到psBuffer中。

使用這種方法把資料複製到剪貼簿中的程式寫作者,可能需要公開資料格式名稱和實際的資料格式。如果這個程式流行起來,那麼其他程式就會以這種格式從剪貼簿中複製資料。

實作剪貼簿瀏覽器

監視剪貼簿內容變化的程式稱為「剪貼簿瀏覽器」。您可以在Windows中得到一個剪貼簿瀏覽器,但是您也可以編寫自己的剪貼簿瀏覽器程式。剪貼簿瀏覽器通過傳遞到瀏覽器視窗訊息處理程式的訊息來監視剪貼簿內容的變化。

剪貼簿瀏覽器鏈

任意數量的剪貼簿瀏覽器應用程式都可以同時在Windows下執行,它們都可以監視剪貼簿內容的變化。但是,從Windows的角度來看,只存在一個剪貼簿瀏覽器,我們稱之為「目前剪貼簿瀏覽器」。Windows只保留一個識別目前剪貼簿瀏覽器的視窗代號,並且當剪貼簿的內容發生變化時只把訊息發送到那個視窗中。

剪貼簿瀏覽器應用程式有必要加入「剪貼簿瀏覽器鏈」,以便執行的所有剪貼簿瀏覽器都可以收到Windows發送給目前剪貼簿瀏覽器的訊息。當一個程式將自己註冊為一個剪貼簿瀏覽器時,它就成為目前的剪貼簿瀏覽器。Windows把先前的目前瀏覽器視窗代號交給這個程式,並且此程式將儲存這個代號。當此程式收到一個剪貼簿瀏覽器訊息時,它把這個訊息發送給剪貼簿鏈中下一個程式的視窗訊息處理程式。

剪貼簿瀏覽器的函式和訊息

程式透過呼叫SetClipboardViewer函式可以成為剪貼簿瀏覽器鏈的一部分。如果程式的主要作用是作為剪貼簿瀏覽器,那麼這個程式在WM_CREATE訊息處理期間可以呼叫這個函式,該函式傳回前一個目前剪貼簿瀏覽器的視窗代號。程式應該把這個代號儲存在靜態變數中:

iFormat = RegisterClipboardFormat (szFormatName) ;

GetClipboardFormatName (iFormat, psBuffer, iMaxCount) ;

static HWND hwndNextViewer ; //其他行程式 case WM_CREATE : //其他行程式 hwndNextViewer = SetClipboardViewer (hwnd) ;

如果在Windows的一次執行期間,您的程式成為剪貼簿瀏覽器的第一個程式,那麼hwndNextViewer將為NULL。

不管剪貼簿中的內容怎樣變化,Windows都將把WM_DRAWCLIPBOARD訊息發送給目前的剪貼簿瀏覽器(最近註冊為剪貼簿瀏覽器的視窗)。剪貼簿瀏覽器鏈中的每個程式都應該用SendMessage把這個訊息發送到下一個剪貼簿瀏覽器。瀏覽器鏈中的最後一個程式(第一個將自己註冊為剪貼簿瀏覽器的視窗)所儲存的hwndNextViewer為NULL。如果hwndNextViewer為NULL,那麼程式只簡單地將控制項權還給系統而已,而不向其他程式發送任何訊息(不要把WM_DRAWCLIPBOARD訊息和WM_PAINTCLIPBOARD訊息混淆了。WM_PAINTCLIPBOARD是由剪貼簿瀏覽器發送給使用CF_OWNERDISPLAY剪貼簿資料格式的程式,而WM_ DRAWCLIPBOARD訊息是由Windows發往目前剪貼簿瀏覽器的)。

處理WM_DRAWCLIPBOARD訊息的最簡單方法是將訊息發送給下一個剪貼簿瀏覽器(除非hwndNextViewer為NULL),並使視窗的顯示區域無效:

case WM_DRAWCLIPBOARD : if ( hwndNextViewer) SendMessage (hwndNextViewer, message, wParam, lParam) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;

在處理WM_PAINT訊息處理期間,通過使用常規的OpenClipboard、GetClipboardData和CloseClipboard呼叫可以讀取剪貼簿的內容。

當某個程式想從剪貼簿瀏覽器鏈中刪除它自己時,它必須呼叫ChangeClipboardChain。這個函式接收脫離瀏覽器鏈的程式之視窗代號,和下一個剪貼簿瀏覽器的視窗代號:

當程式呼叫ChangeClipboardChain時,Windows發送WM_CHANGECBCHAIN訊息給目前的剪貼簿瀏覽器。wParam參數是從鏈中移除它自己的那個瀏覽器視窗代號(ChangeClipboardChain的第一個參數),lParam是從鏈中移除自己後的下一個剪貼簿瀏覽器的視窗代號(ChangeClipboardChain的第二個參數)。

當程式接收到WM_CHANGECBCHAIN訊息時,必須檢查wParam是否等於已經儲存的hwndNextViewer的值。如果是這樣,程式必須設定hwndNextViewer為lParam。這項工作保證將來的WM_DRAWCLIPBOARD訊息不會發送給從剪貼簿瀏覽器鏈中刪除了自己的視窗。如果wParam不等於hwndNextViewer ,並且hwndNextViewer不為NULL,則把訊息送到下一個剪貼簿瀏覽器。

ChangeClipboardChain (hwnd, hwndNextViewer) ;

case WM_CHANGECBCHAIN : if ((HWND) wParam == hwndNextViewer) hwndNextViewer = (HWND) lParam ; else if (hwndNextViewer) SendMessage (hwndNextViewer, message, wParam, lParam) ; return 0 ;

不一定要使用else if敘述,它只用於保證hwndNextViewer為非NULL的值。hwndNextViewer的值為NULL時,執行這段程式碼的程式就是鏈中最後一個瀏覽器,而這是不可能的。

當程式快結束時,如果它仍然在剪貼簿瀏覽器鏈中,則必須從鏈中刪除它。您可以在處理WM_DESTROY訊息時呼叫ChangeClipboardChain來完成這項工作。

case WM_DESTROY : ChangeClipboardChain (hwnd, hwndNextViewer) ; PostQuitMessage (0) ; return 0 ;

Windows還有一個允許程式獲得第一個剪貼簿瀏覽器視窗代號的函式:

一般來說不需要這個函式。如果沒有目前的剪貼簿瀏覽器,則傳回值為NULL。

下面是一個說明剪貼簿瀏覽器鏈如何工作的例子。當Windows剛啟動時,目前剪貼簿瀏覽器是NULL:

剪貼簿瀏覽器: NULL

一個具有hwnd1視窗代號的程式呼叫SetClipboardViewer。這個函式傳回的NULL成為這個程式中的hwndNextViewer值:

目前剪貼簿瀏覽器: hwnd1

hwnd1的下一個瀏覽器: NULL

第二個具有hwnd2視窗代號的程式呼叫SetClipboardViewer ,並傳回hwnd1:

目前的剪貼簿瀏覽器: hwnd2

hwnd2的下一個瀏覽器: hwnd1

hwnd1的下一個瀏覽器: NULL

每三個程式 (hwnd3)和第四個程式 (hwnd4) 也呼叫SetClipboardViewer ,並且傳回hwnd2和hwnd3:

目前的剪貼簿瀏覽器: hwnd4

hwnd4的下一個瀏覽器: hwnd3

hwnd3的下一個瀏覽器: hwnd2

hwnd2的下一個瀏覽器: hwnd1

hwnd1的下一個瀏覽器: NULL

當剪貼簿的內容發生變化時, Windows發送一個WM_DRAWCLIPBOARD訊息給hwnd4, hwnd4發送訊息給hwnd3,hwnd3發送訊息給hwnd2,hwnd2發送訊息給hwnd1,hwnd1傳回。

現在hwnd2決定通過下列呼叫從鏈中刪除自己:

ChangeClipboardChain (hwnd2, hwnd1) ;

Windows將wParam等於hwnd2、lParam等於hwnd1的WM_CHANGECBCHAIN訊息發送給hwnd4。由於hwnd4的下一個測覽器是hwnd3,所以hwnd4把這個訊息傳給hwnd3。現在hwnd3注意到wParam等於它的下一個測覽器(hwnd2),所以將下一個瀏覽器設定為lParam (hwnd1)並且傳回。這樣工作就完成了。現在剪貼簿瀏覽器鏈如下:

目前剪貼簿瀏覽器: hwnd4

hwnd4的下一個瀏覽器: hwnd3

hwnd3的下一個瀏覽器: hwnd1

hwnd1的下一個瀏覽器: NULL

一個簡單的剪貼簿瀏覽器

剪貼簿瀏覽器不一定要像Windows所提供的那樣完善,例如,剪貼簿瀏覽器可以只顯示一種剪貼簿資料格式。程式12-2中所示的CLIPVIEW程式是一種只能顯示CF_TEXT格式的剪貼簿瀏覽器。

hwndViewer = GetClipboardViewer () ;

程式12-2 CLIPVIEW CLIPVIEW.C /*------------------------------------------------------------------------- CLIPVIEW.C -- Simple Clipboard Viewer (c) Charles Petzold, 1998 --------------------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("ClipView") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Simple Clipboard Viewer (Text Only)"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HWND hwndNextViewer ; HGLOBAL hGlobal ; HDC hdc ; PTSTR pGlobal ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_CREATE: hwndNextViewer = SetClipboardViewer (hwnd) ; return 0 ; case WM_CHANGECBCHAIN: if ((HWND) wParam == hwndNextViewer) hwndNextViewer = (HWND) lParam ; else if (hwndNextViewer) SendMessage (hwndNextViewer, message, wParam, lParam) ; return 0 ; case WM_DRAWCLIPBOARD: if (hwndNextViewer) SendMessage (hwndNextViewer, message, wParam, lParam) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; OpenClipboard (hwnd) ; #ifdef UNICODE hGlobal = GetClipboardData (CF_UNICODETEXT) ; #else hGlobal = GetClipboardData (CF_TEXT) ; #endif if (hGlobal != NULL) { pGlobal = (PTSTR) GlobalLock (hGlobal) ; DrawText (hdc, pGlobal, -1, &rect, DT_EXPANDTABS) ; GlobalUnlock (hGlobal) ; } CloseClipboard () ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: ChangeClipboardChain (hwnd, hwndNextViewer) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

CLIPVIEW依上面所討論的方法來處理WM_CREATE、WM_CHANGECBCHAIN、WM_DRAWCLIPBOARD和WM_DESTROY訊息。WM_PAINT訊息處理打開剪貼簿,並用CF_TEXT格式呼叫GetClipboardData。如果函式傳回一個整體記憶體代號,那麼CLIPVIEW將鎖定它,並用DrawText在顯示區域顯示文字。

處理標準格式(如Windows提供的那個剪貼簿一樣)以外的資料格式的剪貼簿瀏覽器還需要完成一些其他工作,比如顯示剪貼簿中目前所有資料格式的名稱。使用者可以通過呼叫EnumClipboardFormats並使用GetClipboardFormatName得到非標準資料格式名稱來完成這項工作。使用CF_OWNERDISPLAY資料格式的剪貼簿瀏覽器必須把下面四個訊息送往剪貼簿資料的擁有者以顯示該資料:

WM_PAINTCLIPBOARD

WM_SIZECLIPBOARD

WM_VSCROLLCLIPBOARD

WM_HSCROLLCLIPBOARD

如果您想編寫這樣的剪貼簿瀏覽器,那麼必須使用GetClipboardOwner獲得剪貼簿所有者的視窗代號,並當您需要修改剪貼簿的顯示區域時,將這些訊息發送給該視窗。