您正在查看: MFC 分类下的文章

The Legacy Clipboard

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 ::HeapAlloc superseded 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\\Book\\Chap20.doc"),
     _T ("C:\\My Documents\\Book\\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;
    #endifLPBYTE 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 ::SetClipboardData with 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 to Chapter 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); ::CloseClipboard ();
     }
    }
    

    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);}
     ::CloseClipboard ();
    }
    

    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 ::CloseClipboard because 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 ()// Elsewhere in CMyWindow
    void CMyWindow::OnEditCopy ()
    {
     ::SetClipboardData (CF_BITMAP, NULL);
    }
    

    void CMyWindow::OnRenderFormat (UINT nFormat)
    {
    if (nFormat == CF_BITMAP) {// Make a copy of the bitmap, and store the handle in hBitmap. ::SetClipboardData (CF_BITMAP, hBitmap);
    }
    }

    void CMyWindow::OnRenderAllFormats ()
    {
    ::OpenClipboard (m_hWnd);
    OnRenderFormat (CF_BITMAP);
    ::CloseClipboard ();
    }

    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 ()// Elsewhere in CMyWindow
    void CMyWindow::OnEditCopy ()
    {
     // Save the bitmap to a temporary disk file.
     ::SetClipboardData (CF_BITMAP, NULL);
    }
    

    void CMyWindow::OnRenderFormat (UINT nFormat)
    {
    if (nFormat == CF_BITMAP) {// Re-create the bitmap from the data in the temporary file. ::SetClipboardData (CF_BITMAP, hBitmap);
    }
    }

    void CMyWindow::OnRenderAllFormats ()
    {
    ::OpenClipboard (m_hWnd);
    OnRenderFormat (CF_BITMAP);
    ::CloseClipboard ();
    }

    void CMyWindow::OnDestroyClipboard ()
    {
    // Delete the temporary file.
    }

    The idea is to save a copy of the bitmap to a file in OnEditCopy and re-create the bitmap from the file in OnRenderFormat. 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 a CClipboard 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.

    The OLE Clipboard

    The OLE Clipboard

    The OLE clipboard is a modern-day version of the legacy clipboard. It is also backward-compatible. Thanks to some magic built into the OLE libraries, you can put a text string, a bitmap, or some other item on the OLE clipboard and an application that knows nothing about OLE can paste that item just as if it had come from the legacy clipboard. Conversely, an application can use the OLE clipboard to retrieve data from the legacy clipboard.

    What's different about the OLE clipboard, and why is it superior to the old clipboard? There are two major differences between the two. First, the OLE clipboard is completely COM-based; all data is transferred by calling methods through pointers to COM interfaces. Second, the OLE clipboard supports storage media other than global memory. The legacy clipboard, in contrast, uses memory for all data transfers, which effectively limits the size of items transferred through the clipboard to the amount of memory available. Because of the legacy clipboard's inability to use media other than memory for data transfers, the compatibility between the legacy clipboard and the OLE clipboard is subject to the limitation that only items transferred through memory can be copied to one and retrieved from the other.

    The first reason alone isn't enough to justify forsaking the legacy clipboard. COM is trendy and objects are cool, but without mfc, code that interacts with the OLE clipboard is much more complex than legacy clipboard code. But the second reason—the freedom to use alternative storage media—is just cause to use the OLE clipboard. Transferring a 4-GB bitmap through the legacy clipboard is impossible because current versions of Windows don't support memory objects that large. With the OLE clipboard, however, you can transfer anything that will fit on your hard disk. In fact, with a little ingenuity, you can transfer anything at all—even items too large to fit on a hard disk. Given the huge volumes of information that many modern applications are forced to deal with, the OLE clipboard can be a very handy tool indeed.

    OLE Clipboard Basics

    The first and most fundamental notion to understand about the OLE clipboard is that when you place an item of data on it, you don't actually place the data itself. Instead, you place a COM data object that encapsulates the data. A data object is a COM object that implements the IDataObject interface. IDataObject has two methods that play key roles in the operation of the OLE clipboard: SetData and GetData. Assuming that the data object is a generic data repository (as opposed to an object that is custom-fit to handle a particular set of data), a data provider stuffs data into the data object with IDataObject::SetData. It then places the object on the OLE clipboard with ::OleSetClipboard. A data consumer calls ::OleGetClipboard to get the clipboard data object's IDataObject pointer, and then it calls IDataObject::GetData to retrieve the data.

    Figure 19-1 provides a conceptual look at OLE clipboard operations. This is a simplified view in that the IDataObject pointer returned by ::OleGetClipboard isn't really the IDataObject pointer that was passed to ::OleSetClipboard. Rather, it's a pointer to the IDataObject interface implemented by a system-provided clipboard data object that wraps the data object provided to ::OleSetClipboard and also allows consumers to access data on the legacy clipboard. Fortunately, this bit of indirection doesn't affect the code you write one iota. You simply use the IDataObject interface to interact with the data object. The system does the rest.

    Figure 19-1. Transferring data through the OLE clipboard.

    Using the OLE clipboard sounds reasonably simple, but nothing is simple when COM and OLE are involved. The hard part is writing the code for a data object and implementing not only IDataObject::GetData and IDataObject::SetData but also the other IDataObject methods. But first things first. Assuming that you've already implemented a data object and that pdo holds a pointer to the object's IDataObject interface, here's one way to place a text string on the OLE clipboard:

    // Copy the text string to a global memory block.
    char szText[] = "Hello, world";
    HANDLE hData = ::GlobalAlloc (GMEM_MOVEABLE, ::lstrlen (szText) + 1);
    LPSTR pData = (LPSTR) ::GlobalLock (hData);
    ::lstrcpy (pData, szText);
    ::GlobalUnlock (hData);
    

    // Initialize a FORMATETC structure and a STGMEDIUM structure that
    // describe the data and the location at which it's stored.
    FORMATETC fe;
    fe.cfFormat = CF_TEXT; // Clipboard format=CF_TEXT
    fe.ptd = NULL;// Target device=Screen
    fe.dwAspect = DVASPECT_CONTENT; // Level of detail=Full content
    fe.lindex = -1;// Index=Not applicable
    fe.tymed = TYMED_HGLOBAL; // Storage medium=Memory

    STGMEDIUM stgm;
    stgm.tymed = TYMED_HGLOBAL; // Storage medium=Memory
    stgm.hGlobal = hData; // Handle to memory block
    stgm.pUnkForRelease = NULL; // Use ReleaseStgMedium

    // Place the data object on the OLE clipboard.
    pdo->SetData (&fe, &stgm, FALSE);
    ::OleSetClipboard (pdo);
    pdo->Release ();

    The Release call in the final statement assumes that the application that created the data object has no more use for it after handing it off to the OLE clipboard. Calling Release on the data object won't cause the object to self-delete because ::OleSetClipboard performs an AddRef on the IDataObject pointer passed to it.

    Retrieving the text string is a little less work because we don't have to create a data object. But the process still isn't quite as straightforward as the one for retrieving a string from the legacy clipboard:

    char szText[BUFLEN];
    IDataObject* pdo;
    STGMEDIUM stgm;
    

    FORMATETC fe = {
    CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL
    };

    if (SUCCEEDED (::OleGetClipboard (&pdo))) {
    if (SUCCEEDED (pdo->GetData (&fe, &stgm) && stgm.hGlobal != NULL)) {LPCSTR pData = (LPCSTR) ::GlobalLock (stgm.hGlobal);if (::lstrlen (pData) < BUFLEN)
    ::lstrcpy (szText, pData);::GlobalUnlock (stgm.hGlobal);::ReleaseStgMedium (&stgm);
    }
    pdo->Release ();
    }

    If the data object can't provide the requested data, it returns an HRESULT signifying failure. The SUCCEEDED macro used in this example is the same one that we used to test HRESULTs in Chapter 18.

    Two structures play key roles in the operation of SetData and GetData: FORMATETC and STGMEDIUM. FORMATETC describes the format of the data and identifies the type of storage medium (for example, global memory block or file) that holds the data. Here's how FORMATETC is defined in Objidl.h:

    typedef struct  tagFORMATETC {
     CLIPFORMAT cfFormat;  // Clipboard format
     DVTARGETDEVICE *ptd;  // Target device
     DWORD dwAspect;  // Level of detail
     LONG lindex; // Page number or other index
     DWORD tymed; // Type of storage medium
    } FORMATETC;
    

    The two most important fields are cfFormat and tymed. cfFormat holds a clipboard format ID. The ID can be a standard clipboard format ID such as CF_TEXT or CF_BITMAP, or it can be a private clipboard format ID. tymed identifies the type of storage medium and can be any one of the values listed in the following table. Most OLE clipboard data transfers still use old-fashioned global memory blocks, but as you can plainly see, FORMATETC supports other media types as well.

    IDataObject Storage Media Types

    tymed Flag Storage Medium Type
    TYMED_HGLOBAL Global memory block
    TYMED_FILE File
    TYMED_ISTREAM Stream object (implements interface IStream)
    TYMED_ISTORAGE Storage object (implements interface IStorage)
    TYMED_GDI GDI bitmap
    TYMED_MFPICT Metafile picture
    TYMED_ENHMF GDI enhanced metafile

    FORMATETC identifies the storage medium type, but the STGMEDIUM structure identifies the storage medium itself. For example, if data is stored in a global memory block, the STGMEDIUM structure holds an HGLOBAL. If the data lives in a file instead, the STGMEDIUM holds a pointer to a character string that specifies the file name. STGMEDIUM holds other information as well. Here's how the structure is defined:

    typedef struct  tagSTGMEDIUM {
     DWORD tymed;
     union {HBITMAP hBitmap;// TYMED_GDIHMETAFILEPICT hMetaFilePict;  // TYMED_MFPICTHENHMETAFILE hEnhMetaFile;  // TYMED_ENHMFHGLOBAL hGlobal;// TYMED_HGLOBALLPOLESTR lpszFileName; // TYMED_FILEIStream *pstm;// TYMED_STREAMIStorage *pstg;// TYMED_STORAGE
     };
     IUnknown *pUnkForRelease;
    } STGMEDIUM;
    

    Here tymed holds a TYMED value that identifies the storage medium type, just as FORMATETC's tymed field does. hBitmap, hMetaFilePict, and other members of the embedded union identify the actual storage. Finally, pUnkForRelease holds a pointer to the COM interface whose Release method releases the storage medium. When an application retrieves an item from the OLE clipboard with IDataObject::GetData, that application is responsible for releasing the storage medium when it's no longer needed. For a memory block, "release" means to free the block; for a file, it means to delete the file. COM provides an API function named ::ReleaseStgMedium that an application can call to release a storage medium. If you simply set pUnkForRelease to NULL when you initialize a STGMEDIUM, ::ReleaseStgMedium will free the storage medium using logic that is appropriate for the storage medium type.

    There's much more that could be written about these data structures, but the description offered here should be enough to enable you to understand the examples in the previous section. The first example initialized a FORMATETC structure to describe an ANSI text string (cfFormat=CF_TEXT) stored in a global memory block (tymed=TYMED_HGLOBAL). It also wrapped the memory block with a STGMEDIUM (hGlobal=hData and tymed=TYMED_HGLOBAL). Both structures were passed by address to IDataObject::SetData.

    In the second example, a FORMATETC structure was initialized with the same parameters and the STGMEDIUM structure was left uninitialized. Both were passed to IDataObject::GetData to retrieve the text string. In this case, the parameters in the FORMATETC structure told the data object what kind of data and what type of storage medium the caller wanted. On return from IDataObject::GetData, the STGMEDIUM structure held the HGLOBAL through which the data could be accessed.

    By now, you're probably beginning to understand why programming the OLE clipboard is more involved than programming the legacy clipboard. You haven't seen the half of it yet, however, because I haven't shown the code for the data object. Remember, a data object is a COM object that implements the IDataObject interface. IDataObject is part of a COM-based data transfer protocol that Microsoft has christened Uniform Data Transfer, or UDT. I mentioned earlier that GetData and SetData are just two of the IDataObject methods you must wrestle with. The table below contains a complete list.

    IDataObject Methods

    Method Description
    GetData Retrieves data from the data object (object provides the storage medium)
    GetDataHere Retrieves data from the data object (caller provides the storage medium)
    QueryGetData Determines whether data is available in a particular format
    GetCanonicalFormatEtc Creates a different but logically equivalent FORMATETC
    SetData Provides data to the data object
    EnumFormatEtc Used to enumerate available data formats
    DAdvise Establishes an advisory connection to the data object
    DUnadvise Terminates an advisory connection
    EnumDAdvise Enumerates existing advisory connections

    You don't have to implement all these methods to perform a simple clipboard data transfer (some methods can simply return the special COM error code E_NOTIMPL), but implementing IDataObject is still a nontrivial task. Copying a simple text string to the legacy clipboard requires just a few lines of code. Copying the same text string to the OLE clipboard can require several hundred lines, primarily because of the added overhead of implementing a full-blown COM data object.

    If having to write hundreds of lines of code to copy a string to the clipboard seems silly, take heart. mfc greatly simplifies matters by providing the data object for you and by wrapping it in friendly C++ classes that hide the FORMATETC structures and the STGMEDIUM structures and other low-level nuts and bolts of the IDataObject interface. Generally speaking, using the OLE clipboard in an MFC application is no more difficult than using the legacy clipboard, particularly when you use global memory as the storage medium. And you retain the option of using files and other storage media as alternatives to global memory. All things considered, MFC's abstraction of the OLE clipboard is a big win for programmers. Let's see if you agree.

    MFC, Global Memory, and the OLE Clipboard

    MFC's OLE clipboard support is concentrated in two classes. The first, COleDataSource, models the provider side of clipboard operations. The second, COleDataObject, models the consumer side. In other words, you use COleDataSource to place data on the OLE clipboard and COleDataObject to retrieve it. Not surprisingly, COleDataSource contains a generic implementation of COM's IDataObject interface. You can see this implementation for yourself in the MFC source code file Oledobj2.cpp. If you're not familiar with the manner in which MFC classes implement COM interfaces, you might want to review Chapter 18 before reading the source code.

    Placing an item that's stored in global memory on the OLE clipboard is easy when you let COleDataSource do the dirty work. Here are the steps:

  • Create a COleDataSource object on the heap (not on the stack).
  • Call COleDataSource::CacheGlobalData to hand the HGLOBAL to the COleDataSource object.
  • Place the object on the OLE clipboard by calling COleDataSource::SetClipboard.
  • The following example uses COleDataSource to make an ANSI text string available through the OLE clipboard:

    char szText[] = "Hello, world"; // ANSI characters
    HANDLE hData = ::GlobalAlloc (GMEM_MOVEABLE, ::lstrlen (szText) + 1);
    LPSTR pData = (LPSTR) ::GlobalLock (hData);
    ::lstrcpy (pData, szText);
    ::GlobalUnlock (hData);
    

    COleDataSource* pods = new COleDataSource;
    pods->CacheGlobalData (CF_TEXT, hData);
    pods->SetClipboard ();

    Notice that the COleDataSource object is created on the heap, not on the stack. That fact is important because the object must remain in memory until a call to IUnknown::Release drops the data object's reference count to 0, at which time the object self-deletes. If you were to create the COleDataSource on the stack, the object would be deleted the moment it went out of scope.

    MFC's COleDataObject provides a handy mechanism for retrieving items from the OLE clipboard. Here's the procedure for retrieving an item stored in global memory:

  • Create a COleDataObject object.
  • Call COleDataObject::AttachClipboard to connect the COleDataObject to the OLE clipboard.
  • Use COleDataObject::GetGlobalData to retrieve the item.
  • Free the global memory block returned by GetGlobalData.
  • And here's how the text string placed on the OLE clipboard in the previous example is retrieved using COleDataObject:

    char szText[BUFLEN];
    COleDataObject odo;
    odo.AttachClipboard ();
    HANDLE hData = odo.GetGlobalData (CF_TEXT);
    

    if (hData != NULL) {
    LPCSTR pData = (LPCSTR) ::GlobalLock (hData);
    if (::lstrlen (pData) < BUFLEN)::lstrcpy (szText, pData);
    ::GlobalUnlock (hData);
    ::GlobalFree (hData);
    }

    The AttachClipboard function creates a logical connection between a COleDataObject and the OLE clipboard. Once the connection is made, MFC transforms calls to GetGlobalData and other COleDataObject data retrieval functions into GetData calls through the IDataObject pointer returned by ::OleGetClipboard. Don't forget that it's your responsibility to free the global memory block returned by GetGlobalData. That requirement explains the call to ::GlobalFree in the preceding example.

    Using Alternative Storage Media

    All the examples presented so far in this chapter have used global memory as the transfer medium. But remember that the OLE clipboard supports other media types, too. COleDataSource::CacheGlobalData and COleDataObject::GetGlobalData are hardwired to use global memory blocks. You can use the more generic COleDataSource::CacheData and COleDataObject::GetData functions to transfer data in other types of storage media.

    The next example demonstrates how to transfer a text string through the OLE clipboard using a file as the transfer medium. The string is first copied into a temporary file. Then FORMATETC and STGMEDIUM structures are initialized with information describing the file and the data that it contains. Finally, the information is passed to COleDataSource::CacheData, and the data object is placed on the OLE clipboard with COleDataSource::SetClipboard:

    char szText[] = "Hello, world";
    TCHAR szPath[MAX_PATH], szFileName[MAX_PATH];
    ::GetTempPath (sizeof (szPath) / sizeof (TCHAR), szPath);
    ::GetTempFileName (szPath, _T ("tmp"), 0, szFileName);
    

    CFile file;
    if (file.Open (szFileName, CFile::modeCreate | CFile::modeWrite)) {
    file.Write (szText, ::lstrlen (szText) + 1);
    file.Close ();LPWSTR pwszFileName =(LPWSTR) ::CoTaskMemAlloc (MAX_PATH * sizeof (WCHAR));

    #ifdef UNICODE
    ::lstrcpy (pwszFileName, szFileName);
    #else
    ::MultiByteToWideChar (CP_ACP, MB_PRECOMPOSED, szFileName, -1,pwszFileName, MAX_PATH);
    #endifFORMATETC fe = {CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_FILE
    };STGMEDIUM stgm;
    stgm.tymed = TYMED_FILE;
    stgm.lpszFileName = pwszFileName;
    stgm.pUnkForRelease = NULL;COleDataSource* pods = new COleDataSource;
    pods->CacheData (CF_TEXT, &stgm, &fe);
    pods->SetClipboard ();
    }

    The file name whose address is copied to the STGMEDIUM structure prior to calling CacheData must be composed of Unicode characters. This is always true, even in Windows 98. You must also allocate the file name buffer using the COM function ::CoTaskMemAlloc. Among other things, this ensures that the buffer is properly freed when ::ReleaseStgMedium calls ::CoTaskMemFree on the buffer pointer.

    On the consumer side, you can use COleDataObject::GetData to retrieve the string from the clipboard:

    char szText[BUFLEN];
    STGMEDIUM stgm;
    

    FORMATETC fe = {
    CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_FILE
    };

    COleDataObject odo;
    odo.AttachClipboard ();

    if (odo.GetData (CF_TEXT, &stgm, &fe) && stgm.tymed == TYMED_FILE) {
    TCHAR szFileName[MAX_PATH];

    #ifdef UNICODE
    ::lstrcpy (szFileName, stgm.lpszFileName);
    #else
    ::WideCharToMultiByte (CP_ACP, 0, stgm.lpszFileName,-1, szFileName, sizeof (szFileName) / sizeof (TCHAR), NULL, NULL);
    #endifCFile file;
    if (file.Open (szFileName, CFile::modeRead)) {DWORD dwSize = file.GetLength ();if (dwSize < BUFLEN)
    file.Read (szText, (UINT) dwSize);file.Close ();
    }
    ::ReleaseStgMedium (&stgm);
    }

    When you retrieve data with COleDataObject::GetData, you are responsible for freeing the storage medium, which is why ::ReleaseStgMedium is called in the final statement of this example.

    Of course, transferring small text strings through files rather than global memory blocks doesn't make much sense. If the item being transferred is a large bitmap, however, such a transfer might make a lot of sense—especially if the bitmap is already stored on disk somewhere. I used text strings in this section's examples to make the code as simple and uncluttered as possible, but the principle represented here applies to data of all types.

    Treating the OLE Clipboard as a CFile

    MFC's COleDataObject::GetFileData function provides a handy abstraction of the OLE clipboard that enables data stored in any of the following storage media to be retrieved as if the clipboard were an ordinary CFile:

    • TYMED_HGLOBAL
    • TYMED_FILE
    • TYMED_MFPICT
    • TYMED_ISTREAM

    If successful, GetFileData returns a pointer to a CFile object that wraps the item retrieved from the clipboard. You can call CFile::Read through that pointer to read the data out.

    The following example demonstrates how to use GetFileData to retrieve a string from the OLE clipboard:

    char szText[BUFLEN];
    COleDataObject odo;
    odo.AttachClipboard ();
    

    CFile* pFile = odo.GetFileData (CF_TEXT);

    if (pFile != NULL) {
    DWORD dwSize = pFile->GetLength ();
    if (dwSize < BUFLEN)pFile->Read (szText, (UINT) dwSize);
    delete pFile; // Don't forget this!
    }

    Again, notice that you are responsible for deleting the CFile object whose address is returned by GetFileData. If you forget to delete it, you'll suffer memory leaks.

    The code above is the functional equivalent of the GetData example presented in the previous section, but with two added benefits. One, it's simpler. Two, it works whether data is stored in a global memory block, a file, or a stream. In other words, one size fits all. To get the same results with GetData, you'd have to do something like this:

    char szText[BUFLEN];
    STGMEDIUM stgm;
    

    COleDataObject odo;
    odo.AttachClipboard ();

    FORMATETC fe = {
    CF_TEXT, NULL, DVASPECT_CONTENT, -1,
    TYMED_FILE | TYMED_HGLOBAL | TYMED_ISTREAM
    };

    if (odo.GetData (CF_TEXT, &stgm, &fe)) {
    switch (stgm.tymed) {case TYMED_FILE:// Read the string from a file.
    .
    .
    .break;case TYMED_HGLOBAL:// Read the string from a global memory block.
    .
    .
    .break;case TYMED_ISTREAM:// Read the string from a stream object.
    .
    .
    .break;
    }
    ::ReleaseStgMedium (&stgm);
    }

    Notice the use of multiple TYMED flags in the FORMATETC structure passed to GetData. TYMED flags can be OR'd together in this manner to inform a data object that the caller will accept data in a variety of different storage media.

    Multiple Formats and Multiple Storage Media

    A data provider can call CacheData or CacheGlobalData as many times as necessary to make data available to data consumers in a variety of formats. The following code offers an item in two formats: a private format registered with ::RegisterClipboardFormat (nFormat) and a CF_TEXT format:

    COleDataSource* pods = new COleDataSource;
    pods->CacheGlobalData (nFormat, hPrivateData);
    pods->CacheGlobalData (CF_TEXT, hTextData);
    pods->SetClipboard ();
    

    You can also make multiple data items available in the same format but in different storage media. Suppose you want to make CF_TEXT data available ineither a global memory block or a file. Assuming that pwszFileName has already been initialized to point to a file name (expressed in Unicode characters), here's how you go about it:

    FORMATETC fe = {
     CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_FILE
    };
    

    STGMEDIUM stgm;
    stgm.tymed = TYMED_FILE;
    stgm.lpszFileName = pwszFileName;
    stgm.pUnkForRelease = NULL;

    COleDataSource* pods = new COleDataSource;
    pods->CacheGlobalData (CF_TEXT, hTextData); // TYMED_HGLOBAL
    pods->CacheData (CF_TEXT, &stgm, &fe); // TYMED_FILE
    pods->SetClipboard ();

    Calling CacheData and CacheGlobalData more than once and then placing the data object on the clipboard is analogous to calling ::SetClipboardData multiple times to place two or more formats on the legacy clipboard. However, the legacy clipboard won't accept two items that are of the same format. The OLE clipboard will—as long as each FORMATETC structure has a unique tymed value, which is another way of saying that the items are stored indifferent types of storage media.

    Checking Data Availability

    The API function ::IsClipboardFormatAvailable allows users of the legacy clipboard to find out whether data is available in a certain format. COleDataObject::IsDataAvailable lets OLE clipboard users do the same. The following code fragment checks to see whether CF_TEXT data is available in an HGLOBAL:

    COleDataObject odo;
    odo.AttachClipboard ();
    if (odo.IsDataAvailable (CF_TEXT)) {
     // CF_TEXT is available in an HGLOBAL.
    }
    else {
     // CF_TEXT is not available in an HGLOBAL.
    }
    

    To check for storage media types other than global memory, you simply initialize a FORMATETC structure and pass its address to IsDataAvailable, as shown here:

    COleDataObject odo;
    odo.AttachClipboard ();
    

    FORMATETC fe = {
    CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_ISTREAM
    };

    if (odo.IsDataAvailable (CF_TEXT, &fe)) {
    // CF_TEXT is available in a stream object.
    }
    else {
    // CF_TEXT is not available in a stream object.
    }

    If you want to, you can OR several TYMED flags into the tymed field of the FORMATETC structure passed to IsDataAvailable. The return value will be nonzero if data is available in any of the requested storage media.

    NOTE As a result of a bug in MFC 6.0, COleDataObject::IsDataAvailable sometimes returns a nonzero value if the requested data is available in any storage medium. In effect, the media type information passed to IsDataAvailable in a FORMATETC structure is ignored. Significantly, the bug manifests itself only if IsDataAvailable is called on a COleDataObject that's attached to the OLE clipboard, and it affects some data types (notably CF_TEXT data) more than others. IsDataAvailable works as advertised when COleDataObject is used to implement an OLE drop target.

    Data consumers can use the COleDataObject functions BeginEnumFormats and GetNextFormat to enumerate the various formats available. The following code fragment enumerates all the formats available on the OLE clipboard:

    COleDataObject odo;
    odo.AttachClipboard ();
    

    FORMATETC fe;
    odo.BeginEnumFormats ();
    while (odo.GetNextFormat (&fe)) {
    // FORMATETC structure describes the next available format.
    }

    If a particular data format is available in two or more types of storage media, GetNextFormat is supposed to either initialize the FORMATETC structure's tymed field with bit flags identifying each storage medium type or return a unique FORMATETC structure for each tymed. However, an interesting (and potentially aggravating) anomaly can occur. If the OLE clipboard contains two data items with identical cfFormats but different tymeds, GetNextFormat will return information for only one of them. This appears to be a bug in the system-supplied clipboard data objectwhose IDataObject pointer is returned by ::OleGetClipboard. If you need to know what media types a given clipboard format is available in, use IsDataAvailable to query for individual combinations of clipboard formats and storage media.

    Delayed Rendering with COleDataSource

    Does the OLE clipboard support delayed rendering? The short answer is yes, although in truth, MFC's implementation of COleDataSource, not the OLE clipboard, makes delayed rendering work. A glimpse under the hood of COleDataSource explains why.

    A COleDataSource object is first and foremost a data cache. Internally, it maintains an array of FORMATETC and STGMEDIUM structures that describe the data that is currently available. When an application calls CacheData or CacheGlobalData, a STGMEDIUM structure with a tymed value that describes the storage medium type is added to the array. If an application calls DelayRenderData instead, a STGMEDIUM structure that contains a NULL tymed value is added to the array. When asked to retrieve that data, the COleDataSource sees the NULL tymed value and knows that the data was promised via delayed rendering. COleDataSource responds by calling a virtual function named OnRenderData. Your job is to override this function in a derived class so that you can provide the data on request.

    Here's an example that demonstrates how to place a bitmap on the OLE clipboard using delayed rendering. The first step is to make a copy of the bitmap and store it in a file. (You could store it in memory, but that might defeat the purpose of using delayed rendering in the first place.) The second step is to call DelayRenderData:

    FORMATETC fe = {
     CF_BITMAP, NULL, DVASPECT_CONTENT, -1, TYMED_GDI
    };
    

    CMyDataSource* pmds = new CMyDataSource;
    pmds->DelayRenderData (CF_BITMAP, &fe);
    pmds->SetClipboard ();

    CMyDataSource is a COleDataSource derivative. Here's the OnRenderData function that renders the bitmap to a TYMED_GDI storage medium when the data source is asked to hand over the bitmap:

    BOOL CMyDataSource::OnRenderData (LPFORMATETC lpFormatEtc,
     LPSTGMEDIUM lpStgMedium) 
    {
     if (COleDataSource::OnRenderData (lpFormatEtc, lpStgMedium))return TRUE;if (lpFormatEtc->cfFormat == CF_BITMAP &&lpFormatEtc->tymed & TYMED_GDI) { // Re-create the bitmap from the file, and store the// handle in hBitmap..
    .
    .lpFormatEtc->cfFormat = CF_BITMAP;lpFormatEtc->ptd = NULL;lpFormatEtc->dwAspect = DVASPECT_CONTENT;lpFormatEtc->lindex = -1;lpFormatEtc->tymed = TYMED_GDI; lpStgMedium->tymed = TYMED_GDI;lpStgMedium->hBitmap = hBitmap;lpStgMedium->pUnkForRelease = NULL; CacheData (CF_BITMAP, lpStgMedium, lpFormatEtc);return TRUE;
     }
     return FALSE;
    }
    

    Other than the fact that you have to derive a class and override OnRenderData, delayed rendering with a COleDataSource isn't much different from immediate rendering.

    Other COleDataSource functions can sometimes simplify the delayed rendering code that you write. For example, if you intend to render data only to HGLOBAL storage media, you can override OnRenderGlobalData instead of OnRenderData. You can use a separate set of COleDataSource functions named DelayRenderFileData and OnRenderFileData functions to delay-render data using CFile output functions.

    One detail to be aware of when you use, COleDataSource delayed rendering is that if the storage type is TYMED_HGLOBAL, TYMED_FILE, TYMED_ISTREAM, or TYMED_ISTORAGE, the storage medium might be allocated before OnRenderData is called. If the storage medium is preallocated, OnRenderData must render the data into the existing storage medium rather than create a new storage medium itself. The tymed value in the STGMEDIUM structure whose address is passed to OnRenderData tells the tale. If lpStgMedium->tymed is TYMED_NULL, OnRenderData is responsible for allocating the storage medium. If lpStgMedium->tymed holds any other value, the caller has supplied the storage medium and lpStgMedium->tymed identifies the storage type. The following code sample demonstrates proper handling of OnRenderData for media types that are subject to preallocation:

    BOOL CMyDataSource::OnRenderData (LPFORMATETC lpFormatEtc,
     LPSTGMEDIUM lpStgMedium) 
    {
     if (COleDataSource::OnRenderData (lpFormatEtc, lpStgMedium))return TRUE;if (lpStgMedium->tymed == TYMED_NULL) { // Medium is not preallocated.if (lpFormatEtc->tymed & TYMED_HGLOBAL) {
    // Allocate a global memory block, render the data
    // into it, and then copy the handle to lpStgMedium->hGlobal.}
     }
     else { // Medium is preallocated.if (lpStgMedium->tymed == TYMED_HGLOBAL) {
    // Render the data into the global memory block whose
    // handle is stored in lpStgMedium->hGlobal.}
     }
    }
    

    This example addresses only the case in which the storage medium is an HGLOBAL, but the principle should be clear nonetheless.

    The most common reason for using COleDataSource's brand of delayed rendering is to provide data in a variety of storage media without having to allocate each and every storage medium up front. If you're willing to provide, say, CF_TEXT data in several different media, you can call DelayRenderData and pass in a FORMATETC structure whose tymed field contains bit flags representing each of the media types that you support. Then you can render the data in any medium that the data consumer requests by inspecting the tymed field of the FORMATETC structure passed to OnRenderData. If the consumer asks for the data in a medium that you don't support, you can simply fail the call to OnRenderData by returning FALSE.

    COleDataSource and COleDataObject in Review

    You now know how to use MFC's COleDataSource and COleDataObject classes to interact with the OLE clipboard. Just to put things in perspective (and to reinforce what you've already learned), the following tables provide a brief summary of the most useful COleDataSource and COleDataObject member functions. These classes have other functions as well, but those listed here are the ones that you're most likely to need.

    Key COleDataSource Member Functions

    Function Description
    SetClipboardPlaces the COleDataSource on the OLE clipboard
    CacheDataProvides data to the COleDataSource
    CacheGlobalData Provides data stored in global memory to the COleDataSource
    DelayRenderDataOffers data for delayed rendering
    DelayRenderFileDataOffers data for delayed rendering using CFile output functions
    OnRenderDataCalled to render data to an arbitrary storage medium
    OnRenderFileDataCalled to render data to a CFile
    OnRenderGlobalDataCalled to render data to an HGLOBAL

    Key COleDataObject Member Functions

    FunctionDescription
    AttachClipboardAttaches the COleDataObject to the OLE clipboard
    GetData Retrieves data from the data object to which the COleDataObject is attached
    GetFileData Retrieves data using CFile functions
    GetGlobalData Retrieves data in an HGLOBAL
    IsDataAvailable Determines whether data is available in a particular format and storage medium
    BeginEnumFormats Begins the process of enumerating available data formats
    GetNextFormat Fills a FORMATETC structure with information describing the next available data format

    Earlier, I said that the primary reason to use the OLE clipboard is to gain the ability to use storage media other than global memory. That's true, but there's another reason, too. Thanks to the abstractions offered by COleDataSource and COleDataObject, once you write MFC code to utilize the OLE clipboard, you only have to do a little more work to add support for an even more convenient form of data transfer: OLE drag-and-drop. OLE drag-and-drop lets the user transfer data by grabbing it with the mouse and dragging it. Writing OLE drag-and-drop code without a class library to help out isn't any fun, but MFC makes the process as hassle-free as possible.

    OLE Drag-and-Drop

    OLE Drag-and-Drop

    If you've never seen OLE drag-and-drop in action, you can perform a simple demonstration using the source code editor in Visual C++. Begin by opening a source code file and highlighting a line of text. Grab the highlighted text with the left mouse button, and with the button held down, drag it down a few lines. Then release the mouse button. The text will disappear from its original location and appear where you dropped it, just as if you had performed a cut-and-paste operation. Repeat the operation with the Ctrl key held down, and the text will be copied rather than moved. That's OLE drag-and-drop. You used it to transfer text from one part of a document to another, but it works just as well if the destination is a different document or even a different application. And just as with the OLE clipboard, you can use OLE drag-and-drop to transfer any kind of data—not just text.

    Programmatically, OLE drag-and-drop is very similar to the OLE clipboard. The data provider, or drop source, creates a data object that encapsulates the data and makes an IDataObject pointer available. The data consumer, or drop target, retrieves the IDataObject pointer and uses it to extract data from the data object.

    One difference between OLE drag-and-drop and the OLE clipboard is how the IDataObject pointer changes hands. The OLE clipboard uses ::OleSetClipboard and ::OleGetClipboard to transfer the pointer from sender to receiver. In OLE drag-and-drop, the drop source initiates a drag-and-drop operation by passing an IDataObject pointer to ::DoDragDrop. On the other end, any window interested in being a drop target registers itself with the system by calling the API function ::RegisterDragDrop. If a drop occurs over a window that's registered in this way, the drop target is handed the IDataObject pointer passed to ::DoDragDrop.

    If that's all there was to it, OLE drag-and-drop wouldn't be difficult at all. What complicates matters is that OLE drag-and-drop requires three COM objects instead of just one:

    • A data object that implements IDataObject
    • A drop source object that implements IDropSource
    • A drop target object that implements IDropTarget

    The data object is identical to the one used for OLE clipboard transfers. The drop source and drop target objects are new. Figure 19-2 shows a schematic representation of the participants in a drag-and-drop data transfer. On the sending end of the transaction is an application that implements two COM objects: a data object and a drop source object. (There's nothing to prevent one object from supporting both interfaces, but in practice, the objects are usually implemented separately.) On the receiving end is an application that implements a drop target object. Neither the drop source nor the drop target receives an IDropSource or IDropTarget pointer that references the other. Instead, the system acts as an intermediary and calls methods on both interfaces at the appropriate times.

    Figure 19-2. Participants in an OLE drag-and-drop operation.

    Anatomy of a Drop Source

    An OLE drag-and-drop operation begins when an application calls ::DoDragDrop and passes in four key pieces of information:

    • An IDataObject pointer
    • An IDropSource pointer
    • An input value containing one or more DROPEFFECT codes specifying what types of operations are allowed on the data (for example, move, copy, or both move and copy)
    • A pointer to a DWORD that receives a DROPEFFECT code specifying what happened on the other end (for example, did a drop occur, and if it did, was the data moved or copied?)

    ::DoDragDrop returns when either of two conditions is met:

    • A drop occurs.
    • The operation is canceled.

    The action that cancels a drag-and-drop operation varies from application to application and is ultimately determined by the drop source. In most cases, the stimulus is a press of the Esc key. If the operation is canceled or the drop target rejects the drop, ::DoDragDrop copies the value DROPEFFECT_NONE to the address in the fourth parameter. If the drop is successful, ::DoDragDrop copies one of the DROPEFFECT codes passed in the third parameter to the address in the fourth parameter so that the drop source will know precisely what occurred.

    Assume that pdo and pds hold IDataObject and IDropSource pointers, respectively. The following statements initiate a drag-and-drop operation in which the data encapsulated in the data object can be either moved or copied:

    DWORD dwEffect;
    HRESULT hr = ::DoDragDrop (pdo, pds,
     DROPEFFECT_MOVE | DROPEFFECT_COPY, &dwEffect);
    

    When ::DoDragDrop returns, dwEffect tells the drop source what transpired on the other end. If dwEffect equals DROPEFFECT_NONE or DROPEFFECT_COPY, the drop source doesn't need to do anything more. If dwEffect equals DROPEFFECT_MOVE, however, the drop source must delete the data from the source document:

    if (SUCCEEDED (hr) && dwEffect == DROPEFFECT_MOVE) {
     // Delete the original data from the document.
    }
    

    The code that deletes the data isn't shown because, obviously, it's application-specific.

    Calls to ::DoDragDrop are synchronous; that is, ::DoDragDrop doesn't return until the operation has been completed or canceled. However, as a drag-and-drop operation is being performed, the system communicates with the drop source through the IDropSource pointer provided to ::DoDragDrop. IDropSource is a simple interface that contains just two methods besides the IUnknown methods common to all COM interfaces:

    IDropSource Methods

    MethodDescription
    GiveFeedback Called each time the cursor moves or a key state changes to allow the drop source to update the cursor
    QueryContinueDragCalled when a key state or mouse button state changes to allow the drop source to specify whether to continue the operation, cancel it, or execute a drop

    Whenever a change occurs in the state of a key or mouse button that might be of interest to the drop source, the drop source object's QueryContinueDrag method is called. QueryContinueDrag receives two parameters: a BOOL indicating whether the Esc key has been pressed and a DWORD containing flags that reflect the current state of the mouse buttons as well as the Ctrl, Alt, and Shift keys. Using this information, QueryContinueDrag must return one of three values telling the system what to do next:

    Return ValueDescription
    S_OKContinue the drag-and-drop operation
    DRAGDROP_S_DROPEnd the operation by executing a drop
    DRAGDROP_S_CANCELCancel the drag-and-drop operation

    Typical responses are to cancel the operation if the Esc key has been pressed, to execute a drop if the left mouse button has been released, or to allow the operation to continue if neither of the first two conditions is true. The following QueryContinueDrag implementation embodies this logic:

    HRESULT __stdcall CDropSource::QueryContinueDrag (BOOL fEscape,
     DWORD grfKeyState)
    {
     if (fEscape)return DRAGDROP_S_CANCEL;  // Esc key was pressed.
     if (!(grfKeyState & MK_LBUTTON))return DRAGDROP_S_DROP; // Left mouse button was released.
     return S_OK;  // Let the operation continue.
    }
    

    This code assumes that the drag-and-drop operation began when the left mouse button was depressed. If you're implementing right-button drag instead, check the right mouse button (MK_RBUTTON) to determine whether to execute the drop. If you prefer to use a key other than Esc to cancel the operation, you can call ::GetAsyncKeyState to read the key's state and use that value rather than fEscape to decide whether to return DRAGDROP_S_CANCEL.

    As a drag-and-drop operation unfolds, the drop source receives a flurry of calls to its IDropSource::GiveFeedback method. GiveFeedback receives one function parameter: a DROPEFFECT code that tells the drop source what would happen if a drop were to occur right now. (As you'll see in the next section, this information comes from the drop target because ultimately it's the drop target that controls what happens on the other end.) GiveFeedback's job is to inspect this parameter and update the cursor to provide visual feedback to the user. When you see the cursor change shape as it moves from window to window during a drag-and-drop data transfer or when you see a little plus sign appear next to the cursor when the Ctrl key is pressed, what you're actually seeing is the drop source's response to IDropSource::GiveFeedback.

    If you want to, you can create your own cursors and display them each time GiveFeedback is called; however, the system provides several predefined cursors for just this purpose. To use them, simply return DRAGDROP_S_USEDEFAULTCURSORS from your GiveFeedback implementation. Rather than do this:

    HRESULT __stdcall CDropSource::GiveFeedback (DWORD dwEffect)
    {
     HCURSOR hCursor;
     switch (dwEffect) {// Inspect dwEffect, and load a cursor handle in hCursor.
     }
     ::SetCursor (hCursor);
     return S_OK;
    }
    

    you can do this:

    HRESULT __stdcall CDropSource::GiveFeedback (DWORD dwEffect)
    {
     return DRAGDROP_S_USEDEFAULTCURSORS;
    }
    

    That's all there is to most implementations of IDropSource::GiveFeedback. You can do more if you'd like, but you might as well use the default cursors unless you have compelling reasons to do otherwise.

    Anatomy of a Drop Target

    A window becomes an OLE drop target when an application calls ::RegisterDragDrop and passes in the window's handle and a pointer to an IDropTarget interface:

    ::RegisterDragDrop (hWnd, pdt);
    

    You unregister a drop target by calling ::RevokeDragDrop. Although the system will clean up after you if you fail to call this function before a drop target window is destroyed, calling it yourself is good form.

    When the cursor enters, leaves, or moves over a drop target window during a drag-and-drop operation, the system apprises the drop target of that fact by calling IDropTarget methods through the IDropTarget pointer provided to ::RegisterDragDrop. IDropTarget has just the four methods listed in the following table.

    IDropTarget Methods

    MethodDescription
    DragEnterCalled when the cursor enters the drop target window
    DragOver Called as the cursor moves over the drop target window
    DragLeave Called when the cursor leaves the drop target window or if the operation is canceled while the cursor is over the window
    Drop Called when a drop occurs

    Both DragEnter and DragOver receive a pointer to a DWORD (among other things) in their parameter lists. When either of these methods is called, the drop target must let the drop source know what would happen if a drop were to occur by copying a DROPEFFECT value to the DWORD. The value copied to the DWORD is the value passed to the drop source's GiveFeedback method. DragEnter and DragOver also receive a set of cursor coordinates (in case the outcome of a drop depends on the current cursor position) and flags that specify the status of the Ctrl, Alt, and Shift keys and each of the mouse buttons. In addition, DragEnter receives an IDataObject pointer that it can use to query the data object. The following implementations of DragEnter and DragOver return DROPEFFECT_NONE, DROPEFFECT_MOVE, or DROPEFFECT_COPY to the data source depending on whether text is available from the data object and whether the Ctrl key is up (move) or down (copy):

    HRESULT __stdcall CDropTarget::DragEnter (IDataObject* pDataObject,
     DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
     FORMATETC fe = {CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL
     };if (pDataObject->QueryGetData (&fe) == S_OK) {m_bCanAcceptData = TRUE;*pdwEffect = (grfKeyState & MK_CONTROL) ?
    DROPEFFECT_COPY : DROPEFFECT_MOVE;
     }
     else {m_bCanAcceptData = FALSE;*pdwEffect = DROPEFFECT_NONE;
     }
     return S_OK;
    }
    

    HRESULT __stdcall CDropTarget::DragOver (DWORD grfKeyState,
    POINTL pt, DWORD* pdwEffect)
    {
    if (m_bCanAcceptData)pdwEffect = (grfKeyState & MK_CONTROL) ?
    DROPEFFECT_COPY : DROPEFFECT_MOVE;
    else
    pdwEffect = DROPEFFECT_NONE;
    return S_OK;
    }

    m_bCanAcceptData is a BOOL member variable that keeps a record of whether the data offered by the drop source is in a format that the drop target will accept. When DragOver is called, the drop target uses this value to determine whether to indicate that it's willing to accept a drop.

    The drop target's DragLeave method is called if the cursor leaves the drop target window without executing a drop or if the drag-and-drop operation is canceled while the cursor is over the drop target window. The call to DragLeave gives the drop target the opportunity to clean up after itself by freeing any resources allocated in DragEnter or DragOver if the anticipated drop doesn't occur.

    The final IDropTarget method, Drop, is called if (and only if) a drop occurs. Through its parameter list, Drop receives all the information it needs to process the drop, including an IDataObject pointer; a DWORD that specifies the state of the Ctrl, Alt, and Shift keys and the mouse buttons; and cursor coordinates. It also receives a DWORD pointer to which it must copy a DROPEFFECT value that informs the data source what happened as a result of the drop. The following Drop implementation retrieves a text string from the data object, provided that a text string is available:

    HRESULT __stdcall CDropTarget::Drop (IDataObject* pDataObject,
     DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
     if (m_bCanAcceptData) {FORMATETC fe = {
    CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM stgm; if (SUCCEEDED (pDataObject->GetData (&fe, &stgm)) &&
    stgm.hGlobal != NULL) {
    // Copy the string from the global memory block.
     .
     .
     .::ReleaseStgMedium (&stgm);
    *pdwEffect = (grfKeyState & MK_CONTROL) ?
     DROPEFFECT_COPY : DROPEFFECT_MOVE;
    return S_OK;}
     }
     // If we make it to here, the drop did not succeed.
    *pdwEffect = DROPEFFECT_NONE;
     return S_OK;
    }
    

    A call to Drop isn't followed by a call to DragLeave, so if there's any cleaning up to do after the drop is completed, the Drop method should do it.

    mfc Support for OLE Drag-and-Drop

    Most of the work in writing OLE drag-and-drop code lies in implementing the COM objects. Fortunately, mfc will implement them for you. The same COleDataSource class that provides data objects for OLE clipboard operations works with OLE drag-and-drop, too. COleDropSource provides a handy implementation of the drop source object, and COleDropTarget provides the drop target object. Very often, you don't even have to instantiate COleDropSource yourself because COleDataSource does it for you. You will have to instantiate COleDropTarget, but you usually do that simply by adding a COleDropTarget member variable to the application's view class.

    Suppose you'd like to transfer a text string using OLE drag-and-drop in an MFC application. Here's how to do it using a global memory block as the storage medium:

    char szText[] = "Hello, world";
    HANDLE hData = ::GlobalAlloc (GMEM_MOVEABLE, ::lstrlen (szText) + 1)
    LPSTR pData = (LPSTR) ::GlobalLock (hData);
    ::lstrcpy (pData, szText);
    ::GlobalUnlock (hData);
    

    COleDataSource ods;
    ods.CacheGlobalData (CF_TEXT, hData);

    DROPEFFECT de =
    ods.DoDragDrop (DROPEFFECT_MOVE | DROPEFFECT_COPY)

    if (de == DROPEFFECT_MOVE) {
    // Delete the string from the document.
    }

    This code is strikingly similar to the code presented earlier in this chapter that used COleDataSource to place a text string on the OLE clipboard. Other than the fact that the COleDataSource object is created on the stack rather than on the heap (which is correct because, in this case, the object doesn't need to outlive the function that created it), the only real difference is that COleDataSource::DoDragDrop is called instead of COleDataSource::SetClipboard. COleDataSource::DoDragDrop is a wrapper around the API function of the same name. In addition to calling ::DoDragDrop for you, it also creates the COleDropSource object whose IDropSource interface pointer is passed to ::DoDragDrop.

    If you'd rather create your own COleDropSource object, you can do so and pass it by address to COleDataSource::DoDragDrop in that function's optional third parameter. The only reason to create this object yourself is if you want to derive a class from COleDropSource and use it instead of COleDropSource. Programmers occasionally derive from COleDropSource and override its GiveFeedback and QueryContinueDrag member functions to provide custom responses to the IDropSource methods of the same names.

    MFC makes acting as a target for OLE drag-and-drop data transfers relatively easy, too. The first thing you do is add a COleDropTarget data member to the application's view class:

    // In CMyView's class declaration
    COleDropTarget m_oleDropTarget;
    

    Then, in the view's OnCreate function, you call COleDropTarget::Register and pass in a pointer to the view object:

    m_oleDropTarget.Register (this);
    

    Finally, you override the view's OnDragEnter, OnDragOver, OnDragLeave, and OnDrop functions or some combination of them. These CView functions are coupled to the similarly named IDropTarget methods. For example, when the drop target object's IDropTarget::Drop method is called, COleDropTarget::OnDrop calls your view's OnDrop function. To respond to calls to IDropTarget::Drop, you simply override CView::OnDrop.

    Here's an example that demonstrates how to override OnDragEnter, OnDragOver, and OnDrop in a CScrollView-derived class to make the view a drop target for text. OnDragLeave isn't overridden in this example because nothing special needs to be done when it's called. Notice that a preallocated COleDataObject is provided in each function's parameter list. This COleDataObject wraps the IDataObject pointer passed to the drop target's IDropTarget methods:

    DROPEFFECT CMyView::OnDragEnter (COleDataObject* pDataObject,
     DWORD dwKeyState, CPoint point)
    {
     CScrollView::OnDragEnter (pDataObject, dwKeyState, point);
     if (!pDataObject->IsDataAvailable (CF_TEXT))return DROPEFFECT_NONE;
     return (dwKeyState & MK_CONTROL) ?DROPEFFECT_COPY : DROPEFFECT_MOVE;
    }
    

    DROPEFFECT CMyView::OnDragOver (COleDataObject* pDataObject,
    DWORD dwKeyState, CPoint point)
    {
    CScrollView::OnDragOver (pDataObject, dwKeyState, point);
    if (!pDataObject->IsDataAvailable (CF_TEXT))return DROPEFFECT_NONE;
    return (dwKeyState & MK_CONTROL) ?DROPEFFECT_COPY : DROPEFFECT_MOVE;
    }

    BOOL CMyView::OnDrop (COleDataObject* pDataObject, DROPEFFECT dropEffect,
    CPoint point)
    {
    CScrollView::OnDrop (pDataObject, dropEffect, point);
    HANDLE hData = pDataObject->GetGlobalData (CF_TEXT);
    if (hData != NULL) {// Copy the string from the global memory block. ...::GlobalFree (hData);return TRUE; // Drop succeeded.
    }
    return FALSE; // Drop failed.
    }

    This code looks a lot like the non-MFC version presented in the previous section. OnDragEnter and OnDragOver call COleDataObject::IsDataAvailable through the pointer provided in their parameter lists to determine whether text is available. If the answer is no, both functions return DROPEFFECT_NONE to indicate that they won't accept the drop. The drop source, in turn, will probably display a "no-drop" cursor. If text is available, OnDragEnter and OnDragOver return either DROPEFFECT_MOVE or DROPEFFECT_COPY, depending on whether the Ctrl key is down. OnDrop uses COleDataObject::GetGlobalData to retrieve the data when a drop occurs.

    Drop Target Scrolling

    The examples in the previous section assume that the drop target is a view-based application. You can use COleDropTarget to implement drop targeting in applications that don't have views by deriving your own class from COleDropTarget and overriding OnDragEnter, OnDragOver, OnDragLeave, and OnDrop. However, using a view as a drop target offers one very attractive benefit if the drop target has scroll bars: you get drop target scrolling for free, courtesy of MFC.

    What is drop target scrolling? Suppose a drag-and-drop operation has begun and the user wants to drop the data at a location in a CScrollView that is currently scrolled out of sight. If the cursor pauses within a few pixels of the view's border, a CScrollView will automatically scroll itself for as long as the cursor remains in that vicinity. Thus, the user can move the cursor to the edge of the window and wait until the drop point scrolls into view. This is just one more detail you'd have to handle yourself if MFC didn't do if for you.

    MFC and COM

    mfc and COM

    The primary reason why mfc makes COM, OLE, and ActiveX programming simpler is that it provides canned implementations of common COM interfaces in classes such as COleControl and COleControlSite. COM has been described as an "empty API," which means that Microsoft defines the interfaces and the methods and tells you what the methods are supposed to do but leaves it up to you, the object implementor, to write the code. The good news is that as long as you're writingActiveX controls, Automation servers, or other types of components that MFC explicitly supports, MFC implements the necessary COM interfaces for you.

    In the next three chapters, you'll get acquainted with many of the MFC classes that implement COM interfaces. Right now, I want you to understand how MFC classes implement COM interfaces. To do that, you must understand the two techniques that COM programmers use to write C++ classes representing COM objects. The first is multiple inheritance. The second is nested classes. MFC uses only nested classes, but let's look at both techniques so that we can compare the relative merits of each.

    Multiple Inheritance

    C++ programmers define COM interfaces using the following syntax:

    interface IUnknown
    {
     virtual HRESULT __stdcall QueryInterface (REFIID riid, void** ppv) = 0;
     virtual ULONG __stdcall AddRef () = 0;
     virtual ULONG __stdcall Release () = 0;
    };
    

    The keyword interface is an alias for struct. Therefore, to the C++ programmer, an interface definition is a set of pure virtual functions logically bound together as members of a common structure. And because structures and classes are treated almost identically in C++, it's perfectly legal to derive one interface from another, like this.

    interface IMath : public IUnknown
    {
     virtual HRESULT __stdcall Add (int a, int b, int* pResult) = 0;
     virtual HRESULT __stdcall Subtract (int a, int b, int* pResult) = 0;
    };
    

    You can take advantage of the fact that interface definitions are merely sets of pure virtual functions when you develop C++ classes that represent COM objects. For example, you can declare a class that implements IMath like this:

    class CComClass : public IMath
    {
    protected:
     long m_lRef; // Reference count
    public:
     CComClass ();
     virtual ~CComClass ();
     // IUnknown methods
     virtual HRESULT __stdcall QueryInterface (REFIID riid, void** ppv);
     virtual ULONG __stdcall AddRef ();
     virtual ULONG __stdcall Release ();
     // IMath methods
     virtual HRESULT __stdcall Add (int a, int b, int* pResult);
     virtual HRESULT __stdcall Subtract (int a, int b, int* pResult);
    };
    

    With this setup, you can implement QueryInterface, AddRef, Release, Add, and Subtract as member functions of class CComClass.

    Now, suppose you want CComClass to implement not just one COM interface, but two. How do you do it? One approach is to derive CComClass from both IMath and another interface by using multiple inheritance, like so:

    class CComClass : public IMath, public ISpelling
    {
    protected:
     long m_lRef; // Reference count
    public:
     CComClass ();
     virtual ~CComClass ();
     // IUnknown methods
     virtual HRESULT __stdcall QueryInterface (REFIID riid, void** ppv);
     virtual ULONG __stdcall AddRef ();
     virtual ULONG __stdcall Release ();
     // IMath methods
     virtual HRESULT __stdcall Add (int a, int b, int* pResult);
     virtual HRESULT __stdcall Subtract (int a, int b, int* pResult);
     // ISpelling methods
     virtual HRESULT __stdcall CheckSpelling (wchar_t* pString);
    };
    

    This approach has a couple of advantages. First, it's simple. To declare a class that implements n interfaces, you simply include all n interfaces in the class's list of base classes. Second, you have to implement IUnknown only once. If each interface were truly implemented separately, you'd have to implement QueryInterface, AddRef, and Release for each one. But with multiple inheritance, all methods supported by all interfaces are essentially merged into one implementation.

    One of the more interesting aspects of using multiple inheritance to write COM classes is what happens when a client calls QueryInterface asking for an interface pointer. Let's say that the client asks for an IMath pointer. The proper way to return the interface pointer is to cast the this pointer to an IMath*:

    *ppv = (IMath*) this;
    

    If the client asks for an ISpelling pointer instead, you cast to ISpelling*:

    *ppv = (ISpelling*) this;
    

    If you omit the casts, the code will compile just fine but will probably blow up when one of the two interfaces is used. Why? Because a class formed with multiple inheritance contains multiple vtables and multiple vtable pointers, and without the cast, you don't know which vtable the this pointer references. In other words, the two casts shown here return different numeric values, even though this never varies. If a client asks for an ISpelling pointer and you return a plain (uncasted) this pointer, and if this happens to reference IMath's vtable, the client calls ISpelling methods through an IMath vtable. That's a formula for disaster and is why COM classes that use multiple inheritance always cast to retrieve the proper vtable pointer.

    Nested Classes

    What's wrong with using multiple inheritance to implement COM classes? Nothing—provided that no two interfaces have methods with the same names and signatures. If IMath and ISpelling both contained methods named Init that had identical parameter lists but required separate implementations, you wouldn't be able to use multiple inheritance to define a class that implements both of them. Why? Because with multiple inheritance, the class would have just one member function named Init. It would therefore be impossible to implement Init separately for IMath and ISpelling.

    This limitation is the reason MFC uses the nested class approach to implementing COM interfaces. Nested classes are a little more work and slightly less intuitive than multiple inheritance, but they're also suitably generic. You can use the nested class approach to implement any combination of COM interfaces in a single C++ class, regardless of the interfaces' characteristics. Here's how it works.

    Suppose that CComClass implements IMath and ISpelling and that both interfaces have a method named Init that accepts no parameters.

    virtual HRESULT __stdcall Init () = 0;
    

    You can't use multiple inheritance in this case because of C++'s inability to support two semantically identical functions in one class. So instead, you define two subclasses, each of which implements one interface:

    class CMath : public IMath
    {
    protected:
     CComClass* m_pParent; // Back pointer to parent
    public:
     CMath ();
     virtual ~CMath ();
     // IUnknown methods
     virtual HRESULT __stdcall QueryInterface (REFIID riid, void** ppv);
     virtual ULONG __stdcall AddRef ();
     virtual ULONG __stdcall Release ();
     // IMath methods
     virtual HRESULT __stdcall Add (int a, int b, int* pResult);
     virtual HRESULT __stdcall Subtract (int a, int b, int* pResult);
     virtual HRESULT __stdcall Init () = 0;
    };
    

    class CSpelling : public ISpelling
    {
    protected:
    CComClass* m_pParent; // Back pointer to parent
    public:
    CSpelling ();
    virtual ~CSpelling ();
    // IUnknown methods
    virtual HRESULT __stdcall QueryInterface (REFIID riid, void** ppv);
    virtual ULONG __stdcall AddRef ();
    virtual ULONG __stdcall Release ();
    // ISpelling methods
    virtual HRESULT __stdcall CheckSpelling (wchar_t* pString);
    virtual HRESULT __stdcall Init () = 0;
    };

    To make CMath and CSpelling nested classes, you declare them inside CComClass. Then you include in CComClass a pair of data members that are instances of CMath and CSpelling:

    class CComClass : public IUnknown
    {
    protected:
     long m_lRef;  // Reference count
     class CMath : public IMath
     {[...]
     };
     CMath m_objMath;  // CMath object
     class CSpelling : public ISpelling
     {[...]
     };
     CSpelling m_objSpell; // CSpelling object
    public:
     CComClass ();
     virtual ~CComClass ();
     // IUnknown methods
     virtual HRESULT __stdcall QueryInterface (REFIID riid, void** ppv);
     virtual ULONG __stdcall AddRef ();
     virtual ULONG __stdcall Release ();
    };
    

    Notice that CComClass now derives only from IUnknown. It doesn't derive from IMath or ISpelling because the nested classes provide implementations of both interfaces. If a client calls QueryInterface asking for an IMath pointer, CComClass simply passes out a pointer to the CMath object:

    *ppv = (IMath*) &m_objMath;
    

    Similarly, if asked for an ISpelling pointer, CComClass returns a pointer to m_objSpell:

    *ppv = (ISpelling*) &m_objSpell;
    

    A key point to understand about the nested class approach is that the subobjects must delegate all calls to their IUnknown methods to the equivalent methods in the parent class. Notice that in place of a member variable that stores a reference count, each nested class stores a CComClass pointer. That pointer is a "back pointer" to the subobject's parent. Delegation is performed by calling CComClass's IUnknown methods through the back pointer. Typically, the parent's constructor initializes the back pointers:

    CComClass::CComClass ()
    {
     [...]  // Normal initialization stuff goes here.
     m_objMath.m_pParent = this;
     m_objSpell.m_pParent = this;
    }
    

    The nested classes' implementations of IUnknown look like this:

    HRESULT __stdcall CComClass::CMath::QueryInterface (REFIID riid, void** ppv)
    {
     return m_pParent->QueryInterface (riid, ppv);
    }
    

    ULONG __stdcall CComClass::CMath::AddRef ()
    {
    return m_pParent->AddRef ();
    }

    ULONG __stdcall CComClass::CMath::Release ()
    {
    return m_pParent->Release ();
    }

    Delegation of this sort is necessary for two reasons. First, if a client calls AddRef or Release on an interface implemented by a subobject, the parent's reference count should be adjusted, not the subobject's. Second, if a client calls QueryInterface on one of the subobjects, the parent must field the call because only the parent knows which nested classes are present and therefore which interfaces it implements.

    MFC and Nested Classes

    If you browse through the source code for MFC classes such as COleControl, you won't see anything that resembles the code in the previous section. That's because MFC hides its nested classes behind macros.

    MFC's COleDropTarget class is a case in point. It's one of the simpler MFC COM classes, and it implements just one COM interface—a standard interface named IDropTarget. If you look inside Afxole.h, you'll see these statements near the end of COleDropTarget's class declaration:

    BEGIN_INTERFACE_PART(DropTarget, IDropTarget)
     [...]
     STDMETHOD(DragEnter)(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
     STDMETHOD(DragOver)(DWORD, POINTL, LPDWORD);
     STDMETHOD(DragLeave)();
     STDMETHOD(Drop)(LPDATAOBJECT, DWORD, POINTL pt, LPDWORD);
    END_INTERFACE_PART(DropTarget)
    

    MFC's BEGIN_INTERFACE_PART macro defines a nested class that implements one COM interface. The class is named by prepending a capital X to the first parameter in the macro's parameter list. In this example, the nested class's name is XDropTarget. The END_INTERFACE_PART macro declares a member variable that's an instance of the nested class. Here's the code generated by the preprocessor:

    class XDropTarget : public IDropTarget
    {
    public:
     STDMETHOD_(ULONG, AddRef)();
     STDMETHOD_(ULONG, Release)();
     STDMETHOD(QueryInterface)(REFIID iid, LPVOID* ppvObj);
     STDMETHOD(DragEnter)(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
     STDMETHOD(DragOver)(DWORD, POINTL, LPDWORD);
     STDMETHOD(DragLeave)();
     STDMETHOD(Drop)(LPDATAOBJECT, DWORD, POINTL pt, LPDWORD);
    } m_xDropTarget;
    friend class XDropTarget;
    

    Do you see the resemblance between the preprocessor output and the nested class example we looked at earlier? Notice that the name of the nested class instance is m_x plus the first parameter in the macro's parameter list—in this case, m_xDropTarget.

    The nested class implements the three IUnknown methods plus the methods listed between BEGIN_INTERFACE_PART and END_INTERFACE_PART. IDropTarget has four methods— DragEnter, DragOver, DragLeave, and Drop—hence the methods named in the preceding code listing. Here's an excerpt from the MFC source code file Oledrop2.cpp showing how IDropTarget's methods are implemented in the nested XDropTarget class:

    STDMETHODIMP_(ULONG) COleDropTarget::XDropTarget::AddRef()
    {
     [...]
    }
    

    STDMETHODIMP_(ULONG) COleDropTarget::XDropTarget::Release()
    {
    [...]
    }

    STDMETHODIMP COleDropTarget::XDropTarget::QueryInterface(...)
    {
    [...]
    }

    STDMETHODIMP COleDropTarget::XDropTarget::DragEnter(...)
    {
    [...]
    }

    STDMETHODIMP COleDropTarget::XDropTarget::DragOver(...)
    {
    [...]
    }

    STDMETHODIMP COleDropTarget::XDropTarget::DragLeave(...)
    {
    [...]
    }

    STDMETHODIMP COleDropTarget::XDropTarget::Drop(...)
    {
    [...]
    }

    The code inside the method implementations is unimportant for now. The key here is that a few innocent-looking macros in an MFC source code listing turn into a nested class that implements a full-blown COM interface. You can create a class that implements several COM interfaces by including one BEGIN_INTERFACE_PART/END_INTERFACE_PART block for each interface. Moreover, you needn't worry about conflicts if two or more interfaces contain identical methods because the nested class technique permits each interface (and its methods) to be implemented independently.

    How MFC Implements IUnknown

    Let's go back and look more closely at COleDropTarget's implementation of QueryInterface, AddRef, and Release. Here's the complete, unabridged version:

    STDMETHODIMP_(ULONG) COleDropTarget::XDropTarget::AddRef()
    {
     METHOD_PROLOGUE_EX_(COleDropTarget, DropTarget)
     return pThis->ExternalAddRef();
    }
    

    STDMETHODIMP_(ULONG) COleDropTarget::XDropTarget::Release()
    {
    METHOD_PROLOGUE_EX_(COleDropTarget, DropTarget)
    return pThis->ExternalRelease();
    }

    STDMETHODIMP COleDropTarget::XDropTarget::QueryInterface(
    REFIID iid, LPVOID* ppvObj)
    {
    METHOD_PROLOGUE_EX_(COleDropTarget, DropTarget)
    return pThis->ExternalQueryInterface(&iid, ppvObj);
    }

    Once more, what MFC is doing is hidden behind a macro. In this case, the macro is METHOD_PROLOGUE_EX_, which creates a stack variable named pThis that points to XDropTarget's parent—that is, the COleDropTarget object of which the XDropTarget object is a member. Knowing this, you can see that XDropTarget's IUnknown methods delegate to COleDropTarget. Which begs a question or two: What do COleDropTarget's ExternalAddRef, ExternalRelease, and ExternalQueryInterface functions do, and where do they come from?

    The second question is easy to answer. All three functions are members of CCmdTarget, and COleDropTarget is derived from CCmdTarget. To answer the first question, we need to look at the function implementations inside CCmdTarget. Here's an excerpt from the MFC source code file Oleunk.cpp:

    DWORD CCmdTarget::ExternalAddRef()
    {
     [...]
     return InternalAddRef();
    }
    

    DWORD CCmdTarget::ExternalRelease()
    {
    [...]
    return InternalRelease();
    }

    DWORD CCmdTarget::ExternalQueryInterface(const void* iid,
    LPVOID* ppvObj)
    {
    [...]
    return InternalQueryInterface(iid, ppvObj);
    }

    ExternalAddRef, ExternalRelease, and ExternalQueryInterface call another set of CCmdTarget functions named InternalAddRef, InternalRelease, and InternalQueryInterface. The Internal functions are a little more complicated, but if you look at them, you'll find that they do just what AddRef, Release, and QueryInterface are supposed to do, albeit in an MFC way. So now we know that the nested class's IUnknown methods delegate to the parent class and that the parent class inherits implementations of these methods from CCmdTarget. Let's keep going.

    Interface Maps

    The most interesting Internal function is InternalQueryInterface. If you peek at it in Oleunk.cpp, you'll see that it calls a little-known function named GetInterface, which belongs to a little-known class named CUnknown. GetInterface does a table lookup to determine whether this class supports the specified interface. It then retrieves a pointer to the nested class that implements the interface and returns it to InternalQueryInterface. So MFC uses a table-driven mechanism to implement QueryInterface. But where do the tables come from?

    Once more, we can look to COleDropTarget for an example. At the very end of COleDropTarget's class declaration is the statement

    DECLARE_INTERFACE_MAP()
    

    And in COleDropTarget's implementation is this set of related statements:

    BEGIN_INTERFACE_MAP(COleDropTarget, CCmdTarget)
     INTERFACE_PART(COleDropTarget, IID_IDropTarget, DropTarget)
    END_INTERFACE_MAP()
    

    DECLARE_INTERFACE_MAP is an MFC macro that declares an interface map—a table containing one entry for each interface that a class (in reality, a nested class) implements. BEGIN_INTERFACE_MAP and END_INTERFACE_MAP are also macros. They define the contents of the interface map. Just as message maps tell MFC which messages a class provides handlers for, interface maps tell MFC which COM interfaces a class supports and which nested classes provide the interface implementations. Each INTERFACE_PART macro that appears between BEGIN_INTERFACE_MAP and END_INTERFACE_MAP constitutes one entry in the table. In this example, the INTERFACE_PART statement tells MFC that the interface map is a member of COleDropTarget, that COleDropTarget implements the IDropTarget interface, and that the nested class containing the actual IDropTarget implementation is XDropTarget. INTERFACE_PART prepends an X to the class name in the same manner as BEGIN_INTERFACE_PART.

    Because an interface map can contain any number of INTERFACE_PART macros, MFC classes aren't limited to one COM interface each; they can implement several. For each INTERFACE_PART entry that appears in a class's interface map, there is one BEGIN_INTERFACE_PART/END_INTERFACE_PART block in the class declaration. Take a look at COleControl's interface map in Ctlcore.cpp and the numerous BEGIN_INTERFACE_PART/END_INTERFACE_PART blocks in AfxCtl.h and you'll see what I mean.

    MFC and Aggregation

    Does it seem curious that CCmdTarget has two sets of functions with QueryInterface, AddRef, and Release in their names? When I showed you the source code for the External functions, I omitted (for clarity) the part that explains why. Here it is again, but this time in unabbreviated form:

    DWORD CCmdTarget::ExternalAddRef()
    {
     // delegate to controlling unknown if aggregated
     if (m_pOuterUnknown != NULL)return m_pOuterUnknown->AddRef();return InternalAddRef();
    }
    

    DWORD CCmdTarget::ExternalRelease()
    {
    // delegate to controlling unknown if aggregated
    if (m_pOuterUnknown != NULL)return m_pOuterUnknown->Release();return InternalRelease();
    }

    DWORD CCmdTarget::ExternalQueryInterface(const void* iid,
    LPVOID* ppvObj)
    {
    // delegate to controlling unknown if aggregated
    if (m_pOuterUnknown != NULL)return m_pOuterUnknown->QueryInterface((IID)iid, ppvObj);return InternalQueryInterface(iid, ppvObj);
    }

    Observe that the External functions call the Internal functions only if m_pOuterUnknown holds a NULL value. m_pOuterUnknown is a CCmdTarget member variable that holds an object's controlling unknown. If m_pOuterUnknown is not NULL, the External functions delegate through the pointer held in m_pOuterUnknown. If you're familiar with COM aggregation, you can probably guess what's going on here. But if aggregation is new to you, the preceding code requires further explanation.

    COM has never supported inheritance in the way that C++ does. In other words, you can't derive one COM object from another in the way that you can derive one C++ class from another. However, COM does support two mechanisms— containment and aggregation—for object reuse.

    Containment is the simpler of the two. To illustrate how it works, let's say you've written an object that contains a pair of methods named Add and Subtract. Now suppose someone else has written a COM object with Multiply and Divide methods that you'd like to incorporate into your object. One way to "borrow" the other object's methods is to have your object create the other object with CoCreateInstance and call its methods as needed. Your object is the outer object, the other object is the inner object, and if m_pInnerObject holds a pointer to the interface on the inner object that implements Multiply and Divide, you might also include Multiply and Divide methods in your object and implement them like this:

    HRESULT __stdcall CComClass::Multiply (int a, int b, int* pResult)
    {
     return m_pInnerObject->Multiply (a, b, pResult);
    }
    

    HRESULT __stdcall CComClass::Divide (int a, int b, int* pResult)
    {
    return m_pInnerObject->Divide (a, b, pResult);
    }

    That's containment in a nutshell. Figure 18-5 shows the relationship between the inner and outer objects. Notice that the inner object's interface is exposed only to the outer object, not to the clients of the outer object.

    Figure 18-5. Containment.

    Aggregation is altogether different. When one object aggregates another, the aggregate object exposes the interfaces of both the inner and the outer objects. (See Figure 18-6.) The client has no idea that the object is actually an aggregate of two or more objects.

    Figure 18-6. Aggregation.

    Aggregation is similar to containment in that the outer object creates the inner object. But the similarities end there. For aggregation to work, the inner object and the outer object must work together to create the illusion that they're really one object. Both objects must adhere to a strict set of rules governing their behavior. One of those rules says that the outer object must pass its own IUnknown pointer to the inner object. This pointer becomes the inner object's controlling unknown. If a client calls an IUnknown method on the inner object, the inner object must delegate to the outer object by calling QueryInterface, AddRef, or Release through the controlling unknown. That's what happens when CCmdTarget's External functions call QueryInterface, AddRef, or Release through m_pOuterUnknown. If the object is aggregated, m_pOuterUnknown is non-NULL and the External functions delegate to the outer object. Otherwise, the object isn't aggregated and the Internal functions are called instead.

    A key difference between containment and aggregation is that any object can be contained by another object, but only objects that specifically support aggregation can be aggregated. MFC makes aggregation easy because it builds in aggregation support for free.

    MFC and Class Factories

    Any class library that places a friendly wrapper around COM should include support for class factories. COM class factories typically contain a lot of boilerplate code that varies little from one application to the next, so they're perfect candidates to be hidden away inside a C++ class.

    MFC provides a canned implementation of COM class factories in COleObjectFactory. MFC's COleObjectFactory class implements two COM interfaces: IClassFactory and IClassFactory2. IClassFactory2 is a superset of IClassFactory; it supports all of IClassFactory's methods and adds licensing methods that are used primarily by ActiveX controls.

    When you create a COleObjectFactory, you feed its constructor four critical pieces of information. The first is the CLSID of the object that the class factory creates. The second is a RUNTIME_CLASS pointer identifying the C++ class that implements objects of that type. The third is a BOOL that tells COM whether this server, if it's an EXE, is capable of creating multiple object instances. If this parameter is TRUE and 10 clients call CoCreateInstance on the COM class that this server implements, 10 different instances of the EXE are launched. If the parameter is FALSE, one instance of the EXE serves all 10 clients. The fourth and final parameter to COleObjectFactory's constructor is the ProgID of the object that the class factory creates. ProgID is short for Program ID; it's a human-readable name (for example, "Math.Object") that can be used in lieu of a CLSID to identify a COM class. The following code fragment creates a COleObjectFactory that instantiates CComClass when CLSID_Math is passed to a COM activation function:

    COleObjectFactory cf (
     CLSID_Math,// The object's CLSID
     RUNTIME_CLASS (CComClass),// Class representing the object
     FALSE,  // Many clients, one EXE
     _T ("Math.Object")  // The object's ProgID
    );
    

    Most MFC applications don't explicitly declare an instance of COleObjectFactory; instead, they use MFC's DECLARE_OLECREATE and IMPLEMENT_OLECREATE macros. When the preprocessor encounters

    // In the class declaration
    DECLARE_OLECREATE (CComClass)
    

    // In the class implementation
    IMPLEMENT_OLECREATE (CComClass, "Math.Object", 0x708813ac,
    0x88d6, 0x11d1, 0x8e, 0x53, 0x00, 0x60, 0x08, 0xa8, 0x27, 0x31)

    it outputs this:

    // In the class declaration
    public:
     static COleObjectFactory factory;
     static const GUID guid;
    

    // In the class implementation
    COleObjectFactory CComClass::factory(CComClass::guid,
    RUNTIME_CLASS(CComClass), FALSE, _T("Math.Object"));
    const GUID CComClass::guid =
    { 0x708813ac, 0x88d6, 0x11d1, { 0x8e, 0x53, 0x00,
    0x60, 0x08, 0xa8, 0x27, 0x31} };

    The one drawback to the OLECREATE macros is that they contain hardcoded references to COleObjectFactory. If you derive a class from COleObjectFactory and want to use it in an application, you must either discard the macros and hand-code the references to the derived class or write your own OLECREATE macros. Programmers occasionally do find it useful to derive their own classes from COleObjectFactory to modify the class factory's behavior. By overriding the virtual OnCreateObject function, for example, you can create a "singleton" class factory—a class factory that creates an object the first time IClassFactory::CreateInstance is called and hands out pointers to the existing object in response to subsequent activation requests.

    Internally, MFC maintains a linked list of all the COleObjectFactory objects that an application creates. (Look inside COleObjectFactory's constructor and you'll see the code that adds each newly instantiated object to the list.) COleObjectFactory includes handy member functions for registering all an application's class factories with the operating system and for registering the objects that the class factories create in the system registry. The statement

    COleObjectFactory::UpdateRegistryAll ();
    

    adds to the registry all the information required to create any object that is served up by this application's class factories. That's powerful, because the alternative is to write low-level code that relies on win32 registry functions to update the registry yourself.

    Putting It All in Perspective

    Has this chapter covered everything there is to know about the relationship between MFC and COM? Hardly. There's plenty more, as you'll discover in the next three chapters. But this chapter has set the stage for the ones that follow. Now when you see a diagram like the one in Figure 18-1, you'll understand what you're looking at and have a pretty good idea of how MFC implements it. Plus, when you look over a wizard-generated source code listing or dig down into the MFC source code, you'll know what statements like INTERFACE_PART and IMPLEMENT_OLECREATE mean.

    If COM is new to you, you're probably feeling a little overwhelmed right now. Don't despair. Learning COM is a lot like learning to program Windows: You endure the obligatory six months of mental fog before it all begins to make sense. The good news is that you don't have to be an expert on COM to build COM-based applications with MFC. In fact, you don't have to know much about COM at all. But if you believe (as I do) that the best programmers are the ones who understand what goes on under the hood, the information presented in this chapter will serve you well in the long run.

    The Clipboard and OLE Drag-and-Drop

    Chapter 19

    The Clipboard and OLE Drag-and-Drop

    Since version 1.0, Microsoft Windows has supported the transfer of data through the clipboard. The clipboard is a centralized location where data can be stored en route from one application to another or on its way to another part of the same application. Even novices quickly grasp the clipboard's simple cut/copy/paste paradigm: select the data, cut it or copy it to the clipboard, and paste it somewhere else. Many applications feature Cut, Copy, and Paste commands in their Edit menus, and millions of Windows users know Ctrl-X, Ctrl-C, and Ctrl-V as the keyboard equivalents of these basic clipboard commands.

    The original clipboard (which I'll refer to as the legacy clipboard or simply as the clipboard) is still present in Windows 98 and Windows 2000, but programmers are encouraged to forego using it in favor of a newer, more capable clipboard known as the OLE clipboard. The OLE clipboard is backward-compatible with the legacy clipboard, meaning that one application can copy data to the OLE clipboard and another application can retrieve that data from the legacy clipboard, and vice versa. More important, the OLE clipboard lets programmers do a few things that the legacy clipboard doesn't. To transfer a large bitmap using the legacy clipboard, for example, you must allocate enough memory to store the entire bitmap. Transfer the same bitmap through the OLE clipboard, however, and you can store the bitmap in a more sensible storage medium, such as in a file on a hard disk.

    Closely related to the OLE clipboard is a visual method of transferring data called OLE drag-and-drop. OLE drag-and-drop simplifies cut/copy/paste by eliminating the middleman. Rather than requiring one command to move the data to the clipboard and another to paste it into a document, OLE drag-and-drop lets you grab a piece of data with the mouse and drag it to the desired location. Under the hood, a mechanism much like the OLE clipboard is used to facilitate the data transfer. But to the user, the transfer appears seamless—and very intuitive.

    mfc doesn't provide explicit support for operations involving the legacy clipboard, but it does support the OLE clipboard and OLE drag-and-drop. Writing code to use either of these data transfer mechanisms is no picnic if you're doing it from scratch, but mfc makes it surprisingly simple.

    The best way to understand the mechanics of the OLE clipboard and OLE drag-and-drop is to first understand how the legacy clipboard works. To that end, I'll begin this chapter with an overview of the legacy clipboard and a review of the Windows API functions used to access it. I'll then introduce the OLE clipboard and OLE drag-and-drop and demonstrate how to use MFC to develop applications that support both of them in no time flat.