From b6a41a2a9adefc3d15f45c67eed012158d3dd74b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 18:58:10 +0000 Subject: [PATCH 1/2] chore: Bump the nuget-dependencies group with 1 update Bumps Vanara.PInvoke.Shell32 from 5.0.5 to 5.0.7 --- updated-dependencies: - dependency-name: Vanara.PInvoke.Shell32 dependency-version: 5.0.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-dependencies ... Signed-off-by: dependabot[bot] --- src/Juknum.Windows.ContextMenu.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Juknum.Windows.ContextMenu.csproj b/src/Juknum.Windows.ContextMenu.csproj index b948021..20e5a71 100644 --- a/src/Juknum.Windows.ContextMenu.csproj +++ b/src/Juknum.Windows.ContextMenu.csproj @@ -24,7 +24,7 @@ - + From 49765a7890edba0efcc454324767f46d1b0b40a3 Mon Sep 17 00:00:00 2001 From: Julien Date: Mon, 24 Aug 2026 15:14:56 +0200 Subject: [PATCH 2/2] refactor: remove custom COM interfaces in favor of Vanara 5.0.7 Remove custom IEnumExplorerCommand and IExplorerCommand interfaces now that dahall/Vanara#621 has been resolved in Vanara.PInvoke.Shell32 5.0.7. --- src/CommandEnumerator.cs | 78 ++++++----- src/ExplorerCommand.cs | 186 ++++++++++++------------- src/ExplorerCommandMenu.cs | 96 ++++++------- src/Interfaces/IEnumExplorerCommand.cs | 29 ---- src/Interfaces/IExplorerCommand.cs | 47 ------- 5 files changed, 182 insertions(+), 254 deletions(-) delete mode 100644 src/Interfaces/IEnumExplorerCommand.cs delete mode 100644 src/Interfaces/IExplorerCommand.cs diff --git a/src/CommandEnumerator.cs b/src/CommandEnumerator.cs index c6ab234..d6635e8 100644 --- a/src/CommandEnumerator.cs +++ b/src/CommandEnumerator.cs @@ -1,37 +1,41 @@ -using Juknum.Windows.ContextMenu.Interfaces; -using System.Runtime.InteropServices; -using Vanara.PInvoke; - -namespace Juknum.Windows.ContextMenu; - -[ComVisible(false)] -internal class CommandEnumerator(IExplorerCommand[] commands) : IEnumExplorerCommand { - private int index = 0; - private readonly IExplorerCommand[] commands = commands; - - #region IEnumExplorerCommand Members - public HRESULT Next(uint celt, IExplorerCommand[] pUICommand, IntPtr pceltFetched) { - uint fetched = 0; - - while (fetched < celt && index < commands.Length) { - pUICommand[fetched++] = commands[index++]; - } - - return fetched == celt ? HRESULT.S_OK : HRESULT.S_FALSE; - } - - public HRESULT Skip(uint celt) { - index += (int)celt; - - if (index > commands.Length) { - index = commands.Length; - return HRESULT.S_FALSE; - } - - return HRESULT.S_OK; - } - - public void Reset() => index = 0; - public IEnumExplorerCommand Clone() => new CommandEnumerator(commands) { index = this.index }; - #endregion -} +using System.Runtime.InteropServices; +using Vanara.PInvoke; +using static Vanara.PInvoke.Shell32; + +namespace Juknum.Windows.ContextMenu; + +[ComVisible(false)] +internal class CommandEnumerator(IExplorerCommand[] commands) : IEnumExplorerCommand { + private int index = 0; + private readonly IExplorerCommand[] commands = commands; + + #region IEnumExplorerCommand Members + public HRESULT Next(uint celt, IExplorerCommand[] pUICommand, IntPtr pceltFetched) { + uint fetched = 0; + + while (fetched < celt && index < commands.Length) { + pUICommand[fetched++] = commands[index++]; + } + + if (pceltFetched != IntPtr.Zero) { + Marshal.WriteInt32(pceltFetched, (int)fetched); + } + + return fetched == celt ? HRESULT.S_OK : HRESULT.S_FALSE; + } + + public HRESULT Skip(uint celt) { + index += (int)celt; + + if (index > commands.Length) { + index = commands.Length; + return HRESULT.S_FALSE; + } + + return HRESULT.S_OK; + } + + public void Reset() => index = 0; + public IEnumExplorerCommand Clone() => new CommandEnumerator(commands) { index = this.index }; + #endregion +} diff --git a/src/ExplorerCommand.cs b/src/ExplorerCommand.cs index 8dec643..c97998d 100644 --- a/src/ExplorerCommand.cs +++ b/src/ExplorerCommand.cs @@ -1,94 +1,94 @@ -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; -using Vanara.PInvoke; -using static Vanara.PInvoke.Shell32; - -namespace Juknum.Windows.ContextMenu; - -[ComVisible(false)] -public abstract class ExplorerCommand : Interfaces.IExplorerCommand { - - /// - /// Unique identifier (GUID) of the command. - /// - public abstract Guid Guid { get; } - - /// - /// Title of the command, which is displayed in the context menu. - /// - public abstract string Title { get; } - - /// - /// Optional icon for the command, which is displayed in the context menu. - /// - public virtual string? Icon { get; } = null; - - /// - /// Optional tooltip for the command, which is displayed when hovering over the command in the context menu. - /// Note: This property is not used by Windows Explorer. - /// - public virtual string? ToolTip { get; } = null; - - /// - /// Determines whether the command is enabled for the given shell item array. - /// - /// The shell item array for which to determine command enablement. - /// Indicates whether the operation can be slow. - /// true if the command is enabled; otherwise, false. - public virtual bool IsEnabled(IShellItemArray psiItemArray, bool canBeSlow) => true; - - /// - /// The method that is called when the command is executed. - /// This method should be overridden to provide custom behavior for the command. - /// - /// The shell item array for which to execute the command. - /// The bind context for the command. - /// true if the command was executed successfully; otherwise, false. - public abstract bool Execute(IShellItemArray psiItemArray, IBindCtx? pbc); - - #region IExplorerCommand Members - public HRESULT GetTitle(IShellItemArray psiItemArray, out string? ppszName) { - ppszName = Title; - return HRESULT.S_OK; - } - - public HRESULT GetIcon(IShellItemArray psiItemArray, out string? ppszIcon) { - ppszIcon = Icon; - return Icon is null ? HRESULT.E_NOTIMPL : HRESULT.S_OK; - } - - public HRESULT GetToolTip(IShellItemArray psiItemArray, out string? ppszInfotip) { - ppszInfotip = ToolTip; - return ToolTip is null ? HRESULT.E_NOTIMPL : HRESULT.S_OK; - } - - public HRESULT GetCanonicalName(out Guid pguidCommandName) { - pguidCommandName = Guid; - return HRESULT.S_OK; - } - - public HRESULT GetState(IShellItemArray psiItemArray, bool fOkToBeSlow, out EXPCMDSTATE pCmdState) { - pCmdState = IsEnabled(psiItemArray, fOkToBeSlow) ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_DISABLED; - return HRESULT.S_OK; - } - - public virtual HRESULT Invoke(IShellItemArray psiItemArray, IBindCtx? pbc) { - try { - return Execute(psiItemArray, pbc) ? HRESULT.S_OK : HRESULT.S_FALSE; - } - catch { - return HRESULT.E_FAIL; - } - } - - public virtual HRESULT GetFlags(out EXPCMDFLAGS pFlags) { - pFlags = EXPCMDFLAGS.ECF_DEFAULT; - return HRESULT.S_OK; - } - - public virtual HRESULT EnumSubCommands(out Interfaces.IEnumExplorerCommand? ppEnum) { - ppEnum = null; - return HRESULT.E_NOTIMPL; - } - #endregion +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Vanara.PInvoke; +using static Vanara.PInvoke.Shell32; + +namespace Juknum.Windows.ContextMenu; + +[ComVisible(false)] +public abstract class ExplorerCommand : IExplorerCommand { + + /// + /// Unique identifier (GUID) of the command. + /// + public abstract Guid Guid { get; } + + /// + /// Title of the command, which is displayed in the context menu. + /// + public abstract string Title { get; } + + /// + /// Optional icon for the command, which is displayed in the context menu. + /// + public virtual string? Icon { get; } = null; + + /// + /// Optional tooltip for the command, which is displayed when hovering over the command in the context menu. + /// Note: This property is not used by Windows Explorer. + /// + public virtual string? ToolTip { get; } = null; + + /// + /// Determines whether the command is enabled for the given shell item array. + /// + /// The shell item array for which to determine command enablement. + /// Indicates whether the operation can be slow. + /// true if the command is enabled; otherwise, false. + public virtual bool IsEnabled(IShellItemArray psiItemArray, bool canBeSlow) => true; + + /// + /// The method that is called when the command is executed. + /// This method should be overridden to provide custom behavior for the command. + /// + /// The shell item array for which to execute the command. + /// The bind context for the command. + /// true if the command was executed successfully; otherwise, false. + public abstract bool Execute(IShellItemArray psiItemArray, IBindCtx? pbc); + + #region IExplorerCommand Members + public HRESULT GetTitle(IShellItemArray psiItemArray, out string? ppszName) { + ppszName = Title; + return HRESULT.S_OK; + } + + public HRESULT GetIcon(IShellItemArray psiItemArray, out string? ppszIcon) { + ppszIcon = Icon; + return Icon is null ? HRESULT.E_NOTIMPL : HRESULT.S_OK; + } + + public HRESULT GetToolTip(IShellItemArray psiItemArray, out string? ppszInfotip) { + ppszInfotip = ToolTip; + return ToolTip is null ? HRESULT.E_NOTIMPL : HRESULT.S_OK; + } + + public HRESULT GetCanonicalName(out Guid pguidCommandName) { + pguidCommandName = Guid; + return HRESULT.S_OK; + } + + public HRESULT GetState(IShellItemArray psiItemArray, bool fOkToBeSlow, out EXPCMDSTATE pCmdState) { + pCmdState = IsEnabled(psiItemArray, fOkToBeSlow) ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_DISABLED; + return HRESULT.S_OK; + } + + public virtual HRESULT Invoke(IShellItemArray psiItemArray, IBindCtx? pbc) { + try { + return Execute(psiItemArray, pbc) ? HRESULT.S_OK : HRESULT.S_FALSE; + } + catch { + return HRESULT.E_FAIL; + } + } + + public virtual HRESULT GetFlags(out EXPCMDFLAGS pFlags) { + pFlags = EXPCMDFLAGS.ECF_DEFAULT; + return HRESULT.S_OK; + } + + public virtual HRESULT EnumSubCommands(out IEnumExplorerCommand? ppEnum) { + ppEnum = null; + return HRESULT.E_NOTIMPL; + } + #endregion } \ No newline at end of file diff --git a/src/ExplorerCommandMenu.cs b/src/ExplorerCommandMenu.cs index 2bca94f..cf20876 100644 --- a/src/ExplorerCommandMenu.cs +++ b/src/ExplorerCommandMenu.cs @@ -1,48 +1,48 @@ -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; -using Vanara.PInvoke; -using static Vanara.PInvoke.Shell32; - -namespace Juknum.Windows.ContextMenu; - -[ComVisible(false)] -public abstract class ExplorerCommandMenu : ExplorerCommand { - /// - /// Array of objects that represent the commands in the menu. - /// - public abstract ExplorerCommand[] Commands { get; } - - #region ExplorerCommand Members - /// - /// Does not execute any command when the menu is clicked. - /// Override this method to provide custom behavior for the menu. - /// - /// The shell item array for which to execute the command. - /// The bind context for the command. - /// true if the command was executed successfully; otherwise, false. - public override bool Execute(IShellItemArray psiItemArray, IBindCtx? pbc) => true; - #endregion - - #region IExplorerCommand Members - public override HRESULT Invoke(IShellItemArray psiItemArray, IBindCtx? pbc) { - // Do nothing when the menu is clicked. The menu will be displayed automatically by Windows Explorer. - return HRESULT.E_NOTIMPL; - } - - public override HRESULT GetFlags(out EXPCMDFLAGS pFlags) { - pFlags = EXPCMDFLAGS.ECF_HASSUBCOMMANDS; - return HRESULT.S_OK; - } - - public override HRESULT EnumSubCommands(out Interfaces.IEnumExplorerCommand? ppEnum) { - try { - ppEnum = new CommandEnumerator(Commands); - return HRESULT.S_OK; - } - catch { - ppEnum = null; - return HRESULT.S_FALSE; - } - } - #endregion -} +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Vanara.PInvoke; +using static Vanara.PInvoke.Shell32; + +namespace Juknum.Windows.ContextMenu; + +[ComVisible(false)] +public abstract class ExplorerCommandMenu : ExplorerCommand { + /// + /// Array of objects that represent the commands in the menu. + /// + public abstract ExplorerCommand[] Commands { get; } + + #region ExplorerCommand Members + /// + /// Does not execute any command when the menu is clicked. + /// Override this method to provide custom behavior for the menu. + /// + /// The shell item array for which to execute the command. + /// The bind context for the command. + /// true if the command was executed successfully; otherwise, false. + public override bool Execute(IShellItemArray psiItemArray, IBindCtx? pbc) => true; + #endregion + + #region IExplorerCommand Members + public override HRESULT Invoke(IShellItemArray psiItemArray, IBindCtx? pbc) { + // Do nothing when the menu is clicked. The menu will be displayed automatically by Windows Explorer. + return HRESULT.E_NOTIMPL; + } + + public override HRESULT GetFlags(out EXPCMDFLAGS pFlags) { + pFlags = EXPCMDFLAGS.ECF_HASSUBCOMMANDS; + return HRESULT.S_OK; + } + + public override HRESULT EnumSubCommands(out IEnumExplorerCommand? ppEnum) { + try { + ppEnum = new CommandEnumerator(Commands); + return HRESULT.S_OK; + } + catch { + ppEnum = null; + return HRESULT.S_FALSE; + } + } + #endregion +} diff --git a/src/Interfaces/IEnumExplorerCommand.cs b/src/Interfaces/IEnumExplorerCommand.cs deleted file mode 100644 index 9224301..0000000 --- a/src/Interfaces/IEnumExplorerCommand.cs +++ /dev/null @@ -1,29 +0,0 @@ - -using System.Runtime.InteropServices; -using Vanara.PInvoke; - -namespace Juknum.Windows.ContextMenu.Interfaces; - -/// -// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ienumexplorercommand -[PInvokeData("shobjidl_core.h", MSDNShortId = "c9a21e84-dd95-413a-b9db-e02008185bef")] -[ComImport, Guid("a88826f8-186f-4987-aade-ea0cef8fbfe8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] -public interface IEnumExplorerCommand : Vanara.Collections.ICOMEnum { - - // Here we switched the "out uint pceltFetched" parameter to IntPtr because the original method signature uses - // a pointer to an unsigned integer, which is not directly compatible with C#'s out parameter. - /// - [PreserveSig] - HRESULT Next([In] uint celt, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface, SizeParamIndex = 0)] IExplorerCommand[] pUICommand, IntPtr pceltFetched); - - /// - [PreserveSig] - HRESULT Skip([In] uint celt); - - /// - void Reset(); - - /// - [return: MarshalAs(UnmanagedType.Interface)] - IEnumExplorerCommand Clone(); -} diff --git a/src/Interfaces/IExplorerCommand.cs b/src/Interfaces/IExplorerCommand.cs deleted file mode 100644 index c86f27f..0000000 --- a/src/Interfaces/IExplorerCommand.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; -using Vanara.PInvoke; -using static Vanara.PInvoke.Shell32; - -namespace Juknum.Windows.ContextMenu.Interfaces; - -/// -// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-iexplorercommand -[PInvokeData("shobjidl_core.h", MSDNShortId = "61e94e50-9e12-4a2c-a6c7-64a9181f80b8")] -[ComImport, Guid("a08ce4d0-fa25-44ab-b57c-c7b1c323e0b9"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] -public interface IExplorerCommand { - - /// - [PreserveSig] - HRESULT GetTitle(IShellItemArray psiItemArray, [MarshalAs(UnmanagedType.LPWStr)] out string? ppszName); - - /// - [PreserveSig] - HRESULT GetIcon(IShellItemArray psiItemArray, [MarshalAs(UnmanagedType.LPWStr)] out string? ppszIcon); - - /// - [PreserveSig] - HRESULT GetToolTip(IShellItemArray psiItemArray, [MarshalAs(UnmanagedType.LPWStr)] out string? ppszInfotip); - - /// - [PreserveSig] - HRESULT GetCanonicalName(out Guid pguidCommandName); - - /// - [PreserveSig] - HRESULT GetState(IShellItemArray psiItemArray, [MarshalAs(UnmanagedType.Bool)] bool fOkToBeSlow, out EXPCMDSTATE pCmdState); - - /// - [PreserveSig] - HRESULT Invoke(IShellItemArray psiItemArray, [Optional] IBindCtx? pbc); - - /// - [PreserveSig] - HRESULT GetFlags(out EXPCMDFLAGS pFlags); - - // We use our own IEnumExplorerCommand interface here to fix the Next() method - // signature from Vanara.PInvoke.Shell32.IEnumExplorerCommand as it is bugged. - /// - [PreserveSig] - HRESULT EnumSubCommands(out Interfaces.IEnumExplorerCommand? ppEnum); -}