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:
OnMouseHover
event OnQueryMenuItems
event OnBeforePopup
event OnBeforeInvokeCommand
event
Popup() Method
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
.
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
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).
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:
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", "---" };
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
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:
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; }
public string Command { get; private set; }
public uint CmdID { get; private set; }
...
}
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
public class InvokeCommandEventArgs : MouseHoverEventArgs
{
public bool ContinueInvoke { get; set; }
public FileSystemInfoEx[] SelectedItems { get; private set; }
public string Info { get; private set; }
public string Command
{ get; private set; }
public uint CmdID { get; private set; }
..
}
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 CodeProject. You can find a list of my articles here.