Introduction



DirectoryInfoEx
has a number of classes thats unrelated to list and basic file operations, I place them in separate class so they are easier to maintain, because they wrap ShellAPI codes, they are called wrapper 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

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

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

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

For WPF user, 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 cancel the context menu.

But sometimes it do return a string, because

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

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

OnQueryMenuItems event

 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, allow you to specify what items to appear (extra or hidden), and their status (default, grayed).

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

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

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

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

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

Noted that ExtraMenuItems are not handled by ContextMenuWrapper, if user selected your command, it will return it’s 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 item are not shown.

OnBeforePopup event

 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 query, and before popup, OnBeforePopup event is triggered,

  • If you just want to show the context menu, you 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 :

    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

 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, allow you to know whats going on.
You can use Info as hint in statusbar.  Depending on what is selected, Info and Command is sometimes an empty string (“”).

OnBeforeInvokeCommand event

 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 is 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.