剪贴板是由系统定义的,并不属于任何一个特定的进程。系统中所有进程都可以访问和设置剪贴板。剪贴板最大的特点就是数据传输没有明确的目标,数据是被动访问的;剪贴板的内容可以被多次访问,直到新的数据写入。剪贴板是一种可供选择的进程间通信方式,但由于系统中任何一个进程都可以都可以无限制地访问剪贴板,因此,它是一种不可靠的通信方式。
1)获取和设置剪贴板数据
涉及到的API有:OpenClipboard、EmptyClipboard、SetClipboardData、GetClipboardData、CloseClipboard等。
 
OpenClipboard用于打开剪贴板,获得剪贴板的句柄:
BOOL WINAPI OpenClipboard(
  __in_opt  HWND hWndNewOwner //指定的窗口可以收到剪贴板操作所产生的消息
                                               //如果为NULL,则采用当前任务的窗口
);
 
EmptyClipboard用于清空剪贴板中的内容:
BOOL WINAPI EmptyClipboard(void);
 
SetClipboardData用于设置剪贴板的内容:
HANDLE WINAPI SetClipboardData(
  __in      UINT uFormat,       //指定数据格式,可以是标准系统格式,也可以是用户自定义格式
  __in_opt  HANDLE hMem     //需要设置的数据的内存句柄;需使用全局内存管理的函数分配和
                                     //设置,且在分配时需指定GMEM_MOVEABLE标志
);
 
GetClipboardData用于从剪贴板获取数据:
HANDLE WINAPI GetClipboardData(
  __in  UINT uFormat //指定获取的数据的格式,获得的数据使用句柄返回
);
 
2)一般来说利用剪贴板进行数据通信是不具有实时性,所有操作都依赖于用户。除非使用剪贴板查看器Viewer,这样就可以立即知道剪贴板中内容的变化。
系统提供了WM_DRAWCLIPBOARD消息用于监视剪贴板的变化。如果调用SetClipboardViewer函数设置了窗口为剪贴板查看器,那么当剪贴板中内容变化时,所注册的查看器窗口就会收到WM_CHANGECBCHAIN消息和WM_DRAWCLIPBOARD消息。SetClipboardViewer函数原型如下:
HWND WINAPI SetClipboardViewer(
  __in  HWND hWndNewViewer //指定监视窗口
);
剪贴板查看器的代码例子如下:
HINSTANCE hinst; 
UINT uFormat = (UINT)(-1); 
BOOL fAuto = TRUE; 
 
LRESULT CALLBACK MainWndProc(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: //收到WM_PAINT消息后显示剪贴板中的数据
            hdc = BeginPaint(hwnd, &ps); 
            // Branch depending on the clipboard format. 
              //uFormat全局变量,在WM_DRAWCLIPBOARD和WM_COMMAND
            //消息处理中,使用SetAutoView设置
            //根据剪贴板中数据的不同格式,使用不同的显示方式
            switch (uFormat) 
            { 
                case CF_OWNERDISPLAY: //剪贴板的所有者必须显示并刷新Viewer的窗口
                    hwndOwner = GetClipboardOwner();//获得剪贴板的所有者
                            //获取剪贴板的数据
                    hglb = GlobalAlloc(GMEM_MOVEABLE, 
                        sizeof(PAINTSTRUCT)); 
                    lpps = (LPPAINTSTRUCT)GlobalLock(hglb);
                    memcpy(lpps, &ps, sizeof(PAINTSTRUCT)); 
                    GlobalUnlock(hglb); 
                           //向剪贴板所有者发送WM_PAINTCLIPBOARD消息
                    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); //获取剪贴板的数据
                                      //将位图选择进DC,显示在窗口客户区
                            SelectObject(hdcMem, hbm); 
                            GetClientRect(hwnd, &rc); 
                            BitBlt(hdc, 0, 0, rc.right, rc.bottom, 
                                hdcMem, 0, 0, SRCCOPY); 
                            CloseClipboard();//关闭剪贴板
                        } 
                        DeleteDC(hdcMem); //释放DC
                    } 
                    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); //获取剪贴板数据
                                //调用PlayEnhMetaFile在窗口客户区上显示
                        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: //当窗口创建时,在剪贴板Viewer链中增加一个
            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: 
              //窗口hwnd销毁时,从剪贴板查看器链中移除
            ChangeClipboardChain(hwnd, hwndNextViewer); 
            PostQuitMessage(0); 
            break; 
 
        case  // 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: //当popup菜单弹出时收到此消息
            if (!HIWORD(lParam)) //根据剪贴板中内容的格式设置菜单
                InitMenu(hwnd, (HMENU) wParam); 
            break; 
 
        case WM_COMMAND: //处理用户菜单输入
            switch (LOWORD(wParam)) 
            { 
                case IDM_EXIT: //用户点击“退出”菜单项
                    DestroyWindow(hwnd); 
                    break; 
 
                case IDM_AUTO: //用户点击“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)
* 获取剪贴板的主要格式,并设置显示方式
**********************************************************/
void WINAPI SetAutoView(HWND hwnd) 
{ 
    static UINT auPriorityList[] = { 
        CF_OWNERDISPLAY, 
        CF_TEXT, 
        CF_ENHMETAFILE, 
        CF_BITMAP 
    }; 
//设置剪贴板主要格式,设置显示格式
//uFormat在收到WM_PAINT消息时用到
    uFormat = GetPriorityClipboardFormat(auPriorityList, 4); 
    fAuto = TRUE; 
 
    InvalidateRect(hwnd, NULL, TRUE); 
    UpdateWindow(hwnd); 
} 
 
/**************************************************************
* 功能:根据剪贴板中内容的格式,设置菜单项供用户选择显示方式
* 参数:hwnd--窗口句柄
*       hmenu--菜单句柄
**************************************************************/
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; 
} 
 
3)剪贴板中存在各种数据格式,系统使用一个UINT类型的数据来表示剪贴板中数据类型。在这些格式信息中,有很多是各种应用程序之间通用的,比如文本、位图等。这些数据格式由系统预先定义,称为标准格式;当然应用程序也可自行定义剪贴板的数据格式,这样可以方便地在同一个应用程序的不同实例间进行数据传递而不用对数据格式进行过多的处理(典型的就包括word)。
 
常见标准格式:
CF_BITMAP    //位图句柄(HBITMAP)
CF_DIB             //内存位置包含BITMAPINFO结构和位图数据
CF_ENHMETAFILE         //增强的图元文件句柄(HENHMETAFILE)
CF_OEMTEXT        //OEM字符集的字符串(以CR-LF格式换行)
CF_OWNERDISPLAY    //由剪贴板查看器查看的格式
CF_PALETTE  //调色板数据
CF_RIFF  //标准的CF_WAVE波形数据
CF_TEXT          //ANSI字符串(以CR-LF格式换行)
CF_WAVE       //PCM波形
CF_TIFF  //Tagged图像文件格式
CF_UNICODETEXT       //Unicode字符串
 
自定义格式:
调用函数RegisterClipboardFormat可以自定义格式:
UINT WINAPI RegisterClipboardFormat(//返回值是系统分配的格式类型值(UINT)
  __in  LPCTSTR lpszFormat //格式名,
);
 
多种格式:
很多情况下,数据的格式不止一种,比如格式化的文本有效的格式不止一种(例如从Word中复制的数据、从网页中复制的数据等),因此可能存在多重格式。
以下几个API函数是用于获取当前剪贴板中的格式信息的:
GetPriorityClipboardFormat的功能是检测剪贴板中是否有paFormatPriorityList参数指定的格式数组中的格式存在,如果有则返回格式数组中的第一个剪贴板当前具有的格式:
int WINAPI GetPriorityClipboardFormat(
  __in  UINT *paFormatPriorityList, //格式数组,存储用于检测的格式信息
  __in  int    //paFormatPriorityList数组的大小
);
 
CountClipboardFormats函数用于返回当前剪贴板中具有的不同格式的数量:
int WINAPI CountClipboardFormats(void);
 
EnumClipboardFormats函数用于列举当前剪贴板中的所有格式:
UINT WINAPI EnumClipboardFormats(
  __in  UINT format   //指定一个已知的格式,通过函数返回值返回下一个格式
);
 
GetUpdatedClipboardFormats函数用于获取当前剪贴板的所有格式:
BOOL WINAPI GetUpdatedClipboardFormats(
  __out  PUINT lpuiFormats, //指向用于保存返回的格式数组的缓冲区
  __in   UINT cFormats, //lpuiFormats可以容纳的格式信息的数量
  __out  PUINT pcFormatsOut //返回真是的数组大小
);
 
由于剪贴板数据会有多种格式,在调用GetClipboardData函数获取数据时,应该指定格式。一般情况下,指定不同格式,将获得不同的内容。
 
剪贴板数据的格式信息:
每一个剪贴板格式都有一个格式名,格式名是一个字符串,使用GetClipboardFormatName函数可以获得:
int WINAPI GetClipboardFormatName(
  __in   UINT format, //要检索的格式ID
  __out  LPTSTR lpszFormatName, //存储返回的格式名的缓冲区
  __in   int cchMaxCount //拷贝到缓冲区的最大数据长度
);