MFC Uniform Data Transfer Support

The MFC library does a lot to make data object programming easier. As you study the MFC data object classes, you'll start to see a pattern in MFC COM support. At the component end, the MFC library provides a base class that implements one or more OLE interfaces. The interface member functions call virtual functions that you override in your derived class. At the client end, the MFC library provides a class that wraps an interface pointer. You call simple member functions that use the interface pointer to make COM calls.

The terminology needs some clarification here. The data object that's been described is the actual C++ object that you construct, and that's the way Brockschmidt uses the term. In the MFC documentation, a data object is what the client program sees through an IDataObject pointer. A data source is the object you construct in a component program.

The COleDataSource Class

When you want to use a data source, you construct an object of class COleDataSource, which implements the IDataObject interface (without advisory connection support). This class builds and manages a collection of data formats stored in a cache in memory. A data source is a regular COM object that keeps a reference count. Usually, you construct and fill a data source, and then you pass it to the clipboard or drag and drop it in another location, never to worry about it again. If you decide not to pass off a data source, you can invoke the destructor, which cleans up all its formats.

Following are some of the more useful member functions of the COleDataSource class.

void CacheData(CLIPFORMAT cfFormat, STGMEDIUM* lpStgMedium, FORMATETC* lpFormatEtc = NULL);

This function inserts an element in the data object's cache for data transfer. The lpStgMedium parameter points to the data, and the lpFormatEtc parameter describes the data. If, for example, the STGMEDIUM structure specifies a disk filename, that filename gets stored inside the data object. If lpFormatEtc is set to NULL, the function fills in a FORMATETC structure with default values. It's safer, though, if you create your FORMATETC variable with the tymed member set.

void CacheGlobalData(CLIPFORMAT cfFormat, HGLOBAL hGlobal, FORMATETC* lpFormatEtc = NULL);

You call this specialized version of CacheData to pass data in global memory (identified by an HGLOBAL variable). The data source object is considered the owner of that global memory block, so you should not free it after you cache it. You can usually omit the lpFormatEtc parameter. The CacheGlobalData function does not make a copy of the data.

DROPEFFECT DoDragDrop(DWORD dwEffects = DROPEFFECT_COPY|DROPEFFECT_MOVE| DROPEFFECT_LINK, LPCRECT lpRectStartDrag = NULL, COleDropSource* pDropSource = NULL);

You call this function for drag-and-drop operations on a data source. You'll see it used in the EX26B example.

void SetClipboard(void);

The SetClipboard function, which you'll see in the EX26A example, calls the OleSetClipboard function to put a data source on the Windows Clipboard. The clipboard is responsible for deleting the data source and thus for freeing the global memory associated with the formats in the cache. When you construct a COleDataSource object and call SetClipboard, COM calls AddRef on the object.

The COleDataObject Class

This class is on the destination side of a data object transfer. Its base class is CCmdTarget, and it has a public member m_lpDataObject that holds an IDataObject pointer. That member must be set before you can effectively use the object. The class destructor only calls Release on the IDataObject pointer.

Following are a few of the more useful COleDataObject member functions.

BOOL AttachClipboard(void);

As Brockschmidt points out, OLE clipboard processing is internally complex. From your point of view, however, it's straightforward—as long as you use the COleDataObject member functions. You first construct an "empty" COleDataObject object, and then you call AttachClipboard, which calls the global OleGetClipboard function. Now the m_lpDataObject data member points back to the source data object (or so it appears), and you can access its formats.

If you call the GetData member function to get a format, you must remember that the clipboard owns the format and you cannot alter its contents. If the format consists of an HGLOBAL pointer, you must not free that memory and you cannot hang on to the pointer. If you need to have long-term access to the data in global memory, consider calling GetGlobalData instead.

If a non-COM-aware program copies data onto the clipboard, the AttachClipboard function still works because COM invents a data object that contains formats corresponding to the regular Windows data on the clipboard.

void BeginEnumFormats(void); BOOL GetNextFormat(FORMATETC* lpFormatEtc);

These two functions allow you to iterate through the formats that the data object contains. You call BeginEnumFormats first, and then you call GetNextFormat in a loop until it returns FALSE.

BOOL GetData(CLIPFORMAT cfFormat, STGMEDIUM* lpStgMedium FORMATETC* lpFormatEtc = NULL);

This function calls IDataObject::GetData and not much more. The function returns TRUE if the data source contains the format you asked for. You generally need to supply the lpFormatEtc parameter.

HGLOBAL GetGlobalData(CLIPFORMAT cfFormat, FORMATETC* lpFormatEtc = NULL);

Use the GetGlobalData function if you know your requested format is compatible with global memory. This function makes a copy of the selected format's memory block, and it gives you an HGLOBAL handle that you must free later. You can often omit the lpFormatEtc parameter.

BOOL IsDataAvailable(CLIPFORMAT cfFormat, FORMATETC* lpFormatEtc = NULL);

The IsDataAvailable function tests whether the data object contains a given format.

MFC Data Object Clipboard Transfer

Now that you've seen the COleDataObject and COleDataSource classes, you'll have an easy time doing clipboard data object transfers. But why not just do clipboard transfers the old way with GetClipboardData and SetClipboardData? You could for most common formats, but if you write functions that process data objects, you can use those same functions for drag and drop.

Figure 26-1 shows the relationship between the clipboard and the COleDataSource and COleDataObject classes. You construct a COleDataSource object

Click to view at full size.

Figure 26-1. MFC OLE clipboard processing.

on the copy side, and then you fill its cache with formats. When you call SetClipboard, the formats are copied to the clipboard. On the paste side, you call AttachClipboard to attach an IDataObject pointer to a COleDataObject object, after which you can retrieve individual formats.

Suppose you have a document-view application whose document has a CString data member m_strText. You want to use view class command handler functions that copy to and paste from the clipboard. Before you write those functions, write two helper functions. The first, SaveText, creates a data source object from the contents of m_strText. The function constructs a COleDataSource object, and then it copies the string contents to global memory. Last it calls CacheGlobalData to store the HGLOBAL handle in the data source object. Here is the SaveText code:

COleDataSource* CMyView::SaveText()

{
    CEx26fDoc* pDoc = GetDocument();
    if (!pDoc->m_strtext.IsEmpty()) {
        COleDataSource* pSource = new COleDataSource();
        int nTextSize = GetDocument()->
m_strText.GetLength() + 1;
        HGLOBAL hText = ::GlobalAlloc(GMEM_SHARE, nTextSize);
        LPSTR pText = (LPSTR) ::GlobalLock(hText);
        ASSERT(pText);
        strcpy(pText, GetDocument()->
m_strText);
        ::GlobalUnlock(hText);
        pSource->CacheGlobalData(CF_TEXT, hText);
        return pSource;
    }
    return NULL;
}

The second helper function, DoPasteText, fills in m_strText from a data object specified as a parameter. We're using COleDataObject::GetData here instead of GetGlobalData because GetGlobalData makes a copy of the global memory block. That extra copy operation is unnecessary because we're copying the text to the CString object. We don't free the original memory block because the data object owns it. Here is the DoPasteText code:

// Memory is MOVEABLE, so we must use GlobalLock!
    SETFORMATETC(fmt, CF_TEXT, DVASPECT_CONTENT, NULL, TYMED_HGLOBAL, -1);
    VERIFY(pDataObject->GetData(CF_TEXT, &stg, &fmt));
    HGLOBAL hText = stg.hGlobal;
    GetDocument()->m_strText = (LPSTR) ::GlobalLock(hText);
    ::GlobalUnlock(hText);
    return TRUE;
}

Here are the two command handler functions:

void CMyView::OnEditCopy()
{
    COleDataSource* pSource = SaveText();
    if (pSource) {
        pSource->SetClipboard(); 
    }
}
void CMyView::OnEditPaste()
{
    COleDataObject dataObject;
    VERIFY(dataObject.AttachClipboard());
    DoPasteText(&dataObject);
    // dataObject released
}