You can drag multiple virtual objects, you know

Raymond Chen

A customer wanted to know how they could find out thedirectory 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 thecustomer, “What are you really trying to do?”They have a problem andsolved half of it and are askingyou for help with the second half, the part that makes little sense.

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

Let’s illustrate this by adding a second file to ourminimal 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_FILEGROUPDESCRIPTOR,
    DATA_FILECONTENTS0,
    DATA_FILECONTENTS1,
    DATA_NUM,
    DATA_INVALID = -1,
  };

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

CTinyDataObject::CTinyDataObject() : _cRef(1)
{
  SetFORMATETC(&_rgfe[DATA_FILEGROUPDESCRIPTOR],
               RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR));
  SetFORMATETC(&_rgfe[DATA_FILECONTENTS0],
               RegisterClipboardFormat(CFSTR_FILECONTENTS),
               TYMED_ISTREAM, /* lindex */ 0);
  SetFORMATETC(&_rgfe[DATA_FILECONTENTS1],
               RegisterClipboardFormat(CFSTR_FILECONTENTS),
               TYMED_HGLOBAL, /* lindex */ 1);
}

We need to add this second file to our FILEGROUPDESCRIPTOR.Doing this is trickier because the FILEGROUPDESCRIPTORis a variable-size structure, so we have to declare our own versionthat 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 {
     FILEGROUPDESCRIPTOR fgd;
     BYTE buffer[FIELD_OFFSET(FILEGROUPDESCRIPTOR, fgd[2])];
  } u;
  ZeroMemory(&u, sizeof(u));
  u.fgd.cItems = 2;
  // item 0: the file itself
  WIN32_FILE_ATTRIBUTE_DATA wfad;
  if (!GetFileAttributesEx(FILETODRAG, GetFileExInfoStandard,
                           &wfad)) {
   return E_FAIL;
  }
  u.fgd.fgd[0].dwFlags = FD_ATTRIBUTES | FD_CREATETIME |
                 FD_ACCESSTIME | FD_WRITESTIME | FD_FILESIZE;
  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;
  StringCchCopy(u.fgd.fgd[0].cFileName,
                ARRAYSIZE(u.fgd.fgd[0].cFileName),
                PathFindFileName(FILETODRAG));
  // item 1: The virtual "tag-along" file
  StringCchCopy(u.fgd.fgd[1].cFileName,
                ARRAYSIZE(u.fgd.fgd[0].cFileName),
                TEXT("TagAlong"));
  return CreateHGlobalFromBlob(&u, sizeof(u),
                               GMEM_MOVEABLE, phglob);
}

The ad-hoc union declares a block of memory large enoughfor 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 ofwork necessary” that has guided the series),we fill in the file attributes so that when the file is droppedonto Explorer, the resulting file has the right metadata.On the other hand, our virtual file tries to sneak bywith as little as possible, providing only the mandatory file name.

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

HRESULT CTinyDataObject::GetData(FORMATETC *pfe, STGMEDIUM *pmed)
{
  ZeroMemory(pmed, sizeof(*pmed));
  switch (_GetDataIndex(pfe)) {
  case DATA_FILEGROUPDESCRIPTOR:
    pmed->tymed = TYMED_HGLOBAL;
    return CreateFileGroupDescriptor(&pmed->hGlobal);
  case DATA_FILECONTENTS0:
    pmed->tymed = TYMED_ISTREAM;
    return SHCreateStreamOnFile(FILETODRAG, STGM_READ,
                                &pmed->pstm);
  case DATA_FILECONTENTS1:
    pmed->tymed = TYMED_HGLOBAL;
    return CreateHGlobalFromBlob("Dummy", 5, GMEM_MOVEABLE,
                                  &pmed->hGlobal);
  }
  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.