{"id":8763,"date":"2011-12-30T07:00:00","date_gmt":"2011-12-30T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2011\/12\/30\/using-the-mns_dragdrop-style-menu-rearrangement\/"},"modified":"2011-12-30T07:00:00","modified_gmt":"2011-12-30T07:00:00","slug":"using-the-mns_dragdrop-style-menu-rearrangement","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20111230-00\/?p=8763","title":{"rendered":"Using the MNS_DRAGDROP style: Menu rearrangement"},"content":{"rendered":"<p>\nIn order to do drag-drop rearrangement of menus,\nyou need four things, most of which we already know how to do.\n<\/p>\n<ol>\n<li>Dragging an item out of a menu.\n    <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2011\/12\/28\/10251521.aspx\">\n    Check<\/a>.<\/p>\n<li>Dropping an item into a menu.\n    <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2011\/12\/29\/10251523.aspx\">\n    Check<\/a>.<\/p>\n<li>Connecting the drag with the drop.\n<li>Rearranging menu items in response to the operation.\n<\/ol>\n<p>\nLet&#8217;s do step&nbsp;4 first, just to mix things up.\nAnd since this is just a demonstration rather than production code,\nI&#8217;m only going to support string menu items of up to 255 characters\nin length.\n<\/p>\n<pre>\nBOOL MoveMenuItem(HMENU hmenu, UINT uPosFrom, UINT uPosInsertAfter)\n{\n BOOL fRc = FALSE;\n TCHAR sz[256];\n if (GetMenuString(hmenu, uPosFrom, sz, 256, MF_BYPOSITION) &amp;&amp;\n     InsertMenu(hmenu, uPosInsertAfter, MF_BYPOSITION,\n                GetMenuItemID(hmenu, uPosFrom), sz)) {\n  if (uPosFrom &gt; uPosInsertAfter) uPosFrom++;\n  fRc = DeleteMenu(hmenu, uPosFrom, MF_BYPOSITION);\n }\n return fRc;\n}\n<\/pre>\n<p>\nOne thing you might not have noticed is that I inserted the\ncopy before deleting the original.\nThat way, we don&#8217;t get stuck in the situation where we deleted\nthe original, then the reinsertion fails, and now we&#8217;ve lost the\nmenu item.\n(We can still get stuck if the deletion of the original fails,\nbut the hope is that that is much more unlikely than the failure\nof an insertion.)\n<\/p>\n<p>\nOkay, the next part is connecting the drag with the drop.\nTo do that, I&#8217;ll need some helper COM objects.\nBut first, I&#8217;m going to introduce something that I should have\nintroduced earlier: Objects that do nothing!\n(Just like our scratch program, they start out doing nothing,\nand then we&#8217;ll modify them to do something.)\n<\/p>\n<pre>\n\/\/ dummy data object\nclass CEmptyDataObject : public IDataObject\n{\npublic:\n  \/\/ IUnknown\n  STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj)\n  {\n    IUnknown *punk = NULL;\n    if (riid == IID_IUnknown) {\n      punk = static_cast&lt;IUnknown*&gt;(this);\n    } else if (riid == IID_IDataObject) {\n      punk = static_cast&lt;IDataObject*&gt;(this);\n    }\n    *ppvObj = punk;\n    if (punk) {\n      punk-&gt;AddRef();\n      return S_OK;\n    } else {\n      return E_NOINTERFACE;\n    }\n  }\n  STDMETHODIMP_(ULONG) AddRef()\n  {\n    return ++m_cRef;\n  }\n  STDMETHODIMP_(ULONG) Release()\n  {\n    ULONG cRef = --m_cRef;\n    if (cRef == 0) delete this;\n    return cRef;\n  }\n  \/\/ IDataObject\n  STDMETHODIMP GetData(FORMATETC *pfe, STGMEDIUM *pmed)\n  {\n    ZeroMemory(pmed, sizeof(*pmed));\n    return DV_E_FORMATETC;\n  }\n  STDMETHODIMP GetDataHere(FORMATETC *pfe, STGMEDIUM *pmed)\n  {\n    return E_NOTIMPL;\n  }\n  STDMETHODIMP QueryGetData(FORMATETC *pfe)\n  {\n    return DV_E_FORMATETC;\n  }\n  STDMETHODIMP GetCanonicalFormatEtc(FORMATETC *pfeIn,\n                                     FORMATETC *pfeOut)\n  {\n    *pfeOut = *pfeIn;\n    pfeOut-&gt;ptd = NULL;\n    return DATA_S_SAMEFORMATETC;\n  }\n  STDMETHODIMP SetData(FORMATETC *pfe, STGMEDIUM *pmed,\n                       BOOL fRelease)\n  {\n    return E_NOTIMPL;\n  }\n  STDMETHODIMP EnumFormatEtc(DWORD dwDirection,\n                             LPENUMFORMATETC *ppefe)\n  {\n  *ppefe = NULL;\n  return E_NOTIMPL;\n  }\n  STDMETHODIMP DAdvise(FORMATETC *pfe, DWORD grfAdv,\n                    IAdviseSink *pAdvSink, DWORD *pdwConnection)\n  {\n    *pdwConnection = 0;\n    return OLE_E_ADVISENOTSUPPORTED;\n  }\n  STDMETHODIMP DUnadvise(DWORD dwConnection)\n  {\n    return OLE_E_ADVISENOTSUPPORTED;\n  }\n  STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *ppefe)\n  {\n    *ppefe = NULL;\n    return OLE_E_ADVISENOTSUPPORTED;\n  }\n  CEmptyDataObject() : m_cRef(1) { }\n  virtual ~CEmptyDataObject() { }\nprivate:\n  ULONG m_cRef;\n};\n<\/pre>\n<p>\nThe <code>CEmpty&shy;Data&shy;Object<\/code>\nis simply a data object that contains no data.\nAnd here&#8217;s an equally uninteresting\n<code>CEmpty&shy;Drop&shy;Target<\/code>:\n<\/p>\n<pre>\nclass CEmptyDropTarget : public IDropTarget\n{\npublic:\n  \/\/ IUnknown\n  STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj)\n  {\n    IUnknown *punk = NULL;\n    if (riid == IID_IUnknown) {\n      punk = static_cast&lt;IUnknown*&gt;(this);\n    } else if (riid == IID_IDropTarget) {\n      punk = static_cast&lt;IDropTarget*&gt;(this);\n    }\n    *ppvObj = punk;\n    if (punk) {\n      punk-&gt;AddRef();\n      return S_OK;\n    } else {\n      return E_NOINTERFACE;\n    }\n  }\n  STDMETHODIMP_(ULONG) AddRef()\n  {\n    return ++m_cRef;\n  }\n  STDMETHODIMP_(ULONG) Release()\n  {\n    ULONG cRef = --m_cRef;\n    if (cRef == 0) delete this;\n    return cRef;\n  }\n  \/\/ IDropTarget\n  STDMETHODIMP DragEnter(IDataObject *pdto, DWORD grfKeyState,\n                         POINTL pt, DWORD *pdwEffect)\n  {\n    *pdwEffect = DROPEFFECT_NONE;\n    return E_NOTIMPL;\n  }\n  STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)\n  {\n    *pdwEffect = DROPEFFECT_NONE;\n    return E_NOTIMPL;\n  }\n  STDMETHODIMP DragLeave()\n  {\n    return E_NOTIMPL;\n  }\n  STDMETHODIMP Drop(IDataObject *pdto, DWORD grfKeyState,\n                    POINTL pt, DWORD *pdwEffect)\n  {\n    *pdwEffect = DROPEFFECT_NONE;\n    return E_NOTIMPL;\n  }\n  CEmptyDropTarget() : m_cRef(1) { }\n  virtual ~CEmptyDropTarget() { }\nprivate:\n  ULONG m_cRef;\n};\n<\/pre>\n<p>\nOkay, now back to item&nbsp;3: Connecting the drag with the drop.\nYour initial reaction might be to create a new clipboard format\ncalled, say, <tt>Dragged&shy;Menu&shy;Item<\/tt>\nwhich takes the form of a <code>TYMED_HGLOBAL<\/code>\nconsisting of a struct like\n<\/p>\n<pre>\nstruct MENUANDITEM\n{\n HMENU hmenu;\n UINT uItem;\n};\n<\/pre>\n<p>\nBut once you do that, you already have a problem:\nWhat happens if this item is dragged out of a 32-bit process\nand dropped into a 64-bit process?\nThe size of <code>HMENU<\/code> is different between the two processes,\nso the 32-bit and 64-bit\n<code>MENU&shy;AND&shy;ITEM<\/code> structures are\nnot compatible.\nThis is an example of\nhow you need to be aware of inter-process\ncommunications scenarios when developing persistence formats.\nIn this case,\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2005\/01\/31\/363790.aspx\">\nwe are passing a pointer-sized object between processes<\/a>.\nAlthough most people think of a persistence format as a file format,\nhere&#8217;s a case where a persistence format takes the form of\nan in-memory storage format.\n<\/p>\n<p>\nYou might decide to solve this problem by tweaking the structure\nto accommodate 32-bit and 64-bit Windows:\n<\/p>\n<pre>\nstruct MENUANDITEM\n{\n __int64 i64Menu;\n UINT uItem;\n void SetMenu(HMENU hmenu) { i64Menu = (INT_PTR)hmenu; }\n HMENU GetMenu() const { return (HMENU)(INT_PTR)i64Menu; }\n};\n<\/pre>\n<p>\nBut there&#8217;s an easier way out:\nSince we only want to support drag\/drop menu editing from within\nthe same menu (we don&#8217;t care about dragging an item from one\nmenu to another menu),\nthe drag source and drop target reside in the same process,\nso all we need to do is verify the data object&#8217;s identity,\nand if it&#8217;s our data object, we can consult side data to determine\nwhat is being dragged.\n<\/p>\n<p>\nOkay, so let&#8217;s start with a fresh\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2003\/07\/23\/54576.aspx\">\nscratch program<\/a>,\nand paste in the following:\n<\/p>\n<ul>\n<li>\n    <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/12\/06\/275659.aspx\">\n    <code>CDrop&shy;Source<\/code><\/a><\/p>\n<li><code>CEmpty&shy;Data&shy;Object<\/code>\n<li><code>CEmpty&shy;Drop&shy;Target<\/code>\n<li><code>Move&shy;Menu&shy;Item<\/code>\n<li>\n    <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2011\/12\/28\/10251521.aspx\">\n    <code>HANDLE_WM_MENU&shy;DRAG<\/code><\/a><\/p>\n<li>\n    <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2011\/12\/29\/10251523.aspx\">\n    <code>HANDLE_WM_MENU&shy;GET&shy;OBJECT<\/code><\/a>\n<\/ul>\n<p>\nOkay, enough\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2009\/08\/04\/9856634.aspx\">\nshopping<\/a>.\nNow to teach our drop target how to recognize that the data\nobject being dropped on it is our own:\n<\/p>\n<pre>\nclass CMenuDataObject : public CEmptyDataObject\n{\npublic:\n  CMenuDataObject(HMENU hmenu, UINT uPos)\n    : m_hmenu(hmenu), m_uPos(uPos) { }\npublic:\n  const HMENU m_hmenu;\n  const UINT m_uPos;\n};\nCMenuDataObject *g_pdtoDrag;\n<\/pre>\n<p>\nOur special data object when dragging a menu item merely carries\naround the menu and item so we can find it later.\nThe magical bit is that we also keep track of the object being\ndragged.\n(Exercise: Since this is a demo program, the object is just a global variable.\nWhat is the correct way of keeping track of <code>g_pdtoDrag<\/code>?)\n<\/p>\n<p>\nNow we get to teach our drop target to recognize\n<code>CMenu&shy;Data&shy;Object<\/code> and only\n<code>CMenu&shy;Data&shy;Object<\/code>:\n<\/p>\n<pre>\nclass CMenuDropTarget : public CEmptyDropTarget\n{\npublic:\n  \/\/ IDropTarget\n  STDMETHODIMP DragEnter(IDataObject *pdto, DWORD grfKeyState,\n                         POINTL pt, DWORD *pdwEffect);\n  STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);\n  STDMETHODIMP DragLeave();\n  STDMETHODIMP Drop(IDataObject *pdto, DWORD grfKeyState,\n                    POINTL pt, DWORD *pdwEffect);\n  CMenuDropTarget(HMENU hmenu, UINT uPos)\n    : m_hmenu(hmenu), m_uPos(uPos), m_uPosDrag(uPosNone) { }\n  void Reset() { m_uPosDrag = uPosNone; }\nprivate:\n  static const UINT uPosNone = 0xFFFFFFFF;\nprivate:\n  HMENU m_hmenu;   \/\/ menu being dropped on\n  UINT m_uPos;     \/\/ menu item being dropped on\n  UINT m_uPosDrag; \/\/ menu item being dragged, if from the same menu\n                   \/\/ else uPosNone\n};\nHRESULT CMenuDropTarget::DragEnter(\n    IDataObject *pdto, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)\n{\n  Reset();\n  IUnknown *punk;\n  if (SUCCEEDED(pdto-&gt;QueryInterface(IID_PPV_ARGS(&amp;punk)))) {\n    punk-&gt;Release();\n  }\n  if (punk == g_pdtoDrag &amp;&amp; g_pdtoDrag-&gt;m_hmenu == m_hmenu) {\n    m_uPosDrag = g_pdtoDrag-&gt;m_uPos;\n  } else {\n    m_uPosDrag = uPosNone;\n  }\n  return DragOver(grfKeyState, pt, pdwEffect);\n}\n<\/pre>\n<p>\nThe job of <code>CMenu&shy;Drop&shy;Target::&shy;Drag&shy;Enter<\/code>\nis to determine whether the item being dragged is a menu item from\nthe same menu.\nWe detect that the object being dragged is <code>g_pdtoDrag<\/code>\nby first querying for the canonical unknown,\nto remove any layers of wrapping COM may have placed around the object.\nWe compare this against <code>g_pdtoDrag<\/code>, which is a bit of a cheat;\nmore properly we should call <code>g_pdtoDrag-&gt;Query&shy;Interface<\/code>\nto get the canonical unknown for <code>g_pdtoDrag<\/code>,\nbut we can cheat because\nwe know that <code>CMenu&shy;Data&shy;Object<\/code>\nis singly-derived from <code>IUnknown<\/code> and that it does not\nsupport aggregation (and therefore it is its own canonical unknown).\n(Exercise: Why is it okay to use <code>punk<\/code> after releasing it?)\n<\/p>\n<p>\nAnyway, if the item is confirmed to be our item after all,\nthen we copy the menu item position so we can move it on the drop.\n<\/p>\n<pre>\nHRESULT CMenuDropTarget::DragOver(\n    DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)\n{\n  if (m_uPosDrag == uPosNone) {\n    *pdwEffect = DROPEFFECT_NONE;\n  } else {\n    *pdwEffect &amp;= DROPEFFECT_MOVE;\n  }\n  return S_OK;\n}\nHRESULT CMenuDropTarget::DragLeave()\n{\n  Reset();\n  return S_OK;\n}\n<\/pre>\n<p>\nThe <code>Drag&shy;Over<\/code> and <code>Drag&shy;Leave<\/code>\nmethods are largely uninteresting.\n<code>Drag&shy;Over<\/code> just gives appropriate feedback,\nand\n<code>Drag&shy;Leave<\/code> forgets about the data object that is\nno longer being dragged over us.\nThe real excitement is in the <code>Drop<\/code> method.\n<\/p>\n<pre>\nHRESULT CMenuDropTarget::Drop(\n    IDataObject *pdto, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)\n{\n  DragEnter(pdto, grfKeyState, pt, pdwEffect);\n  if (*pdwEffect &amp; DROPEFFECT_MOVE) {\n     MoveMenuItem(m_hmenu, m_uPosDrag, m_uPos);\n  }\n  return S_OK;\n}\n<\/pre>\n<p>\nWhen the drop happens, we move the menu item.\nKind of anticlimactic, isn&#8217;t it.\n<\/p>\n<p>\nOkay, at this point the <code>WM_MENU&shy;DRAG<\/code> and\n<code>WM_MENU&shy;GET&shy;OBJECT<\/code> handlers\nare old hat:\n<\/p>\n<pre>\nLRESULT OnMenuDrag(HWND hwnd, UINT uPos, HMENU hmenu)\n{\n LRESULT lres = MND_CONTINUE;\n if (g_pdtoDrag == NULL &amp;&amp; hmenu == GetSubMenu(GetMenu(hwnd), 0)) {\n  g_pdtoDrag = new(std::nothrow) CMenuDataObject(hmenu, uPos);\n  if (g_pdtoDrag) {\n   IDropSource *pds = new(std::nothrow) CDropSource();\n   if (pds) {\n    DWORD dwEffect;\n    DoDragDrop(g_pdtoDrag, pds, DROPEFFECT_MOVE, &amp;dwEffect);\n    pds-&gt;Release();\n   }\n   g_pdtoDrag-&gt;Release();\n   g_pdtoDrag = NULL;\n  }\n }\n return lres;\n}\nLRESULT OnMenuGetObject(HWND hwnd, MENUGETOBJECTINFO *pmgoi)\n{\n HRESULT hr = E_NOTIMPL;\n if (pmgoi-&gt;hmenu == GetSubMenu(GetMenu(hwnd), 0) &amp;&amp;\n     (pmgoi-&gt;dwFlags &amp; (MNGOF_BOTTOMGAP | MNGOF_TOPGAP))) {\n  IDropTarget *pdt = new(std::nothrow)\n      CMenuDropTarget(pmgoi-&gt;hmenu, pmgoi-&gt;uPos);\n  if (pdt) {\n   hr = pdt-&gt;QueryInterface(*(IID*)pmgoi-&gt;riid, &amp;pmgoi-&gt;pvObj);\n   pdt-&gt;Release();\n  }\n }\n return SUCCEEDED(hr) ? MNGO_NOERROR : MNGO_NOINTERFACE;\n}\n    HANDLE_MSG(hwnd, WM_MENUDRAG, OnMenuDrag);\n    HANDLE_MSG(hwnd, WM_MENUGETOBJECT, OnMenuGetObject);\n\/\/ and change CoInitialize and CoUninitialize\n\/\/ to OleInitialize and OleUninitialize, respectively\n<\/pre>\n<p>\nThere is a tricky part in <code>On&shy;Menu&shy;Get&shy;Object<\/code>,\nnamely that we only return a drop target if the mouse is <i>between<\/i>\nitems, because it is only when you are between items that you are actually\nrearranging.\n<\/p>\n<p>\nAnd there you have it, some menu drag\/drop stuff.\nIt was a lot of typing (mostly for those dummy objects),\nbut not a lot of work.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In order to do drag-drop rearrangement of menus, you need four things, most of which we already know how to do. Dragging an item out of a menu. Check. Dropping an item into a menu. Check. Connecting the drag with the drop. Rearranging menu items in response to the operation. Let&#8217;s do step&nbsp;4 first, just [&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],"class_list":["post-8763","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>In order to do drag-drop rearrangement of menus, you need four things, most of which we already know how to do. Dragging an item out of a menu. Check. Dropping an item into a menu. Check. Connecting the drag with the drop. Rearranging menu items in response to the operation. Let&#8217;s do step&nbsp;4 first, just [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/8763","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=8763"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/8763\/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=8763"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=8763"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=8763"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}