http://aclacl.brinkster.net/MFC/ch19b.htm
The Legacy Clipboard
Data is transferred to and from the legacy clipboard using a small subset of Windows API functions. The following table briefly summarizes those functions.
Clipboard API Functions
Function | Description |
---|---|
OpenClipboard | Opens the clipboard |
CloseClipboard | Closes the clipboard |
EmptyClipboard | Deletes the current contents of the clipboard |
GetClipboardData | Retrieves data from the clipboard |
SetClipboardData | Transfers data to the clipboard |
Placing data on the clipboard is a four-step process:
- Open the clipboard with ::OpenClipboard.
- Discard any data presently stored in the clipboard with ::EmptyClipboard.
- Use ::SetClipboardData to transfer ownership of a global memory block or other object (for example, a bitmap handle) containing clipboard data to the clipboard.
- Close the clipboard with ::CloseClipboard.
A global memory block is a block of memory allocated with the ::GlobalAlloc API function.::GlobalAlloc returns a handle of type HGLOBAL, which can be treated as a generic HANDLE in a Win32 application. A related function named ::GlobalLock takes an HGLOBAL and returns a pointer to the memory block. Windows programmers don't use ::GlobalAlloc much anymore because ::HeapAllocsuperseded it in the Win32 API. But ::GlobalAlloc is still useful for clipboard programming because the clipboard requires a memory handle, not a pointer.
The following code places a text string on the clipboard by copying the text string to a global memory block and handing the memory block over to the clipboard:
char szText[]= "Hello, world"; // ANSI characters if (::OpenClipboard (m_hWnd)) { ::EmptyClipboard (); HANDLE hData = ::GlobalAlloc (GMEM_MOVEABLE, ::lstrlen (szText) + 1); LPSTR pData = (LPSTR) ::GlobalLock (hData); ::lstrcpy (pData, szText); ::GlobalUnlock (hData); ::SetClipboardData (CF_TEXT, hData); ::CloseClipboard (); } |
Once a global memory block is handed over to the clipboard, the application that allocated the block should neither use it nor delete it. The clipboard now owns the memory and will release it at the appropriate time—specifically, the next time an application calls ::EmptyClipboard.
The sole parameter passed to ::OpenClipboard is the handle of the window that "owns" the clipboard while the clipboard is open. In an MFC application, of course, you can retrieve a CWnd's window handle from its m_hWnd data member. ::OpenClipboard will fail if another application has the clipboard open. Forcing every application to open the clipboard before using it is the way that Windows synchronizes access to this shared resource and ensures that the clipboard's contents don't change while an application is using it.
Retrieving data from the clipboard is equally simple. Here are the steps:
- Open the clipboard with ::OpenClipboard.
- Use ::GetClipboardData to retrieve the handle of the global memory block or other object containing clipboard data.
- Make a local copy of the data by copying it from the global memory block.
- Close the clipboard with ::CloseClipboard.
Here's how you can retrieve the text string placed on the clipboard in the previous example:
char szText[BUFLEN]; if (::OpenClipboard (m_hWnd)) { HANDLE hData = ::GetClipboardData (CF_TEXT); if (hData != NULL) { LPCSTR pData = (LPCSTR) ::GlobalLock (hData); if (::lstrlen (pData) < BUFLEN) ::lstrcpy (szText, pData); ::GlobalUnlock (hData); } ::CloseClipboard (); } |
If a text string is available from the clipboard, szText will hold a copy of it when this routine finishes.
Clipboard Formats
Both ::SetClipboardData and ::GetClipboardData accept an integer value specifying a clipboard format, which identifies the type of data involved in the transfer. The examples in the previous section used CF_TEXT, which identifies the data as ANSI text. Windows uses a separate clipboard format ID for Unicode text. (That's why both examples used the char data type instead of TCHAR.) CF_TEXT is one of several predefined clipboard formats that Windows supports. A partial list of clipboard formats is shown in the following table.
Commonly Used Clipboard Formats
Format | Data Type |
---|---|
CF_BITMAP | Windows bitmap |
CF_DIB | Device-independent bitmap |
CF_ENHMETAFILE | GDI enhanced metafile |
CF_METAFILEPICT | Old-style (nonenhanced) GDI metafile with sizing and mapping-mode information attached |
CF_HDROP | List of file names in HDROP format |
CF_PALETTE | GDI palette |
CF_TEXT | Text composed of 8-bit ANSI characters |
CF_TIFF | Bitmap in TIFF format |
CF_UNICODETEXT | Text composed of 16-bit Unicode characters |
CF_WAVE | Audio data in WAV format |
You can use the predefined clipboard formats to transfer bitmaps, palettes, enhanced metafiles, and other objects as easily as you can transfer text. For example, if m_bitmap is a CBitmap data member that holds a bitmap, here's one way to make a copy of the bitmap and place it on the clipboard:
if (::OpenClipboard (m_hWnd)) { // Make a copy of the bitmap. BITMAP bm; CBitmap bitmap; m_bitmap.GetObject (sizeof (bm), &bm); bitmap.CreateBitmapIndirect (&bm); CDC dcMemSrc, dcMemDest; dcMemSrc.CreateCompatibleDC (NULL); CBitmap* pOldBitmapSrc = dcMemSrc.SelectObject (&m_bitmap); dcMemDest.CreateCompatibleDC (NULL); CBitmap* pOldBitmapDest = dcMemDest.SelectObject (&bitmap); dcMemDest.BitBlt (0, 0, bm.bmWidth, bm.bmHeight, &dcMemSrc, 0, 0, SRCCOPY); HBITMAP hBitmap = (HBITMAP) bitmap.Detach (); dcMemDest.SelectObject (pOldBitmapDest); dcMemSrc.SelectObject (pOldBitmapSrc); // Place the copy on the clipboard. ::EmptyClipboard (); ::SetClipboardData (CF_BITMAP, hBitmap); ::CloseClipboard (); } |
To retrieve a bitmap from the clipboard, call ::GetClipboardData and pass it a CF_BITMAP parameter:
if (::OpenClipboard (m_hWnd)) { HBITMAP hBitmap = (HBITMAP) ::GetClipboardData (CF_BITMAP); if (hBitmap != NULL) { // Make a local copy of the bitmap. } ::CloseClipboard (); } |
Notice the pattern here. The application that places data on the clipboard tells Windows the data type. The application that retrieves the data asks for a particular data type. If data isn't available in that format,::GetClipboardData returns NULL. In the example above, ::GetClipboardData returns NULL if the clipboard contains no CF_BITMAP-type data and the code that copies the bitmap is bypassed.
The system silently converts some clipboard formats to related data types when ::GetClipboardData is called. For example, if application A copies a string of ANSI text to the clipboard (CF_TEXT) and application B calls ::GetClipboardData requesting Unicode text(CF_UNICODETEXT), Windows 2000 converts the text to Unicode and ::GetClipboardData returns a valid memory handle. Bitmaps benefit from implicit data conversions, too. Both Windows 98 and Windows 2000 convert a CF_BITMAP bitmap into a CF_DIB, and vice versa. This adds a welcome measure of portability to clipboard formats that represent different forms of the same basic data types.
The CF_HDROP Clipboard Format
One of the more interesting—and least documented—clipboard formats is CF_HDROP. When you retrieve CF_HDROP-formatted data from the clipboard, you get back an HDROP, which is actually a handle to a global memory block. Inside the memory block is a list of file names. Rather than read the file names by parsing the contents of the memory block, you can use the ::DragQueryFile function. The following code retrieves an HDROP from the clipboard and stuffs all the file names into the list box referenced by the CListBox pointer pListBox:
if (::OpenClipboard (m_hWnd)) { HDROP hDrop = (HDROP) ::GetClipboardData (CF_HDROP); if (hDrop != NULL) { // Find out how many file names the HDROP contains. int nCount = ::DragQueryFile (hDrop, (UINT) -1, NULL, 0); // Enumerate the file names. if (nCount) { TCHAR szFile[MAX_PATH]; for (int i=0; i<nCount; i++) { ::DragQueryFile (hDrop, i, szFile, sizeof (szFile) / sizeof (TCHAR)); pListBox->AddString (szFile); } } } ::CloseClipboard (); } |
Extracting file names from an HDROP is easy; inserting them is a bit more work. The memory block that an HDROP references contains a DROPFILES structure followed by a list of file names terminated by two consecutive NULL characters. DROPFILES is defined as follows in Shlobj.h:
typedef struct _DROPFILES { DWORD pFiles; // Offset of file list POINT pt; // Drop coordinates BOOL fNC; // Client or nonclient area BOOL fWide; // ANSI or Unicode text } DROPFILES, FAR * LPDROPFILES; |
To create your own HDROP, you allocate a global memory block, initialize a DROPFILES structure inside it, and append a list of file names. The only DROPFILES fields you need to initialize are pFiles, which holds the offset relative to the beginning of the memory block of the first character in the list of file names, and fWide, which indicates whether the file names are composed of ANSI (fWide=FALSE) or Unicode (fWide=TRUE) characters. To illustrate, the following statements create an HDROP containing two file names and place the HDROP on the clipboard:
TCHAR szFiles[3][32] = { _T ("C:\\My Documents\\Course\\Chap20.doc"), _T ("C:\\My Documents\\Course\\Chap21.doc"), _T ("") }; if (::OpenClipboard (m_hWnd)) { ::EmptyClipboard (); int nSize = sizeof (DROPFILES) + sizeof (szFiles); HANDLE hData = ::GlobalAlloc (GHND, nSize); LPDROPFILES pDropFiles = (LPDROPFILES) ::GlobalLock (hData); pDropFiles->pFiles = sizeof (DROPFILES); #ifdef UNICODE pDropFiles->fWide = TRUE; #else pDropFiles->fWide = FALSE; #endif LPBYTE pData = (LPBYTE) pDropFiles + sizeof (DROPFILES); ::CopyMemory (pData, szFiles, sizeof (szFiles)); ::GlobalUnlock (hData); ::SetClipboardData (CF_HDROP, hData); ::CloseClipboard (); } |
The GHND parameter passed to ::GlobalAlloc in this example combines the GMEM_MOVEABLE and GMEM_ZEROINIT flags. GMEM_ZEROINIT tells ::GlobalAlloc to initialize all the bytes in the block to 0, which ensures that the uninitialized members of the DROPFILES structures are set to 0. As an aside, the GMEM_MOVEABLE flag is no longer necessary when you allocate global memory blocks to hand over to the clipboard in the Win32 environment, despite what the documentation might say. Its presence here is a tip of the hat to 16-bit Windows, which required us to allocate clipboard memory with both the GMEM_MOVEABLE and GMEM_DDESHARE flags.
HDROPs might seem like a curious way to pass around lists of file names. However, the Windows 98 and Windows 2000 shells use this format to cut, copy, and paste files. Here's a simple experiment you can perform to see for yourself how the shell uses HDROPs. Copy the sample code into an application, and change the file names to reference real files on your hard disk. Execute the code to transfer the HDROP to the clipboard. Then open a window onto a hard disk folder and select Paste from the window's Edit menu. The shell will respond by moving the files whose names appear in the HDROP into the folder.
Private Clipboard Formats
CF_TEXT, CF_BITMAP, and other predefined clipboard formats cover a wide range of data types, but they can't possibly include every type of data that an application might want to transfer through the clipboard. For this reason, Windows allows you to register your own private clipboard formats and use them in lieu of or in conjunction with standard clipboard formats.
Let's say you're writing a Widget application that creates widgets. You'd like your users to be able to cut or copy widgets to the clipboard and paste them elsewhere in the document (or perhaps into an entirely different document). To support such functionality, call the Win32 API function::RegisterClipboardFormat to register a private clipboard format for widgets:
UINT nID = ::RegisterClipboardFormat (_T ("Widget")); |
The UINT you get back is the ID of your private clipboard format. To copy a widget to the clipboard, copy all the data needed to define the widget into a global memory block, and then call ::SetClipboardDatawith the private clipboard format ID and the memory handle:
::SetClipboardData (nID, hData); |
To retrieve the widget from the clipboard, pass the widget's clipboard format ID to ::GetClipboardData:
HANDLE hData = ::GetClipboardData (nID); |
Then lock the block to get a pointer and reconstruct the widget from the data in the memory block. The key here is that if 10 different applications (or 10 different instances of the same application) call::RegisterClipboardFormat with the same format name, all 10 will receive the same clipboard format ID. Thus, if application A copies a widget to the clipboard and application B retrieves it, the process will work just fine as long as both applications specify the same format name when they call::RegisterClipboardFormat.
Providing Data in Multiple Formats
Placing multiple items on the clipboard is perfectly legal as long as each item represents a different format. Applications do it all the time. It's an effective way to make data available to a wide range of applications—even those that don't understand your private clipboard formats.
Microsoft Excel is a good example of an application that uses multiple clipboard formats. When you select a range of spreadsheet cells in Excel and copy the selection to the clipboard, Excel places up to 30 items on the clipboard. One of those items uses a private clipboard format that represents native Excel spreadsheet data. Another is a CF_BITMAP rendition of the cells. The Paint utility that comes with Windows doesn't understand Excel's private clipboard format, but it can paste Excel spreadsheet cells into a bitmap. At least it appears that Paint can paste spreadsheet cells. In truth, it pastes a bitmapped image of those cells, not real spreadsheet cells. You can even paste Excel data into Notepad because one of the formats that Excel places on the clipboard is—you guessed it—CF_TEXT. By making spreadsheet data available in a wide range of formats, Excel increases the portability of its clipboard data.
How do you place two or more items on the clipboard? It's easy: Just call ::SetClipboardData once for each format:
::SetClipboardData (nID, hPrivateData); ::SetClipboardData (CF_BITMAP, hBitmap); ::SetClipboardData (CF_TEXT, hTextData); |
Now if an application calls ::GetClipboardData asking for data in CF_TEXT format, CF_BITMAP format, or the private format specified by nID, the call will succeed and the caller will receive a non-NULL data handle in return.
Querying for Available Data Formats
One way to find out whether clipboard data is available in a particular format is to call::GetClipboardData and check for a NULL return value. Sometimes, however, you'll want to know in advance whether ::GetClipboardData will succeed or to see all the formats that are currently available enumerated so that you can pick the one that best fits your needs. The following Win32 API functions let you do all this and more:
Function | Description |
---|---|
CountClipboardFormats | Returns the number of formats currently available |
EnumClipboardFormats | Enumerates all available clipboard formats |
IsClipboardFormatAvailable | Indicates whether data is available in a particular format |
GetPriorityClipboardFormat | Given a prioritized list of formats, indicates which one is the first available |
::IsClipboardFormatAvailable is the simplest of the four functions. To find out whether data is available in CF_TEXT format, call ::IsClipboardFormatAvailable like this.
if (::IsClipboardFormatAvailable (CF_TEXT)) { // Yes, it's available. } else { // No, it's not available. } |
This function is often used to implement update handlers for the Edit menu's Paste command. Refer toPart 7 for an example of this usage.
::IsClipboardFormatAvailable works even if the clipboard isn't open. But don't forget that clipboard data is subject to change when the clipboard isn't open. Don't make the mistake of writing code like this:
if (::IsClipboardFormatAvailable (CF_TEXT)) { if (::OpenClipboard (m_hWnd)) { HANDLE hData = ::GetClipboardData (CF_TEXT); LPCSTR pData = (LPCSTR) ::GlobalLock (hData); |
This code is buggy because in a multitasking environment, there's a small but very real chance that the data on the clipboard will be replaced after ::IsClipboardFormatAvailable executes but before::GetClipboardData is called. You can avoid this risk by opening the clipboard prior to calling::IsClipboardFormatAvailable:
if (::OpenClipboard (m_hWnd)) { if (::IsClipboardFormatAvailable (CF_TEXT)) { HANDLE hData = ::GetClipboardData (CF_TEXT); LPCSTR pData = (LPCSTR) ::GlobalLock (hData); |
This code will work just fine because only the application that has the clipboard open can change the clipboard's contents.
You can use ::EnumClipboardFormats to iterate through a list of all available clipboard formats. Here's an example:
if (::OpenClipboard (m_hWnd)) { UINT nFormat = 0; // Must be 0 to start the iteration. while (nFormat = ::EnumClipboardFormats (nFormat)) { // Next clipboard format is in nFormat. } ::CloseClipboard (); } |
Because ::EnumClipboardFormats returns 0 when it reaches the end of the list, the loop falls through after retrieving the last available format. If you simply want to know how many data formats are available on the clipboard, call ::CountClipboardFormats.
The final clipboard data availability function, ::GetPriorityClipboardFormat, simplifies the process of checking for not just one clipboard format, but several. Suppose your application is capable of pasting data in a private format stored in nID, in CF_TEXT format, or in CF_BITMAP format. You would prefer the private format, but if that's not available, you'll take CF_TEXT instead, or if all else fails, CF_BITMAP. Rather than write
if (::OpenClipboard (m_hWnd)) { if (::IsClipboardFormatAvailable (nID)) { // Perfect! } else if (::IsClipboardFormatAvailable (CF_TEXT)) { // Not the best, but I'll take it. } else if (::IsClipboardFormatAvailable (CF_BITMAP)) { // Better than nothing. } ::CloseClipboard (); } |
you can write
UINT nFormats[3] = { nID, // First choice CF_TEXT, // Second choice CF_BITMAP // Third choice }; if (::OpenClipboard (m_hWnd)) { UINT nFormat = ::GetPriorityClipboardFormat (nFormats, 3); if (nFormat > 0) { // nFormat holds nID, CF_TEXT, or CF_BITMAP. } ::CloseClipboard (); } |
::GetPriorityClipboardFormat's return value is the ID of the first format in the list that matches a format that is currently available. ::GetPriorityClipboardFormat returns -1 if none of the formats is available or 0 if the clipboard is empty.
Delayed Rendering
One of the limitations of the legacy clipboard is that all data placed on it is stored in memory. For text strings and other simple data types, memory-based data transfers are both fast and efficient. But suppose someone copies a 10-MB bitmap to the clipboard. Until the clipboard is emptied, the bitmap will occupy 10 MB of RAM. And if no one pastes the bitmap, the memory allocated to hold it will have been used for naught.
To avoid such wastefulness, Windows supports delayed rendering. Delayed rendering allows an application to say, "I have data that I'll make available through the clipboard, but I'm not going to copy it to the clipboard until someone asks for it." How does delayed rendering work? First you call::SetClipboardData with a valid clipboard format ID but a NULL data handle. Then you respond to WM_RENDERFORMAT messages by physically placing the data on the clipboard with::SetClipboardData. The WM_RENDERFORMAT message is sent if and when an application calls::GetClipboardData asking for data in that particular format. If no one asks for the data, the message is never sent, and you'll never have to allocate that 10 MB of memory. Keep in mind that a WM_RENDERFORMAT message handler should not call ::OpenClipboard and ::CloseClipboardbecause the window that receives the message implicitly owns the clipboard at the time the message is received.
An application that processes WM_RENDERFORMAT messages must process WM_RENDERALLFORMATS messages, too. The WM_RENDERALLFORMATS message is sent if an application terminates while the clipboard holds NULL data handles that the application put there. The message handler's job is to open the clipboard, transfer to it the data that the application promised to provide through delayed rendering, and close the clipboard. Putting the data on the clipboard ensures that the data will be available to other applications after an application that uses delayed rendering is long gone.
A third clipboard message, WM_DESTROYCLIPBOARD, also plays a role in delayed rendering. This message informs an application that it's no longer responsible for providing delay-rendered data. It's sent when another application calls ::EmptyClipboard. It's also sent after a WM_RENDERALLFORMATS message. If you're holding on to any resources in order to respond to WM_RENDERFORMAT and WM-_RENDERALLFORMATS messages, you can safely free those resources when a WM_DESTROYCLIPBOARD message arrives.
Here's how an MFC application might use delayed rendering to place a bitmap on the clipboard:
// In CMyWindow's message map ON_COMMAND (ID_EDIT_COPY, OnEditCopy) ON_WM_RENDERFORMAT () ON_WM_RENDERALLFORMATS () |
This example isn't entirely realistic because if there's a possibility that the bitmap could change between the time it's copied to the clipboard and the time it's retrieved (a distinct possibility if the application is a bitmap editor and the bitmap is open for editing), OnEditCopy is obliged to make a copy of the bitmap in its current state. But think about it. If OnEditCopy makes a copy of the bitmap, the whole purpose of using delayed rendering is defeated. Delayed rendering is a tool for conserving memory, but if an application is obliged to make a copy of each item that is "copied" to the clipboard for delayed rendering, shouldn't it just copy the item to the clipboard outright?
Not necessarily. The snapshot can be stored on disk. Here's a revised version of the code that demonstrates how delayed rendering can conserve memory even if the data is subject to change:
// In CMyWindow's message map ON_COMMAND (ID_EDIT_COPY, OnEditCopy) ON_WM_RENDERFORMAT () ON_WM_RENDERALLFORMATS () ON_WM_DESTROYCLIPBOARD () |
The idea is to save a copy of the bitmap to a file in OnEditCopy and re-create the bitmap from the file inOnRenderFormat. Disk space is orders of magnitude cheaper than RAM, so this trade-off is acceptable in most situations.
Building a Reusable Clipboard Class
Given the nature of the clipboard, you might be surprised to discover that MFC doesn't provide aCClipboard class that encapsulates the clipboard API. You could write your own clipboard class without much difficulty, but there's really no good reason to bother. Why? Because the OLE clipboard does everything that the legacy clipboard does and then some, and because MFC does a thorough job of wrapping the OLE clipboard. Operations involving the OLE clipboard are considerably more complex than operations involving the legacy clipboard, but MFC levels the playing field. In fact, with MFC to lend a hand, using the OLE clipboard is no more difficult than using the legacy clipboard. The next several sections explain why.