Using the MNS_DRAGDROP style: Dragging out


Windows 2000 introduced the MNS_DRAG­DROP menu style, which permits drag/drop operations in a menu. Nobody uses this style, probably because it's totally undiscoverable by the end-user. But I'll write a sample program anyway.

Mind you, I knew nothing about the MNS_DRAG­DROP menu style until I started writing this entry. But I simply read the documentation, which says that if you set this style, you will receive WM_MENU­DRAG and WM_MENU­GET­OBJECT messages. The WM_MENU­DRAG message is sent when the user drags a menu item, so let's go with that first. The documentation says that you get information about the item that was dragged, and then you return a code that specifies whether you want the menu to remain up or whether you want it torn down.

Simple enough. Let's do it.

Start with the scratch program, add the function Get­UI­Object­Of­File and the class CDrop­Source, and change the calls to Co­Initialize and Co­Uninitialize into Ole­Initialize and Ole­Uninitialize, respectively. Next, define the menu we're going to play with:

// resource header file
#define IDM_MAIN 1
#define IDC_CLOCK 100

// resource file
IDM_MAIN MENU PRELOAD
BEGIN
    POPUP "&Test"
    BEGIN
        MENUITEM "&Clock", IDC_CLOCK
    END
END

Now we can add some new code to our scratch program. First, we add a menu to our window and enable drag/drop on it:

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
 MENUINFO mi = { sizeof(mi), MIM_STYLE, MNS_DRAGDROP };
 return SetMenuInfo(GetMenu(hwnd), &mi);
}

// InitApp
 // wc.lpszMenuName = NULL;
 wc.lpszMenuName = MAKEINTRESOURCE(IDM_MAIN);

For both dragging and dropping, we need a way to obtain the COM object associated with a menu item, so I'll put them in this common helper function:

HRESULT GetMenuObject(HWND hwnd, HMENU hmenu, UINT uPos,
                      REFIID riid, void **ppvOut)
{
 HRESULT hr = E_NOTIMPL;
 *ppvOut = NULL;
 if (hmenu == GetSubMenu(GetMenu(hwnd), 0)) {
  switch (GetMenuItemID(hmenu, uPos)) {
  case IDC_CLOCK:
   hr = GetUIObjectOfFile(hwnd, L"C:\\Windows\\clock.avi",
                                             riid, ppvOut);
   break;
  }
 }
 return hr;
}

If the menu is our "Test" popup menu, then we know how to map the menu items to COM objects. For now, we have only one item, namely Clock, which corresponds to the C:\Windows\clock.avi¹ file.

Now we can hook up a handler to the WM_MENU­DRAG message:

#define HANDLE_WM_MENUDRAG(hwnd, wParam, lParam, fn) \
 (fn)((hwnd), (UINT)(wParam), (HMENU)(lParam))

LRESULT OnMenuDrag(HWND hwnd, UINT uPos, HMENU hmenu)
{
 LRESULT lres = MND_CONTINUE;
 IDataObject *pdto;
 if (SUCCEEDED(GetMenuObject(hwnd, hmenu, uPos,
                                 IID_PPV_ARGS(&pdto)))) {
  IDropSource *pds = new(std::nothrow) CDropSource();
  if (pds) {
   DWORD dwEffect;
   if (DoDragDrop(pdto, pds, DROPEFFECT_COPY | DROPEFFECT_LINK,
                  &dwEffect) == DRAGDROP_S_DROP) {
    lres = MND_ENDMENU;
   }
   pds->Release();
  }
  pdto->Release();
 }
 return lres;
}

This function is where the magic happens, but it's really not all that magical. We get the data object for the menu item being dragged and tell OLE to do a drag/drop operation with it. Just to make things interesting, I'll say that the menu should be dismissed if the user dropped the object somewhere; otherwise, the menu remains on the screen.

Finally, we hook up the message handler to our window procedure:

HANDLE_MSG(hwnd, WM_MENUDRAG, OnMenuDrag);

And there you have it. A program that calls up a menu with drag enabled. If you drag the item labeled Clock, then the drag/drop operation proceeds as if you were dragging the clock.avi file.

Next time, we'll look at the drop half of drag and drop.

Footnote

¹ I hard-coded the clock.avi file for old time's sake. Yes, I know the file is no longer included with Windows. That'll teach people to use hard-coded paths!

Comments (8)
  1. James says:

    For people using windows 7 where "C:Windowsclock.avi" doesn't exist:

    Relpace this line;

    hr = GetUIObjectOfFile(hwnd, L"C:\Windows\clock.avi",

    with this:

    hr = GetUIObjectOfFile(hwnd, L"C:\Users\Public\Videos\Sample Videos\Wildlife.wmv",

  2. WndSks says:

    If the native menu in 2000 supports both D&D and right-clicks(TPM_RECURSE), why are the startmenu submenus implemented with toolbar+pager? D&D re-order? Hold shift to stay open?

    [For the same reason NASA didn't use the space shuttle to rescue the Apollo 13 astronauts: Windows 2000 didn't exist in 1995. Or because the built-in functionality doesn't quite do everything they needed. -Raymond]
  3. Sunil Joshi says:

    Why was this feature implemted? What was the envisioned use?

    [No idea. Maybe the menu manager folks were jealous of the Start menu. -Raymond]
  4. WndSks says:

    @Raymond: IIRC Win95 used a normal menu and did support right-click, that stuff was added in the IE4 shellupdate/Win98.

  5. WndSks says:

    did NOT (Damn you comment system forcing me to re-type)

  6. Crescens2k says:

    WndSks:

    Even if it was added to the start menu in Win98, that doesn't mean that the native menus supported this kind of thing in Win98.

    As you pointed out, Win98 had to fake it to be able to get this kind of functionality and then the native menu ability to do this was added at a later point.

    As to why the Windows 2000 start menu was still written with the old code, well, if it isn't broke then don't fix it. What they introduced with Win98 worked perfectly well, so without a good reason it would have been daft for them to rewrite the start menu code and potentially introduce more bugs.

  7. Joshua says:

    Facinating. Personally, I think this was for the cascading toolbars, which appeared on the NT branch in 2000. If the IE update that backported it to NT4 backported this also it would make perfect sense.

  8. Anonymous Coward says:

    As for the undiscoverability, this is true to some extent. But a lot of people know that you can drag around icons in the start menu and in the Favourites menu, so if you make your icons look like that, people will try it. By the way, is it really necessary to use owner draw to get that effect or is there an easier way? I've found the standard menu icon functionality doesn't do what I want – the icons invert when I mouseover them.

    [Stay tuned for the exciting conclusion. -Raymond]

Comments are closed.