You can drag multiple virtual objects, you know

A customer wanted to know how they could find out the directory that the user dropped a file onto. As we already noted, users can drop files onto things other than directories, so the question itself comes with incorrect hidden assumptions. This is another one of those cases where you have to ask the customer, "What are you really trying to do?" They have a problem and solved half of it and are asking you for help with the second half, the part that makes little sense.

In this case, what the customer really wanted to do was create additional supporting files into the directory that the user dropped the file onto. To solve the real problem, all you have to do is add virtual objects to the data object the file being dragged.

Let's illustrate this by adding a second file to our minimal example of dragging a virtual file. Actually, let's make it more interesting. We're going to drag one real file plus one virtual file. Start by adding another file's contents to our list of clipboard formats:

  enum {
    DATA_INVALID = -1,

Of course, we need to initialize the FORMATETC for the contents of our new virtual file.

CTinyDataObject::CTinyDataObject() : _cRef(1)
               TYMED_ISTREAM, /* lindex */ 0);
               TYMED_HGLOBAL, /* lindex */ 1);

We need to add this second file to our FILEGROUPDESCRIPTOR. Doing this is trickier because the FILEGROUPDESCRIPTOR is a variable-size structure, so we have to declare our own version that has room for two files.

// Hard-coded for expository purposes
// (I can't believe I had to write that.)
#define FILETODRAG TEXT("C:\\windows\\clock.avi")

HRESULT CreateFileGroupDescriptor(HGLOBAL *phglob)
  union {
  } u;
  ZeroMemory(&u, sizeof(u));
  u.fgd.cItems = 2;

  // item 0: the file itself
  if (!GetFileAttributesEx(FILETODRAG, GetFileExInfoStandard,
                           &wfad)) {
   return E_FAIL;
  u.fgd.fgd[0].dwFlags = FD_ATTRIBUTES | FD_CREATETIME |
  u.fgd.fgd[0].dwFileAttributes = wfad.dwFileAttributes;
  u.fgd.fgd[0].ftCreationTime   = wfad.ftCreationTime;
  u.fgd.fgd[0].ftLastAccessTime = wfad.ftLastAccessTime;
  u.fgd.fgd[0].ftLastWriteTime  = wfad.ftLastWriteTime;
  u.fgd.fgd[0].nFileSizeHigh    = wfad.nFileSizeHigh;
  u.fgd.fgd[0].nFileSizeLow     = wfad.nFileSizeLow;

  // item 1: The virtual "tag-along" file

  return CreateHGlobalFromBlob(&u, sizeof(u),
                               GMEM_MOVEABLE, phglob);

The ad-hoc union declares a block of memory large enough for a FILEGROUPDESCRIPTOR that holds two files. File zero is the file we are dragging, and as a courtesy (and in violation of the "doing the absolute least amount of work necessary" that has guided the series), we fill in the file attributes so that when the file is dropped onto Explorer, the resulting file has the right metadata. On the other hand, our virtual file tries to sneak by with as little as possible, providing only the mandatory file name.

The last thing to do is hand out the FILEGROUPDESCRIPTOR and the two files when we are asked for them.

HRESULT CTinyDataObject::GetData(FORMATETC *pfe, STGMEDIUM *pmed)
  ZeroMemory(pmed, sizeof(*pmed));

  switch (_GetDataIndex(pfe)) {
    pmed->tymed = TYMED_HGLOBAL;
    return CreateFileGroupDescriptor(&pmed->hGlobal);

    pmed->tymed = TYMED_ISTREAM;
    return SHCreateStreamOnFile(FILETODRAG, STGM_READ,

    pmed->tymed = TYMED_HGLOBAL;
    return CreateHGlobalFromBlob("Dummy", 5, GMEM_MOVEABLE,

  return DV_E_FORMATETC;

There you have it, a data object that consists of a file (FILETODRAG) and a virtual file. When the user drops the data object into a folder, both files are dropped into the destination directory.

Comments (4)
  1. Fulov Kwesjuns says:

    Does the cursor reflect this? Or is there no paper under the arrow anymore since Explorer?

    [Remember, the series on dragging virtual content focused on the least amount of work necessary to accomplish the task. If you want to add a fancy cursor, then feel free. -Raymond]
  2. Neil says:

    Interesting declaration; I don’t remember VC6 allowing that when I tried (I haven’t had cause to try it in newer versions). I’d have probably gone for a struct of a FILEGROUPDESCRIPTOR and a FILEDESCRIPTOR.

    If windows headers ever get enhanced for C++ then FILEGROUPDESCRIPTOR could be typedef’d to struct tagFILEGROUPDESCRIPTOR<1>. Or possibly derived from it.

  3. Fulov Kwesjuns says:

    Sorry Raymond -_-‘ Any hint on where to call SetCursor would be appreciated though :) I can make the fancy cursor with some pages sticking under it but actually using it would be superb ;)

    And thanks for another interesting episode in this series :)

    [As I already noted, fancy drag/drop effects are explicitly excluded from this series. I don’t have the answer to your question memorized, so I’d have to go searching for it anyway. I trust you can do that, too. -Raymond]
  4. Steve Thresher says:

    IDropTargetHelper might be what you’re looking for.

Comments are closed.

Skip to main content