diff --git a/Windows.Shell/ShellObjects/ShellContextMenu.cs b/Windows.Shell/ShellObjects/ShellContextMenu.cs
index 5c949d39..75aa5e93 100644
--- a/Windows.Shell/ShellObjects/ShellContextMenu.cs
+++ b/Windows.Shell/ShellObjects/ShellContextMenu.cs
@@ -89,7 +89,7 @@ namespace Vanara.Windows.Shell
}
/// Gets the underlying COM interface.
- public IContextMenu ComInterface { get; set; }
+ public IContextMenu ComInterface { get; private set; }
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
public void Dispose()
@@ -99,6 +99,30 @@ namespace Vanara.Windows.Shell
System.GC.SuppressFinalize(this);
}
+ /// Gets the help text for a specified command.
+ /// The menu command identifier offset.
+ /// The help text value if available; otherwise .
+ public string GetHelpTextForCommand(int command) => GetCommandString(command, GCS.GCS_HELPTEXTW);
+
+ /// Gets the icon location for a specified command.
+ /// The menu command identifier offset.
+ /// The icon location if available; otherwise .
+ public IconLocation GetIconLocationForCommand(int command) => IconLocation.TryParse(GetCommandString(command, GCS.GCS_VERBICONW), out var l) ? l : null;
+
+ /// Gets the verb for a specified command.
+ /// The menu command identifier offset.
+ /// The verb if available; otherwise .
+ public string GetVerbForCommand(int command) => GetCommandString(command, GCS.GCS_VERBW);
+
+ /// Gets the information of all the menu items supported by the underlying interface.
+ /// The menu item information.
+ public MenuItemInfo[] GetItems(CMF menuOptions = CMF.CMF_EXTENDEDVERBS)
+ {
+ using var hmenu = CreatePopupMenu();
+ ComInterface.QueryContextMenu(hmenu, 0, m_CmdFirst, int.MaxValue, menuOptions).ThrowIfFailed();
+ return MenuItemInfo.GetMenuItems(hmenu, this);
+ }
+
/// Handles context menu messages when the is displayed on a Form's main menu bar.
///
///
@@ -149,6 +173,82 @@ namespace Vanara.Windows.Shell
return false;
}
+ /// Invokes the command.
+ ///
+ /// The address of a null-terminated string that specifies the language-independent name of the command to carry out. This member is
+ /// typically a string when a command is being activated by an application. The system provides predefined constant values for the
+ /// following command strings.
+ ///
+ /// If a canonical verb exists and a menu handler does not implement the canonical verb, it must return a failure code to enable the
+ /// next handler to be able to handle this verb.Failing to do this will break functionality in the system including ShellExecute.
+ ///
+ ///
+ /// Alternatively, rather than a pointer, this parameter can be MAKEINTRESOURCE(offset) where offset is the menu-identifier offset
+ /// of the command to carry out. Implementations can use the IS_INTRESOURCE macro to detect that this alternative is being employed.
+ /// The Shell uses this alternative when the user chooses a menu command.
+ ///
+ ///
+ /// A set of values to pass to the ShowWindow function if the command displays a window or starts an application.
+ ///
+ /// A handle to the window that is the owner of the shortcut menu. An extension can also use this handle as the owner of any message
+ /// boxes or dialog boxes it displays. Callers must specify a legitimate HWND that can be used as the owner window for any UI that
+ /// may be displayed. Failing to specify an HWND when calling from a UI thread (one with windows already created) will result in
+ /// reentrancy and possible bugs in the implementation of this call.
+ ///
+ /// If supplied, the point where the command is invoked.
+ ///
+ /// The implementation can spin off a new thread or process to handle the call and does not need to block on completion of the
+ /// function being invoked. For example, if the verb is "delete" the call may return before all of the items have been deleted.
+ /// Since this is advisory, calling applications that specify this flag cannot guarantee that this request will be honored if they
+ /// are not familiar with the implementation of the verb that they are invoking.
+ ///
+ ///
+ /// If , the SHIFT key is pressed. Use this instead of polling the current state of the keyboard that may have
+ /// changed since the verb was invoked.
+ ///
+ ///
+ /// If , the CTRL key is pressed. Use this instead of polling the current state of the keyboard that may have
+ /// changed since the verb was invoked..
+ ///
+ /// An optional keyboard shortcut to assign to any application activated by the command.
+ ///
+ /// If , indicates that the method might want to keep track of the item being invoked for features like the
+ /// "Recent documents" menu.
+ ///
+ ///
+ /// Do not perform a zone check. This flag allows ShellExecuteEx to bypass zone checking put into place by IAttachmentExecute.
+ ///
+ public void InvokeCommand(ResourceId verb, ShowWindowCommand show = ShowWindowCommand.SW_SHOWNORMAL, HWND parent = default,
+ Point? location = default, bool allowAsync = false, bool shiftDown = false,
+ bool ctrlDown = false, uint hotkey = 0, bool logUsage = false, bool noZoneChecks = false)
+ {
+ var invoke = new CMINVOKECOMMANDINFOEX
+ {
+ cbSize = (uint)Marshal.SizeOf(typeof(CMINVOKECOMMANDINFOEX)),
+ hwnd = parent,
+ fMask = (parent.IsNull ? CMIC.CMIC_MASK_FLAG_NO_UI : 0) | (hotkey != 0 ? CMIC.CMIC_MASK_HOTKEY : 0),
+ lpVerb = verb,
+ nShow = show,
+ dwHotKey = hotkey,
+ };
+ if (allowAsync) invoke.fMask |= CMIC.CMIC_MASK_ASYNCOK;
+ if (shiftDown) invoke.fMask |= CMIC.CMIC_MASK_SHIFT_DOWN;
+ if (ctrlDown) invoke.fMask |= CMIC.CMIC_MASK_CONTROL_DOWN;
+ if (logUsage) invoke.fMask |= CMIC.CMIC_MASK_FLAG_LOG_USAGE;
+ if (noZoneChecks) invoke.fMask |= CMIC.CMIC_MASK_NOZONECHECKS;
+ if (location.HasValue)
+ {
+ invoke.ptInvoke = location.Value;
+ invoke.fMask |= CMIC.CMIC_MASK_PTINVOKE;
+ }
+ if (!verb.IsIntResource)
+ {
+ invoke.lpVerbW = (string)verb;
+ invoke.fMask |= CMIC.CMIC_MASK_UNICODE;
+ }
+ ComInterface.InvokeCommand(invoke);
+ }
+
/// Invokes the Copy command on the shell item(s).
public void InvokeCopy() => InvokeVerb("copy");
@@ -182,15 +282,25 @@ namespace Vanara.Windows.Shell
/// Invokes the specified verb on the shell item(s).
/// The verb to invoke.
/// Flags that specify how to display any opened window.
- public void InvokeVerb(string verb, ShowWindowCommand show = ShowWindowCommand.SW_NORMAL)
+ ///
+ /// A handle to the window that is the owner of the shortcut menu. An extension can also use this handle as the owner of any message
+ /// boxes or dialog boxes it displays. Callers must specify a legitimate HWND that can be used as the owner window for any UI that
+ /// may be displayed. Failing to specify an HWND when calling from a UI thread (one with windows already created) will result in
+ /// reentrancy and possible bugs in the implementation of this call.
+ ///
+ public void InvokeVerb(string verb, [Optional] ShowWindowCommand show, [Optional] HWND parent) =>
+ InvokeCommand(new SafeResourceId(verb), show, parent);
+
+ /// Shows a context menu for a shell item.
+ /// The position on the screen that the menu should be displayed at.
+ /// The options that determine which items are requested from .
+ public void ShowContextMenu(Point pos, CMF menuOptions = CMF.CMF_EXTENDEDVERBS)
{
- var invoke = new CMINVOKECOMMANDINFOEX
- {
- cbSize = (uint)Marshal.SizeOf(typeof(CMINVOKECOMMANDINFOEX)),
- lpVerb = new SafeResourceId(verb, CharSet.Ansi),
- nShow = show
- };
- ComInterface.InvokeCommand(invoke);
+ using var hmenu = CreatePopupMenu();
+ ComInterface.QueryContextMenu(hmenu, 0, m_CmdFirst, int.MaxValue, menuOptions).ThrowIfFailed();
+ var command = TrackPopupMenuEx(hmenu, TrackPopupMenuFlags.TPM_RETURNCMD, pos.X, pos.Y, m_MessageWindow.Handle);
+ if (command > 0)
+ InvokeCommand((int)command - m_CmdFirst);
}
/// Releases unmanaged and - optionally - managed resources.
@@ -212,37 +322,26 @@ namespace Vanara.Windows.Shell
}
}
- private void InvokeCommand(int index)
+ private string GetCommandString(ResourceId command, GCS stringType)
{
- var invoke = new CMINVOKECOMMANDINFOEX(index) { nShow = ShowWindowCommand.SW_SHOWNORMAL };
- m_ComInterface2.InvokeCommand(invoke);
+ using var mStr = new SafeCoTaskMemString(4096);
+ try { ComInterface.GetCommandString(command, stringType, default, mStr, mStr.Size / 2U); }
+ catch { return null; }
+ return mStr.ToString();
}
#if !NET5_0
/// Populates a with the context menu items for a shell item.
/// The menu to populate.
- /// The flags to pass to .
+ /// The flags to pass to .
///
/// If this method is being used to populate a Form's main menu then you need to call in the Form's
/// message handler.
///
- public void Populate(Menu menu, CMF menuFlags = CMF.CMF_NORMAL)
+ public void Populate(Menu menu, CMF menuOptions = CMF.CMF_NORMAL)
{
RemoveShellMenuItems(menu);
- ComInterface.QueryContextMenu(menu.Handle, 0, m_CmdFirst, int.MaxValue, menuFlags);
- }
-
- /// Shows a context menu for a shell item.
- /// The position on the screen that the menu should be displayed at.
- public void ShowContextMenu(Point pos)
- {
- using var menu = new ContextMenu();
- Populate(menu);
- var command = TrackPopupMenuEx(menu.Handle, TrackPopupMenuFlags.TPM_RETURNCMD, pos.X, pos.Y, m_MessageWindow.Handle);
- if (command > 0)
- {
- InvokeCommand((int)command - m_CmdFirst);
- }
+ ComInterface.QueryContextMenu(menu.Handle, 0, m_CmdFirst, int.MaxValue, menuOptions);
}
private void RemoveShellMenuItems(Menu menu)
@@ -260,7 +359,7 @@ namespace Vanara.Windows.Shell
// First, tag the managed menu items with an arbitary value (0xAB).
TagManagedMenuItems(menu, tag);
- var remove = new List();
+ var remove = new Stack();
var count = GetMenuItemCount(menu.Handle);
for (uint n = 0; n < count; ++n)
{
@@ -269,37 +368,172 @@ namespace Vanara.Windows.Shell
if (itemInfo.hSubMenu.IsNull)
{
// If the item has no submenu we can't get the tag, so check its ID to determine if it was added by the shell.
- if (itemInfo.wID >= m_CmdFirst) remove.Add(n);
+ if (itemInfo.wID >= m_CmdFirst) remove.Push(n);
}
else
{
GetMenuInfo(itemInfo.hSubMenu, ref menuInfo);
- if ((int)menuInfo.dwMenuData != tag) remove.Add(n);
+ if ((int)menuInfo.dwMenuData != tag) remove.Push(n);
}
}
// Remove the unmanaged menu items.
- remove.Reverse();
- foreach (var position in remove)
- {
- DeleteMenu(menu.Handle, (uint)position, MenuFlags.MF_BYPOSITION);
- }
+ while (remove.Count > 0)
+ DeleteMenu(menu.Handle, remove.Pop(), MenuFlags.MF_BYPOSITION);
}
private void TagManagedMenuItems(Menu menu, int tag)
{
- var info = new MENUINFO();
- info.cbSize = (uint)Marshal.SizeOf(info);
- info.fMask = MenuInfoMember.MIM_MENUDATA;
- info.dwMenuData = (UIntPtr)tag;
+ var info = new MENUINFO
+ {
+ cbSize = (uint)Marshal.SizeOf(typeof(MENUINFO)),
+ fMask = MenuInfoMember.MIM_MENUDATA,
+ dwMenuData = (UIntPtr)tag
+ };
- foreach (MenuItem item in menu.MenuItems)
+ foreach (Menu item in menu.MenuItems)
{
SetMenuInfo(item.Handle, info);
}
}
+
#endif
+ /// Provides information about a single menu entry discovered in a native menu.
+ public class MenuItemInfo
+ {
+ internal MenuItemInfo(HMENU hMenu, uint idx)
+ {
+ using var strmem = new SafeHGlobalHandle(512);
+ var mii = new MENUITEMINFO
+ {
+ cbSize = (uint)Marshal.SizeOf(typeof(MENUITEMINFO)),
+ fMask = MenuItemInfoMask.MIIM_ID | MenuItemInfoMask.MIIM_SUBMENU | MenuItemInfoMask.MIIM_FTYPE | MenuItemInfoMask.MIIM_STRING | MenuItemInfoMask.MIIM_STATE | MenuItemInfoMask.MIIM_BITMAP,
+ fType = MenuItemType.MFT_STRING,
+ dwTypeData = (IntPtr)strmem,
+ cch = strmem.Size / (uint)StringHelper.GetCharSize()
+ };
+ Win32Error.ThrowLastErrorIfFalse(GetMenuItemInfo(hMenu, idx, true, ref mii));
+ Id = unchecked((int)mii.wID);
+ Text = mii.fType.IsFlagSet(MenuItemType.MFT_SEPARATOR) ? "-" : mii.fType.IsFlagSet(MenuItemType.MFT_STRING) ? strmem.ToString(-1, CharSet.Auto) : "";
+ Type = mii.fType;
+ State = mii.fState;
+ BitmapHandle = mii.hbmpItem;
+ SubMenus = GetMenuItems(mii.hSubMenu);
+ }
+
+ ///
+ /// A handle to the bitmap to be displayed, or it can be one of the values in the following table.
+ ///
+ ///
+ /// Value
+ /// Meaning
+ ///
+ /// -
+ /// HBMMENU_CALLBACK ((HBITMAP) -1)
+ ///
+ /// A bitmap that is drawn by the window that owns the menu. The application must process the WM_MEASUREITEM and WM_DRAWITEM messages.
+ ///
+ ///
+ /// -
+ /// HBMMENU_MBAR_CLOSE ((HBITMAP) 5)
+ /// Close button for the menu bar.
+ ///
+ /// -
+ /// HBMMENU_MBAR_CLOSE_D ((HBITMAP) 6)
+ /// Disabled close button for the menu bar.
+ ///
+ /// -
+ /// HBMMENU_MBAR_MINIMIZE ((HBITMAP) 3)
+ /// Minimize button for the menu bar.
+ ///
+ /// -
+ /// HBMMENU_MBAR_MINIMIZE_D ((HBITMAP) 7)
+ /// Disabled minimize button for the menu bar.
+ ///
+ /// -
+ /// HBMMENU_MBAR_RESTORE ((HBITMAP) 2)
+ /// Restore button for the menu bar.
+ ///
+ /// -
+ /// HBMMENU_POPUP_CLOSE ((HBITMAP) 8)
+ /// Close button for the submenu.
+ ///
+ /// -
+ /// HBMMENU_POPUP_MAXIMIZE ((HBITMAP) 10)
+ /// Maximize button for the submenu.
+ ///
+ /// -
+ /// HBMMENU_POPUP_MINIMIZE ((HBITMAP) 11)
+ /// Minimize button for the submenu.
+ ///
+ /// -
+ /// HBMMENU_POPUP_RESTORE ((HBITMAP) 9)
+ /// Restore button for the submenu.
+ ///
+ /// -
+ /// HBMMENU_SYSTEM ((HBITMAP) 1)
+ /// Windows icon or the icon of the window specified in dwItemData.
+ ///
+ ///
+ ///
+ public HBITMAP BitmapHandle { get; }
+
+ /// Gets the help text (tool tip) associated with the menu.
+ public string HelpText { get; internal set; }
+
+ /// Gets the icon location associated with the menu's image.
+ public IconLocation IconLocation { get; internal set; }
+
+ /// An application-defined value that identifies the menu item.
+ public int Id { get; }
+
+ /// The menu item state. This member can be one or more of the values.
+ public MenuItemState State { get; }
+
+ ///
+ /// The submenu items associated with the menu item. If the menu item is not an item that opens a drop-down menu or submenu,
+ /// this member has no values.
+ ///
+ public MenuItemInfo[] SubMenus { get; }
+
+ /// The contents of the menu item. The meaning of this member depends on the value of .
+ public string Text { get; }
+
+ ///
+ /// The menu item type. This member can be one or more of the values.
+ /// The MFT_BITMAP, MFT_SEPARATOR, and MFT_STRING values cannot be combined with one another.
+ ///
+ public MenuItemType Type { get; }
+
+ /// Gets the verb associated with the menu.
+ public string Verb { get; internal set; }
+
+ /// Recursively gets the information for all menu item entries supplied by the provided native menu.
+ /// The handle to the created native menu.
+ /// An array of instances with information about the entries in .
+ public static MenuItemInfo[] GetMenuItems(HMENU hMenu) => GetMenuItems(hMenu, null);
+
+ internal static MenuItemInfo[] GetMenuItems(HMENU hMenu, ShellContextMenu scm)
+ {
+ if (hMenu.IsNull)
+ return new MenuItemInfo[0];
+
+ var SubMenus = new MenuItemInfo[GetMenuItemCount(hMenu)];
+ for (uint i = 0; i < SubMenus.Length; i++)
+ {
+ SubMenus[i] = new MenuItemInfo(hMenu, i);
+ if (scm != null)
+ {
+ SubMenus[i].Verb = scm.GetVerbForCommand(SubMenus[i].Id);
+ SubMenus[i].HelpText = scm.GetHelpTextForCommand(SubMenus[i].Id);
+ SubMenus[i].IconLocation = scm.GetIconLocationForCommand(SubMenus[i].Id);
+ }
+ }
+ return SubMenus;
+ }
+ }
+
private class MessageWindow : Control
{
private readonly ShellContextMenu m_Parent;