使用剪貼簿

本節有下列工作的程序代碼範例:

實作剪下、複製和貼上命令

本節說明如何在應用程式中實作標準 剪下複製貼上 命令。 本節中的範例會使用這些方法,使用已註冊的剪貼簿格式、 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 函式會將目前的選取範圍複製到剪貼簿。 此函式會執行下列動作:

  1. 呼叫 OpenClipboard 函式以開啟剪貼簿。
  2. 藉由呼叫 EmptyClipboard 函式來清空剪貼簿。
  3. SetClipboardData針對應用程式提供的每個剪貼簿格式呼叫函式一次。
  4. 藉由呼叫 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 函式會貼上剪貼簿的內容。 此函式會執行下列動作:

  1. 呼叫 OpenClipboard 函式以開啟剪貼簿。
  2. 決定要擷取的可用剪貼簿格式。
  3. 呼叫 GetClipboardData 函式,以選取的格式擷取數據的句柄。
  4. 將數據複本插入檔中。 所 GetClipboardData 傳回的句柄仍由剪貼簿所擁有,因此應用程式不得釋放或將它鎖定。
  5. 藉由呼叫 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 訊息

如果視窗將句柄傳遞 NULLSetClipboardData 函式,它就必須處理 WM_RENDERFORMATWM_RENDERALLFORMATS 訊息,才能在要求時轉譯數據。

如果窗口延遲轉譯特定格式,然後另一個應用程式要求該格式的數據,則會 WM_RENDERFORMAT 將訊息傳送至視窗。 此外,如果窗口延遲轉譯一或多個格式,而且當視窗即將終結時,如果其中一些格式仍然沒有損毀,則會 WM_RENDERALLFORMATS 在損毀之前將訊息傳送至視窗。

若要轉譯剪貼簿格式,視窗程序應該使用 SetClipboardData 函式在剪貼簿上放置非NULL數據句柄。 如果視窗程式正在轉譯格式以回應 WM_RENDERFORMAT 訊息,則必須先開啟剪貼簿,再呼叫 SetClipboardData。 但是,如果要轉譯一或多個格式以回應 WM_RENDERALLFORMATS 訊息,它必須開啟剪貼簿,並檢查視窗在呼叫 SetClipboardData之前仍擁有剪貼簿,而且它必須在傳回之前關閉剪貼簿。

標籤應用程式會處理 WM_RENDERFORMATWM_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 ,視窗應該執行下列動作:

  1. 判斷要顯示的可用剪貼簿格式。
  2. 擷取剪貼簿數據,並將其顯示在視窗中。 或者,如果剪貼簿格式為 CF_OWNERDISPLAY,請將訊息傳送 WM_PAINTCLIPBOARD 給剪貼簿擁有者。
  3. 將訊息傳送至剪貼簿查看器鏈結中的下一個視窗。

如需處理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; 
}