Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / All-Topics

Display Shell context menu for specific file system entry

0.00/5 (No votes)
17 May 2010LGPL33 min read 10.3K  
ContextMenuWrapper is a class that can generate shell context menu for specific entry(s) (FileSystemInfoEx[]).

Introduction

DirectoryInfoEx has a number of classes that are unrelated to list and basic file operations, I place them in separate classes so they are easier to maintain, because they wrap ShellAPI code, they are called wrappers and placed in Tools\Wrapper directory. ContextMenuWrapper is a class that can generate shell context menu for specific entry(s) (FileSystemInfoEx[]).

ContextMenuWrapper

ContextMenuWrapper has the following methods and events:

  • Popup() method
  • OnMouseHover event
  • OnQueryMenuItems event
  • OnBeforePopup event
  • OnBeforeInvokeCommand event

Popup() Method

C#
public string Popup(FileSystemInfoEx[] items, Point pt)

Popup() is the main method, it generates the context menu based on your specified items and a System.Drawing.Point.

C#
cmw.Popup(new FileSystemInfoEx[] { dir }, new System.Drawing.Point((int)pt.X, (int)pt.Y))

For WPF users, you have to convert it from System.Windows.Point.

Usually it return nothing (null), because:

  • user selected a command that executed internally immediately (before the end of Popup method)
  • user cancelled the context menu

But sometimes it does return a string, because:

  • user selected rename command (return “rename”)
  • user selected a developer generated command (via ExtraMenuItems, return its string)
  • developer specified ContinueInvoke = false in OnBeforeInvokeCommand event

So if it returns a string, then you have to handle it yourself.

OnQueryMenuItems Event

C#
public class QueryMenuItemsEventArgs : EventArgs
{
  public bool QueryContextMenu { get; set; }
  public bool QueryContextMenu2 { get; set; }
  public bool QueryContextMenu3 { get; set; }
  public string[] ExtraMenuItems { get; set; }
  public string[] GrayedItems { get; set; }
  public string[] HiddenItems { get; set; }
  public int DefaultItem { get; set; }
  public FileSystemInfoEx[] SelectedItems { get; private set; }
  ...
}

Context menu has to be queried before popup, before the context menu is queried, OnQueryMenuItem is triggered, allowing you to specify what items to appear (extra or hidden), and their status (default, grayed).

C#
args.GrayedItems = new string[] { "delete", "rename", "cut", "copy" };
args.HiddenItems = new string[] { "link" };

GrayedItems and HiddenItems are the command strings for the items to disable or hide, you can use OnMouseHover event to figure out the command name, although not all commands (especially third party ones) have a command name.

DefaultItem allows you to specify the command ID of the default command, again, use OnMouseHover to find the id, they are consistent in most cases.

ExtraMenuItems are a list of new context menu items. If I redesign the component again, I would make this property a more complex class instead of string. But I still managed to add the required feature using some syntax:

SyntaxMeaningSample
Separator,“—”
\Sub menuItem@”Tools\Add\To…”
[*]Checked“Option1[*]“
&Shortcut“&Add”
C#
string firstCmd = @"Tools\&Add" + (firstOption ? "[*]" : "");
string secondCmd = @"Tools\Remove" + (secondOption ? "[*]" : "");
args.ExtraMenuItems = new string[] { firstCmd, @"Tools\---", secondCmd, "Again", "---" };

Note that ExtraMenuItems is not handled by ContextMenuWrapper, if user selected your command, it will return its command string when Popup() method is finished.

QueryContextMenu, QueryContextMenu2 and QueryContextMenu3 specify to query which interface (IContextMenu, IContextMenu2, IContextMenu3), basically, if you disable QueryContextMenu, all shell context menu items are not shown.

OnBeforePopup Event

C#
public class BeforePopupEventArgs : EventArgs
{
  public IntPtr ptrPopupMenu = IntPtr.Zero;
  public IContextMenu iContextMenu = null;
  public bool ContinuePopup { get; set; }
  public uint DefaultCommandID { get; set; }
  public string DefaultCommand { get; set; }
  ...
}

Right after the context menu is finished with the query, and before popup, OnBeforePopup event is triggered.

  • If you just want to show the context menu, you have nothing to do here.
  • If you want to do your tricks using the generated IContextMenu interface, you have to do it in the event handler, because it will be freed once Popup() function is completed. The most common use is to call a command (e.g. DefaultCommand) directly without showing the context menu:
    C#
    PIDL[] pidls = IOTools.GetPIDL(items, true);
    try
    {
      ContextMenuHelper.InvokeCommand(parent, 
    	IOTools.GetPIDLPtr(pidls), DefaultCommandID, new Point(0,0));
    }
    finally
    {
      IOTools.FreePIDL(pidls);
    }

    ContextMenuHelper.InvokeCommand() method has a number of overloads, you can check the file (ContextMenuWrapper.cs) to find out which is most suitable.

OnMouseHover Event

C#
public class MouseHoverEventArgs : EventArgs
{
  public string Info { get; private set; } //Hint
  public string Command { get; private set; } //Command
  public uint CmdID { get; private set; } //Command ID
  ...
}

When context menu is shown, every time when user selected an item, OnMouseHover event is triggered, along with the information of what is selected, allowing you to know what's going on. You can use Info as a hint in the statusbar. Depending on what is selected, Info and Command are sometimes an empty string (“”).

OnBeforeInvokeCommand Event

C#
public class InvokeCommandEventArgs : MouseHoverEventArgs
{
  public bool ContinueInvoke { get; set; }
  public FileSystemInfoEx[] SelectedItems { get; private set; }

  public string Info { get; private set; } //Hint, derived from MouseHoverEventArgs
  public string Command
   { get; private set; } //Command, derived from MouseHoverEventArgs
  public uint CmdID { get; private set; } //Command ID, derived from MouseHoverEvent
  ..
}

After a user has selected a command, OnBeforeInvokeCommand is triggered. You have to choose to ContinueInvoke or not, if not, you can either run your custom action here (not suggested), or after Popup() returned the command.

ContextMenuWrapper can be found in my DirectoryInfoEx article (0.17).
You can also find a more advanced WPF control in my FileExplorer article (0.4).

This article have been posted on . You can find a list of my articles here.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)