Holy cow, I wrote a book!
Windows 2000 introduced the MNS_DRAGDROP 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.
MNS_DRAGDROP
Mind you, I knew nothing about the MNS_DRAGDROP 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_MENUDRAG and WM_MENUGETOBJECT messages. The WM_MENUDRAG 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.
WM_MENUDRAG
WM_MENUGETOBJECT
Simple enough. Let's do it.
Start with the scratch program, add the function GetUIObjectOfFile and the class CDropSource, and change the calls to CoInitialize and CoUninitialize into OleInitialize and OleUninitialize, respectively. Next, define the menu we're going to play with:
GetUIObjectOfFile
CDropSource
CoInitialize
CoUninitialize
OleInitialize
OleUninitialize
// 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.
C:\Windows\clock.avi
Now we can hook up a handler to the WM_MENUDRAG 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.
clock.avi
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!