使用剪贴板

本部分包含以下任务的代码示例:

实现“剪切”、“复制”和“粘贴”等命令

本部分介绍如何在应用程序中实现标准的“剪切”、“复制”和“粘贴”命令。 本部分中的示例使用这些方法,利用已注册的剪贴板格式(即 CF_OWNERDISPLAY 格式和 CF_TEXT 格式)将数据放在剪贴板上。 已注册的格式用于表示矩形或椭圆文本窗口,称为标签。

选择数据

在将信息复制到剪贴板之前,用户必须选择要复制或剪切的特定信息。 应用程序应提供一种方法,让用户可选择文档内的信息和某种视觉反馈,以指示选择的数据。

创建“编辑”菜单

应用程序应加载包含“编辑”菜单命令的标准键盘快捷键的快捷键表。 必须将 TranslateAccelerator 函数添加到应用程序的消息循环中,快捷键才会生效。 有关键盘快捷键的详细信息,请参阅键盘快捷键

处理 WM_INITMENUPOPUP 消息

并非所有剪贴板命令可供用户在任何给定的时间使用。 应用程序应处理 WM_INITMENUPOPUP 消息,以启用可用命令的菜单项并禁用不可用的命令。

下面是名为“标签”的应用程序的 WM_INITMENUPOPUP 案例。

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 函数。 有关详细信息,请参阅将信息复制到剪贴板。 若要执行“粘贴”命令,窗口过程应调用应用程序定义的 EditPaste 函数。 有关 EditPaste 函数的详细信息,请参阅粘贴剪贴板中的信息

将信息复制到剪贴板

在“标签”应用程序中,应用程序定义的 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; 
}

粘贴剪贴板中的信息

在“标签”应用程序中,应用程序定义的 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_RENDERFORMATWM_RENDERALLFORMATS 消息

如果窗口将 NULL 句柄传递给 SetClipboardData 函数,则此函数必须处理 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_HSCROLLCLIPBOARDWM_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; 
}