[Journal - Using Shell Context Menus]

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:

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.