Using the MNS_DRAGDROP style: Dropping in


Last time, we looked at using the MNS_DRAG­DROP style for dragging items out of a menu. Today, we'll look at dropping them in.

Take the program from last time and make the following additions. First, let's add a second item to the menu.

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

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

// scratch.cpp
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;
  case IDC_WMP:
   hr = GetUIObjectOfFile(hwnd, L"C:\\Program Files"
                  L"\\Windows Media Player\\wmplayer.exe",
                                             riid, ppvOut);
   break;
  }
 }
 return hr;
}

Yes, I hard-coded another path. This is a demo, not production code.

Anyway, it's time to hook up the WM_MENU­GET­OBJECT message:

#define HANDLE_WM_MENUGETOBJECT(hwnd, wParam, lParam, fn) \
 (fn)((hwnd), (MENUGETOBJECTINFO*)(lParam))

LRESULT OnMenuGetObject(HWND hwnd, MENUGETOBJECTINFO *pmgoi)
{
 LRESULT lres = MNGO_NOINTERFACE;
 if (!(pmgoi->dwFlags & (MNGOF_BOTTOMGAP | MNGOF_TOPGAP)) &&
     SUCCEEDED(GetMenuObject(hwnd, pmgoi->hmenu, pmgoi->uPos,
               *(IID*)pmgoi->riid, &pmgoi->pvObj))) {
  lres = MNGO_NOERROR;
 }
 return lres;
}
    HANDLE_MSG(hwnd, WM_MENUGETOBJECT, OnMenuGetObject);

To handle the WM_MENU­GET­OBJECT message, you convert the hmenu, uPos pair into a COM object, requesting the interface provided by the riid member, and putting the result into the pvObj member. (Exercise: Why is the riid member typed as void * rather than REFIID?)

When the user tries to drop on a menu item, we just give them the corresponding object in the shell namespace. Notice that I filter out the GAP messages, since they indicate that the user is trying to drop between items rather than on them.

Run this program, open the Test menu, and drag the Clock menu item onto the WMP menu item. If all goes well (assuming you changed the path for clock.avi to some other AVI file), the AVI file will be opened by Windows Media Player, since that's the behavior of Windows Media Player when you drop an AVI file on it.

So that's menu drag/drop. It's really not all that exciting. Of course, what people tend to be most interested in is not generic drag/drop for menus but menu customization via drag/drop. That's not something that MNS_DRAG­DROP gives you directly; that's something you need to build yourself out of the building blocks provided.

We'll snap some blocks together next time.

Comments (8)
  1. 1st says:

    You imply that there is a good reason to define a pointer that by definition (and by name) should always points to an IID as PVOID. Makes no sense.

    [Hence the exercise. -Raymond]
  2. Mike Dunn says:

    It's void* because the struct has to be usable by languages other than C++. Also, REFIID is defined differently based on whether __cplusplus is defined, so the riid member had to be of a type whose definition will never change.

    Can you prevent the drop indicator from appearing in the menu? Even when the WM_MENUGETOBJECT handler returns MNGO_NOINTERFACE, the indicator is still drawn, which always struck me as odd behavior.

  3. Crescens2k says:

    Most likely, since in guiddef.h in the Windows SDK 6.0A, 7.0A and 7.1, REFIID is defined as

    #ifndef _REFIID_DEFINED

    #define _REFIID_DEFINED

    #ifdef __cplusplus

    #define REFIID const IID &

    #else

    #define REFIID const IID * __MIDL_CONST

    #endif

    #endif

    So unless something has changed between 7.1 and the Windows Developer Preview, then that is what has been in the Windows SDK since at least 6 and most likely longer.

  4. Anonymous Coward says:

    Which is also horrible. Seriously, how hard can it be to type a simple typedef? Why pollute the global namespace with yet another macro?

  5. Crescens2k says:

    Who knows, maybe it is just another case of judging old things by modern standards again. OLE2 and COM was introduced in 1993 so this means the compilers in use would have been VC++ 1 or VC++ 2 or maybe older versions like Microsoft C/C++ 7.0? Whats more, iirc they used ODL back then not MIDL, so again, maybe there was a need to do it this way back then?

  6. Joshua says:

    They're ALL CAPS names. That tells you they're probably macros.

  7. Anonymous Coward says:

    Michael, that makes no sense. The struct definition can only be used in C and C++. Even if there may be a difference* between REFIID in C and C++, it should still be possible to define it as IID const *. Other languages will need their own definition anyway, and if a hypothetical language only has e.g. POINTER and not POINTER TO IID then that will affect the struct definition for that language, but not for the C / C++ header file. (Assuming POINTER is compatible with <type> * in C / C++. This is generally true, but otherwise they may even have to make do with DWORD rather than POINTER…)

    *Note: the headers that I have, which are not the most recent ones, contain the following definitions (irrelevant bits cut out):

    typedef IID *REFIID;

    #ifndef _REFGUID_DEFINED

    #if defined (__cplusplus) && !defined (CINTERFACE)

    #define REFIID const IID&

    #else

    #define REFIID const IID* const

    #endif

    #endif

    Sigh. I'm not pointing fingers though; I don't know whose idea it was to do it like this; it may have even been the folks who shipped the compiler.

  8. 1st says:

    That explains why it is not REFIID, but not why it is PVOID rather than const IID*

Comments are closed.

Skip to main content