*
MSDN*
Results by Bing
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > April 1997
April 1997


Code for this article: Wicked0497.exe (7KB)
Jeff Prosise is the author of Programming Windows 95 with MFC (Microsoft Press, 1996). He also teaches Visual C++®/MFC programming seminars. For more information, visit http://www.solsem.com.

One of the features of Windows® 95 and Windows NT® 4.0 that users quickly grow accustomed to is that right-clicking an item in a shell folder or Explorer window invokes a context menu with a concise list of actions that may be performed on that item. Many programmers find out the hard way that if their application displays files, folders, and other file system objects, it should also support Explorer-like context menus. Otherwise, their users might wonder why right-clicking a readme.txt file icon in Explorer pops up a context menu, but right-clicking an identical icon in their application does not.
Given an IShellFolder pointer to the folder in which an item resides and a pointer to an item ID list (PIDL) identifying the item itself, it's relatively easy to display a context menu identical to the one in Explorer. The basic code, without error checking, is shown in Figure 1. First, the folder's IShellFolder::GetUIObjectOf function is called with IContextMenu's interface ID and a pointer to the PIDL. On return, pContextMenu holds a pointer to the item's IContextMenu interface. Next, a context menu is created with CreatePopupMenu, initialized with IContextMenu::QueryContextMenu, and displayed with TrackPopupMenu. A nonzero return from TrackPopupMenu means you selected a command from the context menu (as opposed to dismissing it without selecting anything), in which case IContextMenu::InvokeCommand is called to execute the command.
If you've never written code that deals with objects in the shell's namespace, Figure 1 probably looks a little intimidating. But take my word for it: once you've used IShellFolder and other COM-based shell interfaces a few times, it'll seem pretty straightforward. The good news is that you don't have to understand the nuts and bolts of COM and shell interfaces to display an Explorer-like context menu; you can simply use the code in Figure 1. The bad news is that before you can use my code, you'll have to initialize psfFolder with a pointer to the proper IShellFolder interface and ppidl with a pointer to a PIDL. And that, unfortunately, can be difficult.
To make matters easier, I've written a C-style function named DoExplorerMenu that takes a path name to an arbitrary file or folder and displays Explorer's context menu for that item. If you select a command from the menu, the command is executed, too. DoExplorerMenu makes it simple to add Explorer-like context menu support to applications that display file system objects because it reduces the amount of code that you have to write to a single function call. My code does the rest, including the not-so-fun task of converting the path name into a PIDL and an IShellFolder pointer.

The ExpMenu Application

The ExpMenu application in Figure 2 places a list view control inside a top-level window and initializes the list view with items representing the files and folders found in the host PC's Windows directory. When an item from the list view is clicked with the right mouse button, the item's Explorer context menu is displayed (see Figure 3). Selecting a command from the menu executes the command just as if it had been selected from a real Explorer menu.

Figure 3 An Explorer context menu
Figure 3 An Explorer context menu

There's nothing remarkable about ExpMenu's source code except for the fact that it includes the source code for DoExplorerMenu and a trio of helper functions named GetItemCount, GetNextItem, and DuplicateItem. You can import these functions into your own applications with good old cut and paste, or you can compile them to create a separate OBJ file and link them into your EXE. Then you can display the Explorer context menu for a file named Foo.txt that's stored in the FooFiles folder of drive C by calling DoExplorerMenu:
DoExplorerMenu (hwnd, "C:\\FooFiles\\Foo.txt", point);
hwnd is the handle of the window that will own the menu (the window whose handle is passed to TrackPopupMenu), and point is a POINT structure whose x and y coordinates specify where the context menu will be positioned; specifically, where its upper-left corner will be located. point should contain client coordinates, not screen coordinates. DoExplorerMenu returns a nonzero value if the menu is successfully displayed and a command is successfully selected and executed. A zero return means either the menu couldn't be displayed (which shouldn't happen unless you specify an invalid path name) or the user dismissed the menu without selecting a command.
How does DoExplorerMenu work? The flow of logic goes something like this. First, call the Win32
® GetFullPathName API to make sure the path name is fully qualified (that is, it specifies a complete path from the root directory of a drive). Also, convert the path name to Unicode characters (if it isn't Unicode already) because IShellFolder::ParseDisplayName expects a Unicode text string.
Next, use the shell APIs SHGetMalloc and SHGetDesktopFolder to get an IMalloc pointer for the shell and an IShellFolder pointer for the desktop. The IMalloc interface is used by DuplicateItem to allocate memory. The IShellFolder interface is used to call IShellFolder functions used by DoExplorerMenu. Then, use IShellFolder::ParseDisplay Name to convert the path from the item we're interested in to an item ID list, which is simply an array of SHITEMID structures describing the item and its location relative to the shell's desktop folder. Walk this list of item IDs, alternately extracting each item ID from the list with DuplicateItem and binding to it with IShellFolder::BindToObject, until psfFolder holds an IShellFolder pointer for the folder that contains the item in question (the item's "container") and pidlItem points to the item ID for the item itself.
Next, get a pointer to the container's IContextMenu interface with IShellFolder::GetUIObjectOf, then create an empty popup menu and call IContextMenu::QueryContextMenu to allow Explorer to fill it with items. Convert the coordinates in the POINT structure passed to DoExplorerMenu to screen coordinates with ClientToScreen, and display the menu with TrackPopupMenu. Call TrackPopupMenu with a TPM_RETURNCMD flag so it will return the integer ID of the item, if any, that the user selects from the menu. (TPM_RETURNCMD is an undocumented flag, but feel free to use it because it was inadvertently omitted from the Win32 documentation.) If TrackPopupMenu returns a nonzero value indicating that a command was selected from the menu, execute the command by initializing a CMINVOKECOMMANDINFO structure and calling IContextMenu::InvokeCommand. This may sound like a lot of work for you, the programmer, but it's not—DoExplorerMenu does it all for you.
One quirk in ExpMenu is that, if an item is created, renamed, or deleted as a result of a context menu operation, the list view doesn't show the Windows directory's updated contents. That's because ExpMenu doesn't update the list view when DoExplorerMenu returns. This brings to light an important point: when DoExplorerMenu returns, you have no idea what the user might have done through the context menu. Therefore, you should assume the worst and refresh anything—your application's window or its internal data structures, for example—that might be affected by an action invoked through the context menu. DoExplorerMenu could be rewritten to return the ID of the command that the user selected, but chances are the ID would only be meaningful to Explorer. Even if you were to make a list of the undocumented IDs that Explorer assigns to its menu items, you couldn't account for items added by third-party context menu shell extensions.
Since it's almost impossible to work backwards from an Explorer menu item ID and determine what the menu item does, you must resort to other methods. In ExpMenu's case, one option would be to erase all the items in the list view and call PopulateListView each time DoExplorerMenu returned. That would result in a lot of unnecessary flashing, so it's hardly an ideal solution. A better solution would be to compare the items in the list view to the files and folders in the Windows directory and add or delete items as necessary.
A more elegant solution would be to create a background thread to monitor events in the file system and have it block on a file change notification object associated with the target directory. The thread would be awakened when a file or folder was created, renamed, or deleted, and it could update the list view accordingly. This approach would conserve CPU cycles because it would restrict the updating of the list view control to times when the contents of the folder have changed. For an example of how background threads and file change notification objects may be used to monitor file system activity, refer to the Wicked Code column in the October 1996 issue of MSJ.

What About Multiple Items?

DoExplorerMenu does not provide a solution for displaying an Explorer context menu for more than one item. You can see what I mean by bringing up the Windows 95 (or Windows NT 4.0) Find utility and searching on *.* to generate a list of every file and folder on, say, drive C. Once the list is displayed, select several objects of different types (for example, a .TXT file, an .XLS file, and a folder) and then right-click one of the items. A composite context menu forms from commands in the items' individual context menus.
Unfortunately, there is not a convenient mechanism (at least none that I know of) to build a composite context menu using operating system-supplied functions. For example, there is no magic way to call QueryContextMenu and have it fill in a context menu pertaining to multiple items. When I asked Microsoft how they did it, the response I got was something like "Well, we kludged it up with some code that we're not too proud of, and our code also relies on some information that's available only to Explorer, so don't try this at home."
The obvious solution is to display Explorer menus for just one item at a time. If you select multiple items and click one of them with the right mouse button, either the context menu for that item should be displayed or nothing should be displayed. You might get clever and call IContextMenu:: QueryContextMenu once for each item (each time using a separate HMENU) and then combine all the unique items into one composite menu, but I wouldn't go to that much trouble unless I had to. Let your conscience be your guide. Meanwhile, cross your fingers and hope that this is an omission Microsoft will remedy in a future version of Windows.

Your Needs, Your Ideas

Are there tough Win32 programming questions you'd like to see answered in this column? If so, email me at the address listed below. I regret that time doesn't permit me to respond individually to all questions, but rest assured that I'll read every one and consider each for inclusion in a future installment of Wicked Code.

Have a tricky issue dealing with Windows? Send your questions via email to Jeff Prosise: 72241.44@compuserve.com

From the April 1997 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1997 Microsoft Corporation. All rights reserved. Legal Notices.

© 2015 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft