This article explains how to use
ContextMenuHelperEx
in
DirectoryInfoEx
to simplify
ContextMenu
construction under .NET 4.0 Framework.
Introduction
DirectoryInfoEx
includes a class named ContextMenuHelperEx
, which can create menu items using ShellAPI
like InsertMenuItem()
and SetMenuItemBitmap()
. It’s originally designed for use in a Window Forms or WPF application.
As .NET 4.0 Framework allows running multiple runtime in a process, constructing context menu is now possible. All-In-One Code Framework included a demo (CSShellExtContextMenuHandler
), it works great, except the demo doesn't provide sample for icon and cascade menu, and it will be difficult to maintain as the menu grows.
So instead of writing it all over again, I found it is easier to use the ContextMenuHelperEx
to do the work.
ContextMenuHelperEx
includes a method named ConstructCustomMenu()
:
public static void ConstructCustomMenu(IntPtr menu, CustomMenuStructure[] customItems,
uint idCmdFirst, out List<IntPtr> menuPtrConstructed, out List<IntPtr> imgPtrConstructed)
ConstructCustomMenu()
method takes a CustomMenuStructure
, which is an ISerializable
that can be stored in harddrive using BinaryFormatter
/XmlFormatter
. I have included a very basic editor for this.
CustomMenuStructure
includes Text , ToolTip, ID, Command, Checked, Items, Icon property.
- Text property specifies the header of the menuitem.
- ID identifies the item in the menu.
- Checked specifies to display checked marks or not.
- Items specifies the sub items.
- Icon, if not null, stores the Bitmap of the menu. (16×16)
- ToolTip and Command are ignored by the
ConstructCustomMenu()
command.
ConstructCustomMenu()
returns the pointer of Sub menu and Icon’s HBitmap
constructed, when they are no longer required, they should to be freed DestroyMenu()
and DeleteObject()
.
How to Use?
You should read the readme.txt in the CSShellExtContextMenuHandler
demo first.
public int QueryContextMenu(IntPtr hMenu, uint iMenu, uint idCmdFirst,
uint idCmdLast, uint uFlags)
{
cmsRoot = new CustomMenuStructure("Hello", 0);
cmsRoot.Icon = new Bitmap(
this.GetType().Assembly.GetManifestResourceStream("TestContextMenuProj.Bitmap1.bmp")
as Stream);
cmsRoot.Items.Add(new CustomMenuStructure("World", 1));
IDLookupDic.Add(0, cmsRoot);
IDLookupDic.Add(1, cmsRoot.Items[0]);
ContextMenuHelperEx.ConstructCustomMenu(hMenu,
new CustomMenuStructure[] { cmsRoot }, idCmdFirst,
out menuPtrConstructed, out imgPtrConstructed);
return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0,
(uint)IDLookupDic.Count + 1);
}
In QueryContextMenu
, you have to prepare the CustomMenuStruture
, either load from a binary file, or hardcode it. IDLookupDic
is a dictionary that holds the structures indexed by ID.
public void GetCommandString(UIntPtr idCmd, uint uFlags, IntPtr pReserved,
StringBuilder pszName, uint cchMax)
{
uint id = idCmd.ToUInt32();
if (IDLookupDic.ContainsKey(id) &&
!String.IsNullOrEmpty(IDLookupDic[id].Tooltip))
{
switch ((GCS)uFlags)
{
case GCS.GCS_HELPTEXTW:
if (this.IDLookupDic[id].Tooltip.Length > cchMax - 1)
{
Marshal.ThrowExceptionForHR(WinError.STRSAFE_E_INSUFFICIENT_BUFFER);
}
else
{
pszName.Clear();
pszName.Append(this.IDLookupDic[id].Tooltip);
}
break;
}
}
}
This is not compulsory to implement, you can return help text based on the requested id.
public void InvokeCommand(IntPtr pici)
{
CMINVOKECOMMANDINFO ici = (CMINVOKECOMMANDINFO)Marshal.PtrToStructure(pici,
typeof(CMINVOKECOMMANDINFO));
uint id = (uint)(NativeMethods.LowWord(ici.lpVerb.ToInt32()));
if (IDLookupDic.ContainsKey(id))
{
saveDroppedFileList();
}
else
Marshal.ThrowExceptionForHR(WinError.E_FAIL);
}
The saveDroppedFileList()
method saves the selected file names to a temporary file, and passes the temporary file name instead of all selected file names. You can then execute your main application using Process.Start()
method.
Reference
This article has been posted on CodeProject. You can find a list of my articles here.