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); -} 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 @@ - +