使用剪貼簿
本節有下列工作的程序代碼範例:
實作剪下、複製和貼上命令
本節說明如何在應用程式中實作標準 剪下、 複製和 貼上 命令。 本節中的範例會使用這些方法,使用已註冊的剪貼簿格式、 CF_OWNERDISPLAY
格式和 CF_TEXT
格式,將數據放在剪貼簿上。 已註冊的格式是用來代表矩形或橢圓形文字視窗,稱為標籤。
選取數據
用戶必須先選取要複製或剪下的特定資訊,才能將資訊複製到剪貼簿。 應用程式應該提供一種方法,讓使用者在檔中選取資訊,以及某種視覺回饋,以指出選取的數據。
建立編輯功能表
應用程式應該載入快速鍵數據表,其中包含 [編輯] 功能表命令的標準鍵盤快捷方式。 函 TranslateAccelerator
式必須新增至應用程式的訊息迴圈,加速器才會生效。 如需鍵盤快捷鍵的詳細資訊,請參閱 鍵盤快捷鍵。
WM_INITMENUPOPUP
處理訊息
並非所有剪貼簿命令隨時可供使用者使用。 應用程式應該處理 WM_INITMENUPOPUP
訊息,以啟用可用命令的功能表項,並停用無法使用的命令。
以下是 WM_INITMENUPOPUP
名為 Label 的應用程式案例。
case WM_INITMENUPOPUP:
InitMenu((HMENU) wParam);
break;
函 InitMenu
式的定義如下。
void WINAPI InitMenu(HMENU hmenu)
{
int cMenuItems = GetMenuItemCount(hmenu);
int nPos;
UINT id;
UINT fuFlags;
PLABELBOX pbox = (hwndSelected == NULL) ? NULL :
(PLABELBOX) GetWindowLong(hwndSelected, 0);
for (nPos = 0; nPos < cMenuItems; nPos++)
{
id = GetMenuItemID(hmenu, nPos);
switch (id)
{
case IDM_CUT:
case IDM_COPY:
case IDM_DELETE:
if (pbox == NULL || !pbox->fSelected)
fuFlags = MF_BYCOMMAND | MF_GRAYED;
else if (pbox->fEdit)
fuFlags = (id != IDM_DELETE && pbox->ichSel
== pbox->ichCaret) ?
MF_BYCOMMAND | MF_GRAYED :
MF_BYCOMMAND | MF_ENABLED;
else
fuFlags = MF_BYCOMMAND | MF_ENABLED;
EnableMenuItem(hmenu, id, fuFlags);
break;
case IDM_PASTE:
if (pbox != NULL && pbox->fEdit)
EnableMenuItem(hmenu, id,
IsClipboardFormatAvailable(CF_TEXT) ?
MF_BYCOMMAND | MF_ENABLED :
MF_BYCOMMAND | MF_GRAYED
);
else
EnableMenuItem(hmenu, id,
IsClipboardFormatAvailable(
uLabelFormat) ?
MF_BYCOMMAND | MF_ENABLED :
MF_BYCOMMAND | MF_GRAYED
);
}
}
}
WM_COMMAND
處理訊息
若要處理功能表命令,請將 WM_COMMAND
案例新增至應用程式的主視窗程式。 WM_COMMAND
以下是標籤應用程式視窗程式的案例。
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_CUT:
if (EditCopy())
EditDelete();
break;
case IDM_COPY:
EditCopy();
break;
case IDM_PASTE:
EditPaste();
break;
case IDM_DELETE:
EditDelete();
break;
case IDM_EXIT:
DestroyWindow(hwnd);
}
break;
為了執行 複製 和 剪下 命令,視窗程式會呼叫應用程式定義的 EditCopy
函式。 如需詳細資訊,請參閱 將資訊複製到剪貼簿。 若要執行 Paste 命令,視窗過程會呼叫應用程式定義的 EditPaste
函式。 如需函式 EditPaste
的詳細資訊,請參閱 從剪貼簿貼上資訊。
將資訊複製到剪貼簿
在 Label 應用程式中,應用程式定義的 EditCopy 函式會將目前的選取範圍複製到剪貼簿。 此函式會執行下列動作:
- 呼叫
OpenClipboard
函式以開啟剪貼簿。 - 藉由呼叫
EmptyClipboard
函式來清空剪貼簿。 SetClipboardData
針對應用程式提供的每個剪貼簿格式呼叫函式一次。- 藉由呼叫
CloseClipboard
函式來關閉剪貼簿。
根據目前的選取範圍,EditCopy 函式會複製一系列文字,或複製代表整個標籤的應用程式定義結構。 稱為的 結構 LABELBOX
定義如下。
#define BOX_ELLIPSE 0
#define BOX_RECT 1
#define CCH_MAXLABEL 80
#define CX_MARGIN 12
typedef struct tagLABELBOX { // box
RECT rcText; // coordinates of rectangle containing text
BOOL fSelected; // TRUE if the label is selected
BOOL fEdit; // TRUE if text is selected
int nType; // rectangular or elliptical
int ichCaret; // caret position
int ichSel; // with ichCaret, delimits selection
int nXCaret; // window position corresponding to ichCaret
int nXSel; // window position corresponding to ichSel
int cchLabel; // length of text in atchLabel
TCHAR atchLabel[CCH_MAXLABEL];
} LABELBOX, *PLABELBOX;
以下是函式 EditCopy
。
BOOL WINAPI EditCopy(VOID)
{
PLABELBOX pbox;
LPTSTR lptstrCopy;
HGLOBAL hglbCopy;
int ich1, ich2, cch;
if (hwndSelected == NULL)
return FALSE;
// Open the clipboard, and empty it.
if (!OpenClipboard(hwndMain))
return FALSE;
EmptyClipboard();
// Get a pointer to the structure for the selected label.
pbox = (PLABELBOX) GetWindowLong(hwndSelected, 0);
// If text is selected, copy it using the CF_TEXT format.
if (pbox->fEdit)
{
if (pbox->ichSel == pbox->ichCaret) // zero length
{
CloseClipboard(); // selection
return FALSE;
}
if (pbox->ichSel < pbox->ichCaret)
{
ich1 = pbox->ichSel;
ich2 = pbox->ichCaret;
}
else
{
ich1 = pbox->ichCaret;
ich2 = pbox->ichSel;
}
cch = ich2 - ich1;
// Allocate a global memory object for the text.
hglbCopy = GlobalAlloc(GMEM_MOVEABLE,
(cch + 1) * sizeof(TCHAR));
if (hglbCopy == NULL)
{
CloseClipboard();
return FALSE;
}
// Lock the handle and copy the text to the buffer.
lptstrCopy = GlobalLock(hglbCopy);
memcpy(lptstrCopy, &pbox->atchLabel[ich1],
cch * sizeof(TCHAR));
lptstrCopy[cch] = (TCHAR) 0; // null character
GlobalUnlock(hglbCopy);
// Place the handle on the clipboard.
SetClipboardData(CF_TEXT, hglbCopy);
}
// If no text is selected, the label as a whole is copied.
else
{
// Save a copy of the selected label as a local memory
// object. This copy is used to render data on request.
// It is freed in response to the WM_DESTROYCLIPBOARD
// message.
pboxLocalClip = (PLABELBOX) LocalAlloc(
LMEM_FIXED,
sizeof(LABELBOX)
);
if (pboxLocalClip == NULL)
{
CloseClipboard();
return FALSE;
}
memcpy(pboxLocalClip, pbox, sizeof(LABELBOX));
pboxLocalClip->fSelected = FALSE;
pboxLocalClip->fEdit = FALSE;
// Place a registered clipboard format, the owner-display
// format, and the CF_TEXT format on the clipboard using
// delayed rendering.
SetClipboardData(uLabelFormat, NULL);
SetClipboardData(CF_OWNERDISPLAY, NULL);
SetClipboardData(CF_TEXT, NULL);
}
// Close the clipboard.
CloseClipboard();
return TRUE;
}
從剪貼簿貼上資訊
在 Label 應用程式中,應用程式定義的 EditPaste
函式會貼上剪貼簿的內容。 此函式會執行下列動作:
- 呼叫
OpenClipboard
函式以開啟剪貼簿。 - 決定要擷取的可用剪貼簿格式。
- 呼叫
GetClipboardData
函式,以選取的格式擷取數據的句柄。 - 將數據複本插入檔中。 所
GetClipboardData
傳回的句柄仍由剪貼簿所擁有,因此應用程式不得釋放或將它鎖定。 - 藉由呼叫
CloseClipboard
函式來關閉剪貼簿。
如果選取標籤並包含插入點,EditPaste 函式會在插入點插入剪貼簿中的文字。 如果沒有選取專案,或選取標籤,函式會使用剪貼簿上的應用程式定義 LABELBOX
結構來建立新的標籤。 結構 LABELBOX
會使用已註冊的剪貼簿格式放在剪貼簿上。
稱為的 結構 LABELBOX
定義如下。
#define BOX_ELLIPSE 0
#define BOX_RECT 1
#define CCH_MAXLABEL 80
#define CX_MARGIN 12
typedef struct tagLABELBOX { // box
RECT rcText; // coordinates of rectangle containing text
BOOL fSelected; // TRUE if the label is selected
BOOL fEdit; // TRUE if text is selected
int nType; // rectangular or elliptical
int ichCaret; // caret position
int ichSel; // with ichCaret, delimits selection
int nXCaret; // window position corresponding to ichCaret
int nXSel; // window position corresponding to ichSel
int cchLabel; // length of text in atchLabel
TCHAR atchLabel[CCH_MAXLABEL];
} LABELBOX, *PLABELBOX;
以下是函式 EditPaste
。
VOID WINAPI EditPaste(VOID)
{
PLABELBOX pbox;
HGLOBAL hglb;
LPTSTR lptstr;
PLABELBOX pboxCopy;
int cx, cy;
HWND hwnd;
pbox = hwndSelected == NULL ? NULL :
(PLABELBOX) GetWindowLong(hwndSelected, 0);
// If the application is in edit mode,
// get the clipboard text.
if (pbox != NULL && pbox->fEdit)
{
if (!IsClipboardFormatAvailable(CF_TEXT))
return;
if (!OpenClipboard(hwndMain))
return;
hglb = GetClipboardData(CF_TEXT);
if (hglb != NULL)
{
lptstr = GlobalLock(hglb);
if (lptstr != NULL)
{
// Call the application-defined ReplaceSelection
// function to insert the text and repaint the
// window.
ReplaceSelection(hwndSelected, pbox, lptstr);
GlobalUnlock(hglb);
}
}
CloseClipboard();
return;
}
// If the application is not in edit mode,
// create a label window.
if (!IsClipboardFormatAvailable(uLabelFormat))
return;
if (!OpenClipboard(hwndMain))
return;
hglb = GetClipboardData(uLabelFormat);
if (hglb != NULL)
{
pboxCopy = GlobalLock(hglb);
if (pboxCopy != NULL)
{
cx = pboxCopy->rcText.right + CX_MARGIN;
cy = pboxCopy->rcText.top * 2 + cyText;
hwnd = CreateWindowEx(
WS_EX_NOPARENTNOTIFY | WS_EX_TRANSPARENT,
atchClassChild, NULL, WS_CHILD, 0, 0, cx, cy,
hwndMain, NULL, hinst, NULL
);
if (hwnd != NULL)
{
pbox = (PLABELBOX) GetWindowLong(hwnd, 0);
memcpy(pbox, pboxCopy, sizeof(LABELBOX));
ShowWindow(hwnd, SW_SHOWNORMAL);
SetFocus(hwnd);
}
GlobalUnlock(hglb);
}
}
CloseClipboard();
}
註冊剪貼簿格式
若要註冊剪貼簿格式,請將對函式的呼叫 RegisterClipboardFormat
新增至應用程式的實例初始化函式,如下所示。
// Register a clipboard format.
// We assume that atchTemp can contain the format name and
// a null-terminator, otherwise it is truncated.
//
LoadString(hinstCurrent, IDS_FORMATNAME, atchTemp,
sizeof(atchTemp)/sizeof(TCHAR));
uLabelFormat = RegisterClipboardFormat(atchTemp);
if (uLabelFormat == 0)
return FALSE;
WM_RENDERFORMAT
處理和 WM_RENDERALLFORMATS
訊息
如果視窗將句柄傳遞 NULL
至 SetClipboardData
函式,它就必須處理 WM_RENDERFORMAT
和 WM_RENDERALLFORMATS
訊息,才能在要求時轉譯數據。
如果窗口延遲轉譯特定格式,然後另一個應用程式要求該格式的數據,則會 WM_RENDERFORMAT
將訊息傳送至視窗。 此外,如果窗口延遲轉譯一或多個格式,而且當視窗即將終結時,如果其中一些格式仍然沒有損毀,則會 WM_RENDERALLFORMATS
在損毀之前將訊息傳送至視窗。
若要轉譯剪貼簿格式,視窗程序應該使用 SetClipboardData
函式在剪貼簿上放置非NULL
數據句柄。 如果視窗程式正在轉譯格式以回應 WM_RENDERFORMAT
訊息,則必須先開啟剪貼簿,再呼叫 SetClipboardData
。 但是,如果要轉譯一或多個格式以回應 WM_RENDERALLFORMATS
訊息,它必須開啟剪貼簿,並檢查視窗在呼叫 SetClipboardData
之前仍擁有剪貼簿,而且它必須在傳回之前關閉剪貼簿。
標籤應用程式會處理 WM_RENDERFORMAT
和 WM_RENDERALLFORMATS
訊息,如下所示。
case WM_RENDERFORMAT:
RenderFormat((UINT) wParam);
break;
case WM_RENDERALLFORMATS:
if (OpenClipboard(hwnd))
{
if (GetClipboardOwner() == hwnd)
{
RenderFormat(uLabelFormat);
RenderFormat(CF_TEXT);
}
CloseClipboard();
}
break;
在這兩種情況下,視窗過程都會呼叫應用程式定義的 RenderFormat
函式,如下所示。
稱為的 結構 LABELBOX
定義如下。
#define BOX_ELLIPSE 0
#define BOX_RECT 1
#define CCH_MAXLABEL 80
#define CX_MARGIN 12
typedef struct tagLABELBOX { // box
RECT rcText; // coordinates of rectangle containing text
BOOL fSelected; // TRUE if the label is selected
BOOL fEdit; // TRUE if text is selected
int nType; // rectangular or elliptical
int ichCaret; // caret position
int ichSel; // with ichCaret, delimits selection
int nXCaret; // window position corresponding to ichCaret
int nXSel; // window position corresponding to ichSel
int cchLabel; // length of text in atchLabel
TCHAR atchLabel[CCH_MAXLABEL];
} LABELBOX, *PLABELBOX;
void WINAPI RenderFormat(UINT uFormat)
{
HGLOBAL hglb;
PLABELBOX pbox;
LPTSTR lptstr;
int cch;
if (pboxLocalClip == NULL)
return;
if (uFormat == CF_TEXT)
{
// Allocate a buffer for the text.
cch = pboxLocalClip->cchLabel;
hglb = GlobalAlloc(GMEM_MOVEABLE,
(cch + 1) * sizeof(TCHAR));
if (hglb == NULL)
return;
// Copy the text from pboxLocalClip.
lptstr = GlobalLock(hglb);
memcpy(lptstr, pboxLocalClip->atchLabel,
cch * sizeof(TCHAR));
lptstr[cch] = (TCHAR) 0;
GlobalUnlock(hglb);
// Place the handle on the clipboard.
SetClipboardData(CF_TEXT, hglb);
}
else if (uFormat == uLabelFormat)
{
hglb = GlobalAlloc(GMEM_MOVEABLE, sizeof(LABELBOX));
if (hglb == NULL)
return;
pbox = GlobalLock(hglb);
memcpy(pbox, pboxLocalClip, sizeof(LABELBOX));
GlobalUnlock(hglb);
SetClipboardData(uLabelFormat, hglb);
}
}
WM_DESTROYCLIPBOARD
處理訊息
視窗可以處理 WM_DESTROYCLIPBOARD
訊息,以釋放它保留以支援延遲轉譯的任何資源。 例如,將標籤複製到剪貼簿時,會配置本機記憶體物件。 然後,它會釋放此物件以回應 WM_DESTROYCLIPBOARD
訊息,如下所示。
case WM_DESTROYCLIPBOARD:
if (pboxLocalClip != NULL)
{
LocalFree(pboxLocalClip);
pboxLocalClip = NULL;
}
break;
使用擁有者顯示剪貼簿格式
如果視窗使用 CF_OWNERDISPLAY
剪貼簿格式將資訊放在剪貼簿上,則必須執行下列動作:
WM_PAINTCLIPBOARD
處理訊息。 當剪貼簿查看器視窗的一部分必須重新繪製時,此訊息會傳送給剪貼簿擁有者。WM_SIZECLIPBOARD
處理訊息。 當剪貼簿查看器視窗已重設大小或其內容已變更時,此訊息會傳送給剪貼簿擁有者。 一般而言,視窗會藉由設定剪貼簿查看器視窗的捲動位置和範圍來回應此訊息。 為了回應此訊息,標籤應用程式也會更新SIZE
剪貼簿查看器視窗的結構。WM_HSCROLLCLIPBOARD
處理和WM_VSCROLLCLIPBOARD
訊息。 當剪貼簿查看器視窗中發生滾動條事件時,這些訊息會傳送給剪貼簿擁有者。WM_ASKCBFORMATNAME
處理訊息。 剪貼簿查看器視窗會將此訊息傳送至應用程式,以擷取擁有者顯示格式的名稱。
卷標應用程式的視窗程式會處理這些訊息,如下所示。
LRESULT CALLBACK MainWindowProc(hwnd, msg, wParam, lParam)
HWND hwnd;
UINT msg;
WPARAM wParam;
LPARAM lParam;
{
static RECT rcViewer;
RECT rc;
LPRECT lprc;
LPPAINTSTRUCT lpps;
switch (msg)
{
//
// Handle other messages.
//
case WM_PAINTCLIPBOARD:
// Determine the dimensions of the label.
SetRect(&rc, 0, 0,
pboxLocalClip->rcText.right + CX_MARGIN,
pboxLocalClip->rcText.top * 2 + cyText
);
// Center the image in the clipboard viewer window.
if (rc.right < rcViewer.right)
{
rc.left = (rcViewer.right - rc.right) / 2;
rc.right += rc.left;
}
if (rc.bottom < rcViewer.bottom)
{
rc.top = (rcViewer.bottom - rc.bottom) / 2;
rc.bottom += rc.top;
}
// Paint the image, using the specified PAINTSTRUCT
// structure, by calling the application-defined
// PaintLabel function.
lpps = (LPPAINTSTRUCT) GlobalLock((HGLOBAL) lParam);
PaintLabel(lpps, pboxLocalClip, &rc);
GlobalUnlock((HGLOBAL) lParam);
break;
case WM_SIZECLIPBOARD:
// Save the dimensions of the window in a static
// RECT structure.
lprc = (LPRECT) GlobalLock((HGLOBAL) lParam);
memcpy(&rcViewer, lprc, sizeof(RECT));
GlobalUnlock((HGLOBAL) lParam);
// Set the scroll ranges to zero (thus eliminating
// the need to process the WM_HSCROLLCLIPBOARD and
// WM_VSCROLLCLIPBOARD messages).
SetScrollRange((HWND) wParam, SB_HORZ, 0, 0, TRUE);
SetScrollRange((HWND) wParam, SB_VERT, 0, 0, TRUE);
break;
case WM_ASKCBFORMATNAME:
LoadString(hinst, IDS_OWNERDISPLAY,
(LPSTR) lParam, wParam);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
監視剪貼簿內容
監視剪貼簿變更的方式有三種。 最舊的方法是建立剪貼簿查看器視窗。 Windows 2000 新增了查詢剪貼簿序號的功能,而 Windows Vista 新增了剪貼簿格式接聽程序的支援。 剪貼簿查看器窗口支援與舊版 Windows 的回溯相容性。 新的程式應該使用剪貼簿格式接聽程式或剪貼簿序號。
查詢剪貼簿序號
每次剪貼簿的內容變更時,都會遞增稱為剪貼簿序號的 32 位值。 程式可以藉由呼叫 GetClipboardSequenceNumber
函式來擷取目前的剪貼簿序號。 藉由將傳回的值與先前呼叫 GetClipboardSequenceNumber
所傳回的值進行比較,程式可以判斷剪貼簿內容是否已變更。 這個方法更適合根據目前剪貼簿內容快取結果的程式,而且在使用該快取的結果之前,必須知道計算是否仍然有效。 請注意,這不是通知方法,不應該在輪詢迴圈中使用。 若要在剪貼簿內容變更時收到通知,請使用剪貼簿格式接聽程式或剪貼簿查看器。
建立剪貼簿格式接聽程式
剪貼簿格式接聽程式是一個視窗,在剪貼簿的內容變更時,已註冊通知。 這個方法建議不要建立剪貼簿查看器視窗,因為實作較簡單,而且如果程式無法正確維護剪貼簿查看器鏈結,或剪貼簿查看器鏈結中的視窗停止回應訊息,則避免發生問題。
視窗會藉由呼叫 AddClipboardFormatListener
函式,註冊為剪貼簿格式接聽程式。 當剪貼簿的內容變更時,視窗會張貼訊息 WM_CLIPBOARDUPDATE
。 註冊會維持有效狀態,直到視窗藉由呼叫 RemoveClipboardFormatListener
函式來取消註冊本身為止。
建立剪貼簿查看器視窗
剪貼簿查看器視窗會顯示剪貼簿的目前內容,並在剪貼簿內容變更時接收訊息。 若要建立剪貼簿檢視器視窗,您的應用程式必須執行下列動作:
- 將視窗新增至剪貼簿查看器鏈結。
WM_CHANGECBCHAIN
處理訊息。WM_DRAWCLIPBOARD
處理訊息。- 先從剪貼簿查看器鏈結移除視窗,再將其終結。
將視窗新增至剪貼簿查看器鏈結
視窗會藉由呼叫 SetClipboardViewer
函式,將本身新增至剪貼簿查看器鏈結。 傳回值是鏈結中下一個視窗的句柄。 窗口必須追蹤此值,例如,將它儲存在名為 hwndNextViewer
的靜態變數中。
下列範例會將視窗新增至剪貼簿查看器鏈結,以回應 WM_CREATE
訊息。
case WM_CREATE:
// Add the window to the clipboard viewer chain.
hwndNextViewer = SetClipboardViewer(hwnd);
break;
下列工作會顯示代碼段:
WM_CHANGECBCHAIN
處理訊息
當另一個視窗從剪貼簿查看器鏈結中移除本身時,剪貼簿查看器視窗就會收到 WM_CHANGECBCHAIN
訊息。 如果移除的視窗是鏈結中的下一個視窗,接收訊息的窗口必須從鏈結取消連結下一個視窗。 否則,此訊息應該傳遞至鏈結中的下一個視窗。
下列範例顯示訊息的 WM_CHANGECBCHAIN
處理。
case WM_CHANGECBCHAIN:
// If the next window is closing, repair the chain.
if ((HWND) wParam == hwndNextViewer)
hwndNextViewer = (HWND) lParam;
// Otherwise, pass the message to the next link.
else if (hwndNextViewer != NULL)
SendMessage(hwndNextViewer, uMsg, wParam, lParam);
break;
從剪貼簿查看器鏈結移除視窗
若要從剪貼簿查看器鏈結中移除本身,視窗會呼叫 函式 ChangeClipboardChain
。 下列範例會從剪貼簿查看器鏈結中移除視窗,以回應 WM_DESTROY
訊息。
case WM_DESTROY:
ChangeClipboardChain(hwnd, hwndNextViewer);
PostQuitMessage(0);
break;
WM_DRAWCLIPBOARD
處理訊息
訊息 WM_DRAWCLIPBOARD
會通知剪貼簿查看器視窗剪貼簿的內容已變更。 處理訊息時 WM_DRAWCLIPBOARD
,視窗應該執行下列動作:
- 判斷要顯示的可用剪貼簿格式。
- 擷取剪貼簿數據,並將其顯示在視窗中。 或者,如果剪貼簿格式為
CF_OWNERDISPLAY
,請將訊息傳送WM_PAINTCLIPBOARD
給剪貼簿擁有者。 - 將訊息傳送至剪貼簿查看器鏈結中的下一個視窗。
如需處理WM_DRAWCLIPBOARD
訊息的範例,請參閱剪貼簿查看器範例中的範例清單。
剪貼簿查看器的範例
下列範例顯示簡單的剪貼簿查看器應用程式。
HINSTANCE hinst;
UINT uFormat = (UINT)(-1);
BOOL fAuto = TRUE;
LRESULT APIENTRY MainWndProc(hwnd, uMsg, wParam, lParam)
HWND hwnd;
UINT uMsg;
WPARAM wParam;
LPARAM lParam;
{
static HWND hwndNextViewer;
HDC hdc;
HDC hdcMem;
PAINTSTRUCT ps;
LPPAINTSTRUCT lpps;
RECT rc;
LPRECT lprc;
HGLOBAL hglb;
LPSTR lpstr;
HBITMAP hbm;
HENHMETAFILE hemf;
HWND hwndOwner;
switch (uMsg)
{
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
// Branch depending on the clipboard format.
switch (uFormat)
{
case CF_OWNERDISPLAY:
hwndOwner = GetClipboardOwner();
hglb = GlobalAlloc(GMEM_MOVEABLE,
sizeof(PAINTSTRUCT));
lpps = GlobalLock(hglb);
memcpy(lpps, &ps, sizeof(PAINTSTRUCT));
GlobalUnlock(hglb);
SendMessage(hwndOwner, WM_PAINTCLIPBOARD,
(WPARAM) hwnd, (LPARAM) hglb);
GlobalFree(hglb);
break;
case CF_BITMAP:
hdcMem = CreateCompatibleDC(hdc);
if (hdcMem != NULL)
{
if (OpenClipboard(hwnd))
{
hbm = (HBITMAP)
GetClipboardData(uFormat);
SelectObject(hdcMem, hbm);
GetClientRect(hwnd, &rc);
BitBlt(hdc, 0, 0, rc.right, rc.bottom,
hdcMem, 0, 0, SRCCOPY);
CloseClipboard();
}
DeleteDC(hdcMem);
}
break;
case CF_TEXT:
if (OpenClipboard(hwnd))
{
hglb = GetClipboardData(uFormat);
lpstr = GlobalLock(hglb);
GetClientRect(hwnd, &rc);
DrawText(hdc, lpstr, -1, &rc, DT_LEFT);
GlobalUnlock(hglb);
CloseClipboard();
}
break;
case CF_ENHMETAFILE:
if (OpenClipboard(hwnd))
{
hemf = GetClipboardData(uFormat);
GetClientRect(hwnd, &rc);
PlayEnhMetaFile(hdc, hemf, &rc);
CloseClipboard();
}
break;
case 0:
GetClientRect(hwnd, &rc);
DrawText(hdc, "The clipboard is empty.", -1,
&rc, DT_CENTER | DT_SINGLELINE |
DT_VCENTER);
break;
default:
GetClientRect(hwnd, &rc);
DrawText(hdc, "Unable to display format.", -1,
&rc, DT_CENTER | DT_SINGLELINE |
DT_VCENTER);
}
EndPaint(hwnd, &ps);
break;
case WM_SIZE:
if (uFormat == CF_OWNERDISPLAY)
{
hwndOwner = GetClipboardOwner();
hglb = GlobalAlloc(GMEM_MOVEABLE, sizeof(RECT));
lprc = GlobalLock(hglb);
GetClientRect(hwnd, lprc);
GlobalUnlock(hglb);
SendMessage(hwndOwner, WM_SIZECLIPBOARD,
(WPARAM) hwnd, (LPARAM) hglb);
GlobalFree(hglb);
}
break;
case WM_CREATE:
// Add the window to the clipboard viewer chain.
hwndNextViewer = SetClipboardViewer(hwnd);
break;
case WM_CHANGECBCHAIN:
// If the next window is closing, repair the chain.
if ((HWND) wParam == hwndNextViewer)
hwndNextViewer = (HWND) lParam;
// Otherwise, pass the message to the next link.
else if (hwndNextViewer != NULL)
SendMessage(hwndNextViewer, uMsg, wParam, lParam);
break;
case WM_DESTROY:
ChangeClipboardChain(hwnd, hwndNextViewer);
PostQuitMessage(0);
break;
case WM_DRAWCLIPBOARD: // clipboard contents changed.
// Update the window by using Auto clipboard format.
SetAutoView(hwnd);
// Pass the message to the next window in clipboard
// viewer chain.
SendMessage(hwndNextViewer, uMsg, wParam, lParam);
break;
case WM_INITMENUPOPUP:
if (!HIWORD(lParam))
InitMenu(hwnd, (HMENU) wParam);
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_EXIT:
DestroyWindow(hwnd);
break;
case IDM_AUTO:
SetAutoView(hwnd);
break;
default:
fAuto = FALSE;
uFormat = LOWORD(wParam);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return (LRESULT) NULL;
}
void WINAPI SetAutoView(HWND hwnd)
{
static UINT auPriorityList[] = {
CF_OWNERDISPLAY,
CF_TEXT,
CF_ENHMETAFILE,
CF_BITMAP
};
uFormat = GetPriorityClipboardFormat(auPriorityList, 4);
fAuto = TRUE;
InvalidateRect(hwnd, NULL, TRUE);
UpdateWindow(hwnd);
}
void WINAPI InitMenu(HWND hwnd, HMENU hmenu)
{
UINT uFormat;
char szFormatName[80];
LPCSTR lpFormatName;
UINT fuFlags;
UINT idMenuItem;
// If a menu is not the display menu, no initialization is necessary.
if (GetMenuItemID(hmenu, 0) != IDM_AUTO)
return;
// Delete all menu items except the first.
while (GetMenuItemCount(hmenu) > 1)
DeleteMenu(hmenu, 1, MF_BYPOSITION);
// Check or uncheck the Auto menu item.
fuFlags = fAuto ? MF_BYCOMMAND | MF_CHECKED :
MF_BYCOMMAND | MF_UNCHECKED;
CheckMenuItem(hmenu, IDM_AUTO, fuFlags);
// If there are no clipboard formats, return.
if (CountClipboardFormats() == 0)
return;
// Open the clipboard.
if (!OpenClipboard(hwnd))
return;
// Add a separator and then a menu item for each format.
AppendMenu(hmenu, MF_SEPARATOR, 0, NULL);
uFormat = EnumClipboardFormats(0);
while (uFormat)
{
// Call an application-defined function to get the name
// of the clipboard format.
lpFormatName = GetPredefinedClipboardFormatName(uFormat);
// For registered formats, get the registered name.
if (lpFormatName == NULL)
{
// Note that, if the format name is larger than the
// buffer, it is truncated.
if (GetClipboardFormatName(uFormat, szFormatName,
sizeof(szFormatName)))
lpFormatName = szFormatName;
else
lpFormatName = "(unknown)";
}
// Add a menu item for the format. For displayable
// formats, use the format ID for the menu ID.
if (IsDisplayableFormat(uFormat))
{
fuFlags = MF_STRING;
idMenuItem = uFormat;
}
else
{
fuFlags = MF_STRING | MF_GRAYED;
idMenuItem = 0;
}
AppendMenu(hmenu, fuFlags, idMenuItem, lpFormatName);
uFormat = EnumClipboardFormats(uFormat);
}
CloseClipboard();
}
BOOL WINAPI IsDisplayableFormat(UINT uFormat)
{
switch (uFormat)
{
case CF_OWNERDISPLAY:
case CF_TEXT:
case CF_ENHMETAFILE:
case CF_BITMAP:
return TRUE;
}
return FALSE;
}