| Tenouk C & C++ | MFC Home | Automation 13 | Uniform Data Transfer & OLE 2 | Download | Site Index |


 

 

 

 

 

 

 

Uniform Data Transfer:

Clipboard transfer and OLE Drag & Drop 1

 

 

 

 

 

Program examples compiled using Visual C++ 6.0 compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this tutorial are listed below. Don’t forget to read Tenouk’s small disclaimer. The supplementary notes for this tutorial are cdib.h, cdib.cpp, formatetc and IDataObject.

  1. Intro

  2. The IDataObject Interface

  3. How IDataObject Improves on Standard Clipboard Support

  4. The FORMATETC and STGMEDIUM Structures

  5. FORMATETC

  6. STGMEDIUM

  7. The IDataObject Interface Member Functions

  8. MFC Uniform Data Transfer Support

  9. The COleDataSource Class

  10. The COleDataObject Class

  11. MFC Data Object Clipboard Transfer

  12. The MFC CRectTracker Class

 

Intro

 

ActiveX technology includes a powerful mechanism for transferring data within and among Microsoft Windows-based applications. The COM IDataObject interface is the key element of what is known as Uniform Data Transfer. As you'll see, Uniform Data Transfer (UDT) gives you all sorts of options for the formatting and storage of your transferred data, going well beyond standard clipboard transfers.

Microsoft Foundation Class support is available for Uniform Data Transfer, but MFC's support for UDT is not so high-level as to obscure what's going on at the COM interface level. One of the useful applications of UDT is OLE Drag and Drop. Many developers want to use drag-and-drop capabilities in their applications, and drag-and-drop support means that programs now have a standard for information interchange. The MFC library supports drag-and-drop operations, and that, together with clipboard transfer, is the main focus of this Module.

 

The IDataObject Interface

 

The IDataObject interface is used for clipboard transfers and drag-and-drop operations, but it's also used in compound documents, ActiveX Controls, and custom OLE features. In his book Inside OLE, 2d ed. (Microsoft Press, 1995) Kraig Brockschmidt says, "Think of objects as little piles of stuff." The IDataObject interface helps you move those piles around, no matter what kind of stuff they contain.

If you were programming at the Win32 level, you would write C++ code that supported the IDataObject interface. Your program would then construct data objects of this class, and you would manipulate those objects with the IDataObject member functions. In this Module you'll see how to accomplish the same results by using MFC's implementation of IDataObject. Let's start by taking a quick look at why the OLE clipboard is an improvement on the regular Windows clipboard.

 

How IDataObject Improves on Standard Clipboard Support

 

There has never been much MFC support for the Windows Clipboard. If you've written programs for the clipboard already, you've used Win32 clipboard functions such as OpenClipboard(), CloseClipboard(), GetClipboardData(), and SetClipboardData(). One program copies a single data element of a specified format to the clipboard, and another program selects the data by format code and pastes it. Standard clipboard formats include global memory (specified by an HGLOBAL variable) and various GDI objects, such as bitmaps and metafiles (specified by their handles). Global memory can contain text as well as custom formats.

The IDataObject interface picks up where the Windows Clipboard leaves off. To make a long story short, you transfer a single IDataObject pointer to or from the clipboard instead of transferring a series of discrete formats. The underlying data object can contain a whole array of formats. Those formats can carry information about target devices, such as printer characteristics, and they can specify the data's aspect or view. The standard aspect is content. Other aspects include an icon for the data and a thumbnail picture.

 

Note that the IDataObject interface specifies the storage medium of a data object format. Conventional clipboard transfer relies exclusively on global memory. The IDataObject interface permits the transmission of a disk filename or a structured storage pointer instead. Thus, if you want to transfer a very large block of data that's already in a disk file, you don't have to waste time copying it to and from a memory block. In case you were wondering, IDataObject pointers are compatible with programs that use existing clipboard transfer methods. The format codes are the same. Windows takes care of the conversion to and from the data object. Of course, if an OLE-aware program puts an IStorage pointer in a data object and puts the object on the clipboard, older, non-OLE-aware programs are unable to read that format.

The FORMATETC and STGMEDIUM Structures

 

Before you're ready for the IDataObject member functions, you need to examine two important COM structures that are used as parameter types: the FORMATETC structure and the STGMEDIUM structure.

 

FORMATETC

 

The FORMATETC structure is often used instead of a clipboard format to represent data format information. However, unlike the clipboard format, the FORMATETC structure includes information about a target device, the aspect or view of the data, and a storage medium indicator.

 

typedef struct tagFORMATETC

{

    CLIPFORMAT      cfFormat;

    DVTARGETDEVICE  *ptd;

    DWORD           dwAspect;

    LONG            lindex;

    DWORD           tymed;

}FORMATETC, *LPFORMATETC;

 

Here are the members of the FORMATETC structure.

 

Type

Name

Description

CLIPFORMAT

cfFormat

Structure that contains clipboard formats, such as standard interchange formats for example, CF_TEXT, which is a text format, and CF_DIB, which is an image compression format, custom formats such as rich text format, and OLE formats used to create linked or embedded objects.

DVTARGETDEVICE*

ptd

Structure that contains information about the target device for the data, including the device driver name (can be NULL).

DWORD

dwAspect

A DVASPECT enumeration constant (DVASPECT_CONTENT, DVASPECT _THUMBNAIL, and so on).

LONG

lindex

Usually -1.

DWORD

tymed

Specifies type of media used to transfer the object's data (TYMED_HGLOBAL, TYMED_FILE, TYMED_ISTORAGE, and so on).

 

Table 1.

 

An individual data object accommodates a collection of FORMATETC elements, and the IDataObject interface provides a way to enumerate them. A useful macro for filling in a FORMATETC structure appears below.

 

#define SETFORMATETC(fe, cf, asp, td, med, li)   \

    ((fe).cfFormat=cf, \

    (fe).dwAspect=asp, \

    (fe).ptd=td, \

    (fe).tymed=med, \

    (fe).lindex=li)

 

STGMEDIUM

 

The other important structure for IDataObject members is the STGMEDIUM structure.

 

typedef struct tagSTGMEDIUM

{

    DWORD tymed;

    [switch_type(DWORD), switch_is((DWORD) tymed)]

    union {

        [case(TYMED_GDI)]      HBITMAP        hBitmap;

        [case(TYMED_MFPICT)]   HMETAFILEPICT  hMetaFilePict;

        [case(TYMED_ENHMF)]    HENHMETAFILE   hEnhMetaFile;

        [case(TYMED_HGLOBAL)]  HGLOBAL        hGlobal;

        [case(TYMED_FILE)]     LPWSTR         lpszFileName;

        [case(TYMED_ISTREAM)]  IStream        *pstm;

        [case(TYMED_ISTORAGE)] IStorage       *pstg;

        [default];

    };

    [unique] IUnknown *pUnkForRelease;

}STGMEDIUM;

typedef STGMEDIUM *LPSTGMEDIUM;

 

The STGMEDIUM structure is a global memory handle used for operations involving data transfer. Here are the members.

 

Type

Name

Description

DWORD

tymed

Storage medium value used in marshaling and unmarshaling routines.

HBITMAP

hBitmap

Bitmap handle*.

HMETAFILEPICT

hMetaFilePict

Metafile handle*.

HENHMETAFILE

hEnhMetaFile

Enhanced metafile handle*.

HGLOBAL

hGlobal

Global memory handle*.

LPOLESTR

lpszFileName

Disk filename (double-byte)*.

ISTREAM*

pstm

IStream interface pointer*.

ISTORAGE*

pstg

IStorage interface pointer*.

IUNKNOWN

pUnkForRelease

Used by clients to call Release() for formats with interface pointers.

 

Table 2.

 

* This member is part of a union, including handles, strings, and interface pointers used by the receiving process to access the transferred data.

As you can see, the STGMEDIUM structure specifies where data is stored. The tymed variable determines which union member is valid.

 

The IDataObject Interface Member Functions

 

This interface has nine member functions.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

IDataObject Methods

Description

GetData()

Renders the data described in a FORMATETC structure and transfers it through the STGMEDIUM structure.

GetDataHere()

Renders the data described in a FORMATETC structure and transfers it through the STGMEDIUM structure allocated by the caller.

QueryGetData()

Determines whether the data object is capable of rendering the data described in the FORMATETC structure.

GetCanonicalFormatEtc()

Provides a potentially different but logically equivalent FORMATETC structure.

SetData()

Provides the source data object with data described by a FORMATETC structure and an STGMEDIUM structure.

EnumFormatEtc()

Creates and returns a pointer to an object to enumerate the FORMATETC supported by the data object.

DAdvise()

Creates a connection between a data object and an advise sink so the advise sink can receive notifications of changes in the data object.

DUnadvise()

Destroys a notification previously set up with the DAdvise() method.

EnumDAdvise()

Creates and returns a pointer to an object to enumerate the current advisory connections.

 

Table 3.

 

Following are the functions that are important for this Module.

 

HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC ppEnum);

 

If you have an IDataObject pointer for a data object, you can use EnumFormatEtc() to enumerate all the formats that it supports. This is an ugly API that the MFC library insulates you from. You'll learn how this happens when you examine the COleDataObject class.

 

HRESULT GetData(FORMATETC* pFEIn, STGMEDIUM* pSTM);

 

GetData() is the most important function in the interface. Somewhere, up in the sky, is a data object, and you have an IDataObject pointer to it. You specify, in a FORMATETC variable, the exact format you want to use when you retrieve the data, and you prepare an empty STGMEDIUM variable to accept the results. If the data object has the format you want, GetData() fills in the STGMEDIUM structure. Otherwise, you get an error return value.

 

HRESULT QueryGetData(FORMATETC* pFE);

 

You call QueryGetData() if you're not sure whether the data object can deliver data in the format specified in the FORMATETC structure. The return value says, "Yes, I can" (S_OK) or "No, I can't" (an error code). Calling this function is definitely more efficient than allocating a STGMEDIUM variable and calling GetData().

 

HRESULT SetData(FORMATETC* pFEIn, STGMEDIUM* pSTM, BOOL fRelease);

 

Data objects rarely support SetData(). Data objects are normally loaded with formats in their own server module; clients retrieve data by calling GetData(). With SetData(), you'd be transferring data in the other direction, like pumping water from your house back to the water company.

 

Other IDataObject Member Functions—Advisory Connections

 

The interface contains other important functions that let you implement an advisory connection. When the program using a data object needs to be notified whether the object's data changes, the program can pass an IAdviseSink pointer to the object by calling the IDataObject::DAdvise function. The object then calls various IAdviseSink member functions, which the client program implements. You won't need advisory connections for drag-and-drop operations, but you will need them when you get to embedding in Module 27.

 

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 MYMFC30B example.

 

void SetClipboard(void);

 

The SetClipboard() function, which you'll see in the MYMFC30A 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 1 shows the relationship between the clipboard and the COleDataSource and COleDataObject classes.

 

 

Figure 1: MFC OLE clipboard processing.

 

Figure 1: MFC OLE clipboard processing.

 

You construct a COleDataSource object 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

}

 

The MFC CRectTracker Class

 

The CRectTracker class is useful in both OLE and non-OLE programs. It allows the user to move and resize a rectangular object in a view window. There are two important data members: the m_nStyle member determines the border, resize handle, and other characteristics; and the m_rect member holds the device coordinates for the rectangle.

The important member functions follow.

 

void Draw(CDC* pDC) const;

 

The Draw() function draws the tracker, including border and resize handles, but it does not draw anything inside the rectangle. That's your job.

 

BOOL Track(CWnd* pWnd, CPoint point, BOOL bAllowInvert = FALSE, CWnd* pWndClipTo = NULL);

 

You call this function in a WM_LBUTTONDOWN handler. If the cursor is on the rectangle border, the user can resize the tracker by holding down the mouse button; if the cursor is inside the rectangle, the user can move the tracker. If the cursor is outside the rectangle, Track() returns FALSE immediately; otherwise, Track returns TRUE only when the user releases the mouse button. That means Track works a little like CDialog::DoModal. It contains its own message dispatch logic.

 

int HitTest(CPoint point) const;

 

Call HitTest() if you need to distinguish between mouse button hits inside and on the tracker rectangle. The function returns immediately with the hit status in the return value.

 

BOOL SetCursor(CWnd* pWnd, UINT nHitTest) const;

 

Call this function in your view's WM_SETCURSOR handler to ensure that the cursor changes during tracking. If SetCursor() returns FALSE, call the base class OnSetCursor() function; if SetCursor() returns TRUE, you return TRUE.

 

CRectTracker Rectangle Coordinate Conversion

 

You must deal with the fact that the CRectTracker::m_rect member stores device coordinates. If you are using a scrolling view or have otherwise changed the mapping mode or viewport origin, you must do coordinate conversion. Here's a strategy:

 

  1. Define a CRectTracker data member in your view class. Use the name m_tracker.

  2. Define a separate data member in your view class to hold the rectangle in logical coordinates. Use the name m_rectTracker.

  3. In your view's OnDraw() function, set m_rect to the updated device coordinates, and then draw the tracker. This adjusts for any scrolling since the last OnDraw(). Some sample code appears below.

 

m_tracker.m_rect = m_rectTracker;

pDC->LPtoDP(m_tracker.m_rect); // tracker requires device coordinates

m_tracker.Draw(pDC);

 

  1. In your mouse button down message handler, call Track(), set m_rectTracker to the updated logical coordinates, and call Invalidate(), as shown here:

 

if (m_tracker.Track(this, point, FALSE, NULL))

{

    CClientDC dc(this);

    OnPrepareDC(&dc);

    m_rectTracker = m_tracker.m_rect;

    dc.DPtoLP(m_rectTracker);

    Invalidate();

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

  1. DCOM at MSDN.

  2. COM+ at MSDN.

  3. COM at MSDN.

  4. Win32 process, thread and synchronization story can be found starting from Module R.

  5. MSDN MFC 7.0 class library online documentation.

  6. MSDN MFC 9.0 class library online documentation - latest version.

  7. MSDN Library

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multibyte character set: Story and program examples.

 

 


 

| Tenouk C & C++ | MFC Home | Automation 13 | Uniform Data Transfer & OLE 2 | Download | Site Index |