Using Shell Context Menus
Sunday, January 15, 2006
Gregor.WinShell, which brings the Windows Shell to .NET applications, provides the CShellContextMenu class that let's an application retrieve context menu items for any item in the Shell Namespace. This post gives some into how to use it.
Now, asking a shell item (or a group of such) for the context menu is straightforward:
private void Init(){ // create list view, and listen m_ListView = new XListViewEx(); m_ListView.MenuCommand += this.HandleMenuCommand; // create context menu m_ListView.ContextMenu = new ContextMenu(); CMenuItemEx miShell = MenuUtil.CreateItem("Shell Commands", null); miShell.MenuItems.Add(MenuUtil.CreateBreak()); // dummy needed m_ListView.ContextMenu.MenuItems.Add(miShell); // create shell item CShellItem hardDrive = CShellItem.Create(@"C:\"); // ask for the shell context menu m_ShellContextMenu = CShellContextMenu.Create(miShell.Handle, 1, hardDrive); }
If you have multiple shell items, just pass them to the Create() method as additional arguments.
Next, notice that it's the application's responsibility to process the messages sent for a given menu item, namely, WM_COMMAND. That message is sent to the control that hosts the context menu. Thus, message handling is needed.
The Gregor.NET framework provides a few control classes which handle the WM_COMMAND message, and filter out non-menu commands. Those messages that remain result in the MenuCommand event, defined like this (all in the Gregor.UICore namespace):
[Description("Controls that implement this interface handle " + "WM_COMMAND messages for menu commands.")] public interface IMenuCommandSender : IWin32Window { event WndProcEventHandler MenuCommand; } public delegate void WndProcEventHandler( object sender, CWndProcEventArgs e ); public class CWndProcEventArgs : EventArgs { public System.Windows.Forms.Message Message{get;} }
Here's a list of the control classes that implement IMenuCommandSender:
- Gregor.AppCore.ShellControls.XShellListView
- Gregor.AppCore.ShellControls.XShellTreeView
- Gregor.UICore.Controls.XToolbarEx
- Gregor.UICore.Controls.XContainerBar
- Gregor.UICore.Controls.XListViewEx
- Gregor.UICore.Controls.XTreeListView
- Gregor.UICore.Controls.XTreeViewEx
- Gregor.UICore.Docking.XStripLabel
For other controls, either use subclassing via the NativeWindow class (in System.Windows.Forms), or derive a class, overriding the WndProc() method like this:
protected override void WndProc(ref Message m){ if(m.Msg == User32.WM_COMMAND){ // low-order word is the menu ID // (high-order word is zero for WM_COMMAND) if((m.WParam.ToInt32() & 0xFFFF0000) == 0){ // ... raise event, or invoke the menu (see below) } } base.WndProc(ref m); }
On the context menus of these controls, you can add Shell context menu items like shown above. Here's how to handle the MenuCommand event:
private void HandleMenuCommand(object sender, CWndProcEventArgs e){ // low-order word is the menu ID // (high-order word is zero for WM_COMMAND) int menuID = e.Message.WParam.ToInt32(); // filter out messages for non-shell menu items if(false == MenuUtil.IsMenuIdUsed(menuId, m_ListView.ContextMenu)){ // form handle needed for dialogs IntPtr hWndOwner = this.Handle; // invoke command by menu ID m_ShellContextMenu.Invoke(menuID, hWndOwner); } }
All menu items in the context menu that are not populated by the Shell must be of type Gregor.UICore.CMenuItemEx in order for the ID filtering to work - CMenuItemEx provides access to the otherwise protected MenuItem.MenuID property.
Wrapping It Up
Wouldn't it be nice if you could have a centralized event handler for the command messages? In WebEdit.NET, I used the existing extensibility mechanism of Context Menu Providers to do so. Have a look at the CShellContextMenuProvider class, which dynamically tests for the presence of the IMenuCommandSender interface, and then manages event handlers on the fly. This is possible because only on context menu can be active at the time.
With the new Context Menu Provider, WebEdit.NET has gained the ability to display the Shell's context menu anywhere a shell item (or serveral items) can be obtained from existing path information. Examples include the Favorites, History, and Project tool windows, the editor window, document window tab strips or title bars, and even toolbar buttons containing document links.
My favorite way of using the Shell context menu in WebEdit is copying file information to the clipboard for later pasting in the Shell.