{"id":22933,"date":"2008-03-31T10:00:00","date_gmt":"2008-03-31T10:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2008\/03\/31\/you-can-drag-multiple-virtual-objects-you-know\/"},"modified":"2008-03-31T10:00:00","modified_gmt":"2008-03-31T10:00:00","slug":"you-can-drag-multiple-virtual-objects-you-know","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20080331-00\/?p=22933","title":{"rendered":"You can drag multiple virtual objects, you know"},"content":{"rendered":"<p>\nA customer wanted to know how they could find out the\ndirectory that the user dropped a file onto.\nAs we already noted,\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2007\/05\/07\/2453927.aspx\">\nusers can drop files onto things other than directories<\/a>,\nso the question itself comes with incorrect hidden assumptions.\nThis is another one of those cases where you have to ask the\ncustomer, &#8220;What are you really trying to do?&#8221;\nThey have a problem and\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2006\/03\/23\/558887.aspx\">\nsolved half of it and are asking\nyou for help with the second half<\/a>, the part that makes little sense.\n<\/p>\n<p>\nIn this case, what the customer really wanted to do was\ncreate additional supporting files into the\ndirectory that the user dropped the file onto.\nTo solve the real problem, all you have to do is add\nvirtual objects to the data object the file being dragged.\n<\/p>\n<p>\nLet&#8217;s illustrate this by adding a second file to our\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2008\/03\/18\/8080183.aspx\">\nminimal example of dragging a virtual file<\/a>.\nActually, let&#8217;s make it more interesting.\nWe&#8217;re going to drag one real file plus one virtual file.\nStart by adding another file&#8217;s contents to our list of clipboard formats:\n<\/p>\n<pre>\n  enum {\n    DATA_FILEGROUPDESCRIPTOR,\n    <font COLOR=\"blue\">DATA_FILECONTENTS0,\n    DATA_FILECONTENTS1,<\/font>\n    DATA_NUM,\n    DATA_INVALID = -1,\n  };\n<\/pre>\n<p>\nOf course, we need to initialize the <code>FORMATETC<\/code>\nfor the contents of our new virtual file.\n<\/p>\n<pre>\nCTinyDataObject::CTinyDataObject() : _cRef(1)\n{\n  SetFORMATETC(&amp;_rgfe[DATA_FILEGROUPDESCRIPTOR],\n               RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR));\n  SetFORMATETC(&amp;_rgfe[<font COLOR=\"blue\">DATA_FILECONTENTS0<\/font>],\n               RegisterClipboardFormat(CFSTR_FILECONTENTS),\n               <font COLOR=\"blue\">TYMED_ISTREAM<\/font>, \/* lindex *\/ 0);\n  <font COLOR=\"blue\">SetFORMATETC(&amp;_rgfe[DATA_FILECONTENTS1],\n               RegisterClipboardFormat(CFSTR_FILECONTENTS),\n               TYMED_HGLOBAL, \/* lindex *\/ 1);<\/font>\n}\n<\/pre>\n<p>\nWe need to add this second file to our <code>FILEGROUPDESCRIPTOR<\/code>.\nDoing this is trickier because the <code>FILEGROUPDESCRIPTOR<\/code>\nis a variable-size structure, so we have to declare our own version\nthat has room for two files.\n<\/p>\n<pre>\n\/\/ Hard-coded for expository purposes\n\/\/ (I can't believe I had to write that.)\n#define FILETODRAG TEXT(\"C:\\\\windows\\\\clock.avi\")\nHRESULT CreateFileGroupDescriptor(HGLOBAL *phglob)\n{\n  union {\n     FILEGROUPDESCRIPTOR fgd;\n     BYTE buffer[FIELD_OFFSET(FILEGROUPDESCRIPTOR, fgd[2])];\n  } u;\n  ZeroMemory(&amp;u, sizeof(u));\n  u.fgd.cItems = 2;\n  \/\/ item 0: the file itself\n  WIN32_FILE_ATTRIBUTE_DATA wfad;\n  if (!GetFileAttributesEx(FILETODRAG, GetFileExInfoStandard,\n                           &amp;wfad)) {\n   return E_FAIL;\n  }\n  u.fgd.fgd[0].dwFlags = FD_ATTRIBUTES | FD_CREATETIME |\n                 FD_ACCESSTIME | FD_WRITESTIME | FD_FILESIZE;\n  u.fgd.fgd[0].dwFileAttributes = wfad.dwFileAttributes;\n  u.fgd.fgd[0].ftCreationTime   = wfad.ftCreationTime;\n  u.fgd.fgd[0].ftLastAccessTime = wfad.ftLastAccessTime;\n  u.fgd.fgd[0].ftLastWriteTime  = wfad.ftLastWriteTime;\n  u.fgd.fgd[0].nFileSizeHigh    = wfad.nFileSizeHigh;\n  u.fgd.fgd[0].nFileSizeLow     = wfad.nFileSizeLow;\n  StringCchCopy(u.fgd.fgd[0].cFileName,\n                ARRAYSIZE(u.fgd.fgd[0].cFileName),\n                PathFindFileName(FILETODRAG));\n  \/\/ item 1: The virtual \"tag-along\" file\n  StringCchCopy(u.fgd.fgd[1].cFileName,\n                ARRAYSIZE(u.fgd.fgd[0].cFileName),\n                TEXT(\"TagAlong\"));\n  return CreateHGlobalFromBlob(&amp;u, sizeof(u),\n                               GMEM_MOVEABLE, phglob);\n}\n<\/pre>\n<p>\nThe ad-hoc union declares a block of memory large enough\nfor a <code>FILEGROUPDESCRIPTOR<\/code> that holds two files.\nFile zero is the file we are dragging, and as a courtesy\n(and in violation of the &#8220;doing the absolute least amount of\nwork necessary&#8221; that has guided the series),\nwe fill in the file attributes so that when the file is dropped\nonto Explorer, the resulting file has the right metadata.\nOn the other hand, our virtual file tries to sneak by\nwith as little as possible, providing only the mandatory file name.\n<\/p>\n<p>\nThe last thing to do is hand out the <code>FILEGROUPDESCRIPTOR<\/code>\nand the two files when we are asked for them.\n<\/p>\n<pre>\nHRESULT CTinyDataObject::GetData(FORMATETC *pfe, STGMEDIUM *pmed)\n{\n  ZeroMemory(pmed, sizeof(*pmed));\n  switch (_GetDataIndex(pfe)) {\n  case DATA_FILEGROUPDESCRIPTOR:\n    pmed-&gt;tymed = TYMED_HGLOBAL;\n    <font COLOR=\"blue\">return CreateFileGroupDescriptor(&amp;pmed-&gt;hGlobal);\n  case DATA_FILECONTENTS0:\n    pmed-&gt;tymed = TYMED_ISTREAM;\n    return SHCreateStreamOnFile(FILETODRAG, STGM_READ,\n                                &amp;pmed-&gt;pstm);\n  case DATA_FILECONTENTS1:\n    pmed-&gt;tymed = TYMED_HGLOBAL;\n    return CreateHGlobalFromBlob(\"Dummy\", 5, GMEM_MOVEABLE,\n                                  &amp;pmed-&gt;hGlobal);<\/font>\n  }\n  return DV_E_FORMATETC;\n}\n<\/pre>\n<p>\nThere you have it, a data object that consists of a file\n(<code>FILETODRAG<\/code>) and a virtual file.\nWhen the user drops the data object into a folder,\nboth files are dropped into the destination directory.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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, [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25,139],"class_list":["post-22933","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code","tag-what-a-drag"],"acf":[],"blog_post_summary":"<p>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, [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/22933","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=22933"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/22933\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=22933"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=22933"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=22933"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}