Windows程序设计-剪贴板

来源:互联网 发布:大数据的作用与意义 编辑:程序博客网 时间:2024/05/20 17:40

      • 数据格式
      • 内存配置
      • 将文字传送到剪贴簿
      • 从剪贴簿上取得文字
      • 剪贴板的简单使用
      • 延迟提出
      • 自定义数据格式
      • 剪贴板浏览器

Windows剪贴板允许把数据从一个程序传送到另一个程序中。它的原理相对而言比较简单,把数据存放到剪贴板上的程序或从剪贴板上取出数据的程序都无须太多的负担。

数据格式

CF_TEXT以NULL结尾的ANSI字符集字符串。它在每行末尾包含一个carriage return和linefeed字符。
CF_OEMTEXT含有文字数据(与CF_TEXT类似)的内存块。但是它使用的是OEM字符集。
CF_LOCALE一个国家地区标识符的句柄。表示剪贴簿文字使用的国别地区设定。
CF_BITMAP与设备相关的位图格式。
CF_DIB定义一个设备无关位图的内存块。
CF_PALETTE调色盘句柄。它通常与CF_DIB配合使用,以定义与设备相关的位图所使用的颜色调色盘。
CF_TIFF含有标号图像文件格式(TIFF)数据的整体内存块。
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 API来配置一个内存块,可以调用:

hGlobal = GlobalAlloc (uiFlags, dwSize) ;

此函数有两个参数:一系列可能的旗标和内存块的字节大小。函数传回一个HGLOBAL型态的句柄,称为整体内存块句柄整体句柄。传回值为NULL表示不能配置足够的内存。

虽然GlobalAlloc的两个参数略有不同,但它们都是32位的无正负号整数。如果将第一个参数设定为0,那么您就可以更有效地使用旗标GMEM_FIXED。在这种情况下,GlobalAlloc传回的整体句柄实际是指向所配置内存块的指针。如 果 不 喜 欢 将 内 存 块 中 的 每 一 位 都 初 始 化 为 0 , 那 么 您 也 能 够 使 用 旗 标GMEM_ZEROINIT。在Windows表头文件中,简洁的GPTR旗标定义为GMEM_FIXED和GMEM_ZEROINIT旗标的组合:

#define GPTR (GMEM_FIXED | GMEM_ZEROINIT)

下面是一个重新配置函数:

hGlobal = GlobalReAlloc (hGlobal, dwSize, uiFlags) ;

如果内存块扩大了,您可以用GMEM_ZEROINIT旗标将新的字节设为0。
下面是获得内存块大小的函数:

dwSize = GlobalSize (hGlobal) ;

释放内存块的函数:

GlobalFree (hGlobal) ;

简写标识符:

#define GHND (GMEM_MOVEABLE | GMEM_ZEROINIT)

GMEM_MOVEABLE旗标允许Windows在虚拟内存中移动一个内存块。这不是说将在物理内存中移动内存块,只是应用程序用于读写这块内存的地址可以被变动。尽管GMEM_MOVEABLE是16位Windows的通则,但是它的作用现在已经少得多了。如果您的应用程序频繁地配置、重新配置以及释放不同大小的内存块,应用程序的虚拟地址空间将会变得支离破碎。可以想象得到,最后虚拟内存地址空间就会被用完。如果这是个可能会发生的问题,那么您将希望内存是可移动的。下面就介绍如何让内存块成为可搬移位置的。
首先定义一个指标(例如,一个int型态的)和一个GLOBALHANDLE型态的变量:

int * p ;GLOBALHANDLE hGlobal ;

然后配置内存。例如:

hGlobal = GlobalAlloc (GHND, 1024) ;

与处理其它Windows句柄一样,您不必担心数字的实际意义,只要照著作就好了。需要存取内存块时,可以呼叫:

p = (int *) GlobalLock (hGlobal) ;

此函数将句柄转换为指标。在内存块被锁定期间,Windows将固定虚拟内存中的地址,不再移动那块内存。存取结束后呼叫:

GlobalUnlock (hGlobal) ;

这将使Windows可以在虚拟内存中移动内存块。要真正确保此程序正常运作(体验早期Windows程序写作者的痛苦经历),您应该在单一个消息处理期间锁定和解锁内存块。
在释放内存时,呼叫GlobalFree应使用句柄而不是指标。如果您现在不能存取句柄,可以使用下面的函数:

hGlobal = GlobalHandle (p) ;

在解锁之前,您能够多次锁定一个内存块。Windows保留一个锁定次数,而且在内存块可被自由移动之前,每次锁定都需要相对应的解锁。当Windows在虚拟内存中移动一个内存块时,不需要将字节从一个位置复制到另一个,只需巧妙地处理内存页映像表。通常,让32位Windows为您的程序配置可移动的内存块,其唯一确实的理由只是避免虚拟内存的空间碎裂出现。使用剪贴簿时,也应该使用可移动内存。
为剪贴簿配置内存时,您应该以GMEM_MOVEABLE和GMEM_SHARE旗标呼叫GlobalAlloc函数。GMEM_SHARE旗标使得其它应用程序也可以使用那块内存。

将文字传送到剪贴簿

让我们想象把一个ANSI字符串传送到剪贴簿上,并且我们已经有了指向这个字符串的指针(pString)。现在希望传送这个字符串的iLength字符,这些字符可能以NULL结尾,也可能
不以NULL结尾。
首先,通过使用GlobalAlloc来配置一个足以储存字符串的内存块,其中还包括一个终止字符NULL:

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

如果未能配置到内存块,hGlobal的值将为NULL 。如果配置成功,则锁定这块内存,并得到指向它的一个指标:

pGlobal = GlobalLock (hGlobal) ;

将字符串复制到内存块中:

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

由于GlobalAlloc的GHND旗标已使整个内存块在配置期间被清除为零,所以不需要增加结尾的NULL 。以下叙述为内存块解锁:

GlobalUnlock (hGlobal) ;

现在就有了表示以NULL结尾的文字所在内存块的内存句柄。为了把它送到剪贴簿中,打开剪贴簿并把它清空:

OpenClipboard (hwnd) ;EmptyClipboard () ;

利用CF_TEXT标识符把内存句柄交给剪贴簿,关闭剪贴簿:

SetClipboardData (CF_TEXT, hGlobal) ;CloseClipboard () ;

工作告一段落。
下面是关于此过程的一些规则:

在处理同一个消息的过程中呼叫OpenClipboard和CloseClipboard。不需要时,不要打开剪贴簿。
不要把锁定的内存句柄交给剪贴簿。
当呼叫SetClipboardData后,请不要再继续使用该内存块。它不再属于使用者程序,必须把句柄看成是无效的。如果需要继续存取数据,可以制作数据的副本,或从剪贴簿中读取它(如下节所述)。您也可以在SetClipboardData呼叫和CloseClipboard呼叫之间继续使用内存块,但是不要使用传递给SetClipboardData函数的整体句柄。事实上,此函数也传回一个整体句柄,必需锁定这些代码以存取内存。在呼叫CloseClipboard之前,应先为此句柄解锁。

从剪贴簿上取得文字

从剪贴簿上取得文字只比把文字传送到剪贴簿上稍微复杂一些。您必须首先确定剪贴簿是否含有CF_TEXT格式的数据,最简单的方法是呼叫

bAvailable = IsClipboardFormatAvailable (CF_TEXT) ;

如果剪贴簿上含有CF_TEXT数据,这个函数将传回TRUE(非零)。
IsClipboardFormatAvailable是少数几个不需先打开剪贴簿就可以使用的剪贴簿函数之一。但是,如果您之后想再打开剪贴簿以取得这个文字,就应该再做一次检查(使用同样的函数或其它方法),以便确定CF_TEXT数据是否仍然留在剪贴簿中。

为了传送出文字,首先打开剪贴簿:

OpenClipboard (hwnd) ;

会得到代表文字的内存块代号:

hGlobal = GetClipboardData (CF_TEXT) ;

如果剪贴簿不包含CF_TEXT格式的数据,此句柄就为NULL。这是确定剪贴簿是否含有文字的另一种方法。如果GetClipboardData传回NULL,则关闭剪贴簿,不做其它任何工作。
从 GetClipboardData 得 到 的 句 柄 并 不 属 于 使 用 者 程 序 - 它 属 于 剪 贴 簿 。 仅 在GetClipboardData和CloseClipboard呼叫之间这个句柄才有效。您不能释放这个句柄或
更改它所引用的数据。如果需要继续存取这些数据,必须制作这个内存块的副本。
这里有一种将数据复制到使用者程序中的方法。首先,配置一块与剪贴簿数据块大小相同的内存块,并配置一个指向该块的指标:

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

再次呼叫hGlobal ,而hGlobal是从GetClipboardData呼叫传回的整体句柄。现在锁定句柄,获得一个指向剪贴簿块的指针:

pGlobal = GlobalLock (hGlobal) ;

现在就可以复制数据了:

strcpy (pText, pGlobal) ;

或者,您可以使用一些简单的C程序代码:

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

在关闭剪贴簿之前先解锁内存块:

GlobalUnlock (hGlobal) ;CloseClipboard () ;

剪贴板的简单使用

clipText

ClipText.rc

///////////////////////////////////////////////////////////////////////////////// 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    ENDEND///////////////////////////////////////////////////////////////////////////////// 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, NOINVERTEND

RESOURCE.H

#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

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) ;// 根据是否是Unicode来设置默认内容和标题#ifdef UNICODE#define CF_TCHAR CF_UNICODETEXTTCHAR szDefaultText[] = TEXT ("Default Text - Unicode Version") ;TCHAR szCaption[]     = TEXT ("Clipboard Text Transfers - Unicode Version") ;#else#define CF_TCHAR CF_TEXTTCHAR szDefaultText[] = TEXT ("Default Text - ANSI Version") ;TCHAR szCaption[]     = TEXT ("Clipboard Text Transfers - ANSI Version") ;#endifint 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:        // IsClipboardFormatAvailable判断是否含有CF_TEXT格式的数据          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) ;}

可以看到,在将数据准备好之后,从剪贴板传输数据时需要四个调用:

复制

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

存取这些数据需要三个调用:

粘贴

OpenClipboard (hwnd) ;hGlobal = GetClipboardData (iFormat) ;// 其它行程序CloseClipboard () ;

延迟提出

当把数据放入剪贴簿中时,一般来说要制作一份数据的副本,并将包含这份副本的内存块句柄传给剪贴簿。对非常大的数据项来说,这种方法会浪费内存空间。如果使用者不想把数据粘贴到另一个程序里,那么,在被其它内容取代之前,它将一直占据着内存空间。通过使用一种叫做「延迟提出」的技术可以避免这个问题。实际上,直到另一个程序需要数据,程序才提供这份数据。为此,不将数据句柄传给Windows,而是在SetClipboardData呼叫中使用NULL:

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

自定义数据格式

共三种方法:

  • 在SetClipboardData和GetClipboardData呼叫中可使用下列wFormat值:CF_DSPTEXT、CF_DSPBITMAP、CF_DSPMETAFILEPICT或CF_DSPENHMETAFILE(字母DSP代表「显示器」);
  • CF_OWNERDISPLAY旗标
  • 注册自己的剪贴簿格式名

剪贴板浏览器

clipview

/*-----------------------------------------   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为Null          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 ;    // windows发送给目前剪贴板浏览器    // 区别:WM_PAINTCLIPBOARD是由剪贴簿浏览器发送给使用CF_OWNERDISPLAY          // 剪贴板数据格式的程序     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:         // 从剪贴板链中删除自己,windows会发送WM_CHANGECBCHAIN          ChangeClipboardChain (hwnd, hwndNextViewer) ;          PostQuitMessage (0) ;          return 0 ;     }     return DefWindowProc (hwnd, message, wParam, lParam) ;}

处理标准格式(如Windows提供的那个剪贴簿一样)以外的数据格式的剪贴簿浏览器还需要完成一些其它工作,比如显示剪贴簿中目前所有数据格式的名称。使用者可以通过呼叫
EnumClipboardFormats并使用GetClipboardFormatName得到非标准数据格式名称来完成这项工作。使用CF_OWNERDISPLAY数据格式的剪贴簿浏览器必须把下面四个消
息送往剪贴簿数据的拥有者以显示该资料:

WM_PAINTCLIPBOARDWM_SIZECLIPBOARDWM_VSCROLLCLIPBOARDWM_HSCROLLCLIPBOARD

如果您想编写这样的剪贴簿浏览器,那么必须使用GetClipboardOwner获得剪贴簿所有者的窗口句柄,并当您需要修改剪贴簿的显示区域时,将这些消息发送给该窗口。

原创粉丝点击