{"id":105208,"date":"2021-05-12T07:00:00","date_gmt":"2021-05-12T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105208"},"modified":"2021-05-12T06:54:53","modified_gmt":"2021-05-12T13:54:53","slug":"20210512-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210512-00\/?p=105208","title":{"rendered":"Using Explorer&#8217;s fancy drag\/drop effects in your own programs"},"content":{"rendered":"<p>The default drag\/drop effect is very plain. Let&#8217;s try it out. Take the scratch program and make these changes. The COM smart pointer library for today is (rolls dice) C++\/WinRT. I will also be using wil for error handling and RAII types.<\/p>\n<pre>#include &lt;winrt\/base.h&gt;\r\n#include &lt;wil\/<a href=\"https:\/\/github.com\/microsoft\/wil\/blob\/master\/include\/wil\/result_macros.h\">result_macros.h<\/a>&gt;\r\n#include &lt;wil\/<a href=\"https:\/\/github.com\/microsoft\/wil\/blob\/master\/include\/wil\/resource.h\">resource.h<\/a>&gt;\r\n\r\nstruct SimpleDropTarget :\r\n    winrt::implements&lt;SimpleDropTarget, IDropTarget&gt;\r\n{\r\n    SimpleDropTarget(HWND hwnd) : hwndOwner(hwnd)\r\n    {\r\n    }\r\n\r\n    STDMETHODIMP DragEnter(IDataObject* pDataObject,\r\n        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)\r\n    {\r\n        dto.copy_from(pDataObject);\r\n        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));\r\n        return S_OK;\r\n    }\r\n    STDMETHODIMP DragOver(\r\n        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)\r\n    {\r\n        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));\r\n        return S_OK;\r\n    }\r\n\r\n    STDMETHODIMP Drop(IDataObject* pDataObject,\r\n        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)\r\n    {\r\n        dto.copy_from(pDataObject);\r\n        auto cleanup = wil::scope_exit([&amp;] { DragLeave(); });\r\n\r\n        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));\r\n        if (*pdwEffect != DROPEFFECT_NONE) {\r\n            \/\/ Do something cool.\r\n        }\r\n        return S_OK;\r\n    }\r\n\r\n    STDMETHODIMP DragLeave()\r\n    {\r\n        dto = nullptr;\r\n        return S_OK;\r\n    }\r\n\r\nprivate:\r\n    HWND hwndOwner;\r\n    winrt::com_ptr&lt;IDataObject&gt; dto;\r\n\r\n    HRESULT CalculateFeedback(\r\n        DWORD grfKeyState,\r\n        DWORD* pdwEffect)\r\n    {\r\n        if (grfKeyState &amp; MK_CONTROL) {\r\n            *pdwEffect = DROPEFFECT_COPY;\r\n        } else {\r\n            *pdwEffect = DROPEFFECT_MOVE;\r\n        }\r\n        return S_OK;\r\n    }\r\n};\r\n<\/pre>\n<p>This drop target does nothing particularly fancy. The only special thing is that holding the <kbd>Ctrl<\/kbd> key provides copy feedback rather than move feedback.<\/p>\n<p>The <code>Drop<\/code> method processes the new data object and either performs the drop or defers to the <code>Drag\u00adLeave<\/code> feedback if it turns out nothing is being dropped after all. Regardless of how the drop plays out, we call our own <code>Drag\u00adLeave()<\/code> to clean up. (We use the <code>wil::scope_exit<\/code> function to create a one-off RAII type that performs some action at destruction.)<\/p>\n<p>Let&#8217;s hook up this drop target to our window.<\/p>\n<pre>BOOL\r\nOnCreate(HWND hwnd, LPCREATESTRUCT lpcs)\r\n{\r\n    <span style=\"color: blue;\">RegisterDragDrop(hwnd,\r\n        winrt::make&lt;SimpleDropTarget&gt;(hwnd).get());<\/span>\r\n\r\n    return TRUE;\r\n}\r\n\r\nint WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,\r\n    LPSTR lpCmdLine, int nShowCmd)\r\n{\r\n    ...\r\n    if (SUCCEEDED(<span style=\"color: blue;\">OleInitialize<\/span>(NULL))) {\r\n        ...\r\n    }\r\n    ...\r\n}\r\n<\/pre>\n<p>In the main program, we use <code>OleInitialize<\/code> instead of <code>CoInitialize<\/code> because we are using OLE drag\/drop, which is an OLE feature.<\/p>\n<p>And when the window is created, we register our drop target.<\/p>\n<p>When you run this program, the drag\/drop feedback is just an empty gray box, possibly with a + sign to indicate that the resulting operation is a copy.<\/p>\n<p>Let&#8217;s add fancy Explorer-style drag\/drop feedback.<\/p>\n<pre>struct SimpleDropTarget :\r\n    winrt::implements&lt;SimpleDropTarget, IDropTarget&gt;\r\n{\r\n    SimpleDropTarget(HWND hwnd) : hwndOwner(hwnd),\r\n        <span style=\"color: blue;\">helper(winrt::create_instance&lt;IDropTargetHelper&gt;(\r\n            CLSID_DragDropHelper))<\/span>\r\n    {\r\n    }\r\n\r\n    STDMETHODIMP DragEnter(IDataObject* pDataObject,\r\n        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)\r\n    {\r\n        dto.copy_from(pDataObject);\r\n        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));\r\n        <span style=\"color: blue;\">POINT point{ pt.x, pt.y };\r\n        RETURN_IF_FAILED(helper-&gt;DragEnter(hwndOwner, dto.get(),\r\n            &amp;point, *pdwEffect));<\/span>\r\n        return S_OK;\r\n    }\r\n\r\n    STDMETHODIMP DragOver(\r\n        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)\r\n    {\r\n        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));\r\n        <span style=\"color: blue;\">POINT point{ pt.x, pt.y };\r\n        RETURN_IF_FAILED(helper-&gt;DragOver(&amp;point, *pdwEffect));<\/span>\r\n        return S_OK;\r\n    }\r\n\r\n    STDMETHODIMP Drop(IDataObject* pDataObject,\r\n        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)\r\n    {\r\n        dto.copy_from(pDataObject);\r\n        auto cleanup = wil::scope_exit([&amp;] { DragLeave(); });\r\n\r\n        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));\r\n        <span style=\"color: blue;\">POINT point{ pt.x, pt.y };\r\n        RETURN_IF_FAILED(helper-&gt;Drop(dto.get(), &amp;point, *pdwEffect));<\/span>\r\n        if (*pdwEffect != DROPEFFECT_NONE) {\r\n            \/\/ Do something cool.\r\n        }\r\n        return S_OK;\r\n    }\r\n\r\n    STDMETHODIMP DragLeave()\r\n    {\r\n        dto = nullptr;\r\n        <span style=\"color: blue;\">RETURN_IF_FAILED(helper-&gt;DragLeave());<\/span>\r\n        return S_OK;\r\n    }\r\n\r\nprivate:\r\n    HWND hwndOwner;\r\n    winrt::com_ptr&lt;IDataObject&gt; dto;\r\n    <span style=\"color: blue;\">winrt::com_ptr&lt;IDropTargetHelper&gt; helper;<\/span>\r\n\r\n    HRESULT CalculateFeedback(\r\n        DWORD grfKeyState,\r\n        DWORD* pdwEffect)\r\n    {\r\n        if (grfKeyState &amp; MK_CONTROL) {\r\n            *pdwEffect = DROPEFFECT_COPY;\r\n        } else {\r\n            *pdwEffect = DROPEFFECT_MOVE;\r\n        }\r\n        return S_OK;\r\n    }\r\n};\r\n<\/pre>\n<p>We create a shell drop target helper and forward all of our drop target methods into it. There is a bit of frustration here due to the <a title=\"Microspeak: Impedance mismatch\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20180123-00\/?p=97865\"> impedance mismatch<\/a> between the way <code>IDropTarget<\/code> and <code>IDropTargetHelper<\/code> represent the point.<\/p>\n<p>With these changes, the drop feedback is much more Explorer-like: The drag image matches the Explorer drag image, showing a thumbnail of the item being dragged, or a collection of items with a numeric badge showing how many items are being dragged. There&#8217;s also an information box below the image that says what the resulting operation will be.<\/p>\n<p>That&#8217;s a good start. Next time, we&#8217;ll add another feature.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Accessing those pre-made shell drop effects.<\/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],"class_list":["post-105208","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Accessing those pre-made shell drop effects.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105208","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=105208"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105208\/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=105208"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105208"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105208"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}