diff --git a/Windows.Shell/ShellCommand.cs b/Windows.Shell/ShellCommand.cs new file mode 100644 index 00000000..07fdba8b --- /dev/null +++ b/Windows.Shell/ShellCommand.cs @@ -0,0 +1,53 @@ +using System; +using Vanara.PInvoke; +using static Vanara.PInvoke.Ole32; +using static Vanara.PInvoke.Shell32; + +namespace Vanara.Windows.Shell +{ + /// + /// Wraps the functionality of IInitializeCommand. When deriving, handling the event is optional. + /// + /// + /// + public abstract class ShellCommand : ComObject, IInitializeCommand + { + /// Initializes a new instance of the class. + protected ShellCommand() : base() + { + } + + /// Initializes a new instance of the class. + /// The context within which the COM object is to be run. + /// Indicates how connections are made to the class object. + protected ShellCommand(CLSCTX classContext, REGCLS classUse) : base(classContext, classUse) + { + } + + /// Occurs when the shell command is initialized. + public event EventHandler InitializeCommand; + + /// Gets the name of the command returned by IInitializeCommand.Initialize. + /// + /// The name of the command as found in the registry. This value is until IInitializeCommand.Initialize + /// is called by the host. + /// + public string CommandName { get; private set; } + + /// Gets the properties exposed through IInitializeCommand.Initialize. + /// + /// Gets a instance. This value is until IInitializeCommand.Initialize is + /// called by the host. + /// + public PropertyBag Properties { get; private set; } + + /// + HRESULT IInitializeCommand.Initialize(string pszCommandName, IPropertyBag ppb) + { + CommandName = pszCommandName; + Properties = new PropertyBag(ppb); + InitializeCommand?.Invoke(this, EventArgs.Empty); + return HRESULT.S_OK; + } + } +} \ No newline at end of file diff --git a/Windows.Shell/ShellExecuteCommand.cs b/Windows.Shell/ShellExecuteCommand.cs new file mode 100644 index 00000000..ab9a5fd3 --- /dev/null +++ b/Windows.Shell/ShellExecuteCommand.cs @@ -0,0 +1,194 @@ +using System; +using System.Drawing; +using Vanara.PInvoke; +using static Vanara.PInvoke.Ole32; +using static Vanara.PInvoke.Shell32; + +namespace Vanara.Windows.Shell +{ + /// + /// Wraps the functionality of IExecuteCommand. To implement, derive from this class and override the method. All Shell items + /// passed to the command are available through the property. + /// + /// + /// This class provides an easy means of creating a COM based out-of-process DropTarget. + /// + /// Create a .NET application project. + /// Delete the Program.cs file that includes the Main method. + /// Create a new class and derive it from with the attributes in the example below. + /// Ensure the project is built as "X86". + /// + /// // Full implementation of a shell context menu handler using IExecuteCommand. + ///[ComVisible(true), Guid("<Your GUID here>"), ClassInterface(ClassInterfaceType.None)] + ///public class MyExecCmd : ShellExecuteCommand + ///{ + /// // *** Replace with your ProgID, verb name and display name *** + /// const string progID = "txtfile"; + /// const string verbName = "ExecuteCommandVerb"; + /// const string verbDisplayName = "ExecuteCommand Verb Sample"; + /// + /// // Overridden method performs all the functionality of your verb handler. The properties from ShellExecuteCommand should all be set + /// // and can be used for your implementation. Once you have completed all work required of this command, you must call the + /// // QuitMessageLoop method to finish processing all messages and then exit. If you fail to call QuitMessageLoop, this process will run + /// // indefinitely and future context menu requests will not be handled. + /// public override void OnExecute() + /// { + /// var szMsg = $"Found {SelectedItems.Count} item(s) called with '{CommandName}' verb: " + string.Join(", ", SelectedItems.Select(i => i.Name)); + /// if (!UIDisplayBlocked) + /// MessageBox.Show(szMsg, verbDisplayName); + /// // Don't fail to call QuitMessageLoop once you're done processing the Shell call. + /// QuitMessageLoop(); + /// } + /// + /// // This is the main thread of the application. When called from the Shell, it will append the -embedding argument to indicate that a + /// // message loop needs to start running and the COM server registered. All that is accomplished by calling the 'Run' method on your + /// // new class. You may choose to register or unregister based on alternate command-line arguments. It is highly recommended that you + /// // supply a timeout to the Run method to prevent a failure from leaving this process running indefinitely. The timeout is automatically + /// // canceled once the OnExecute method is called so as to prevent long-running code from being terminated. You must specify the + /// // [STAThread] attribute on this method for the handler to function. + /// [STAThread] + /// private static void Main(string[] args) + /// { + /// if (args.Length > 0 && args[0] == "-embedding") + /// new MyExecCmd().Run(TimeSpan.FromSeconds(30)); + /// } + /// + /// // This method registers this out-of-proc server to handle the DelegateExecute. This can be omitted if done by an installer. + /// private static void Register() + /// { + /// ShellRegistrar.RegisterLocalServer<MyExecCmd>(verbDisplayName, systemWide: false); + /// using (var progid = new ProgId(progID, false)) + /// using (var verb = progid.Verbs.Add(verbName, verbDisplayName)) + /// verb.DelegateExecute = Marshal.GenerateGuidForType(typeof(MyExecCmd)); + /// } + /// + /// // This method unregisters this out-of-proc server from handling the DelegateExecute. This can be omitted if done by an uninstaller. + /// private static void Unregister() + /// { + /// using (var progid = new ProgId(progID, false)) + /// progid.Verbs.Remove(verbName); + /// ShellRegistrar.UnregisterLocalServer<MyExecCmd>(false); + /// } + ///} + /// + /// + /// + public abstract class ShellExecuteCommand : ShellCommand, IExecuteCommand, IObjectWithSelection + { + /// Initializes a new instance of the class. + protected ShellExecuteCommand() : base() + { + } + + /// Initializes a new instance of the class. + /// The context within which the COM object is to be run. + /// Indicates how connections are made to the class object. + protected ShellExecuteCommand(CLSCTX classContext, REGCLS classUse) : base(classContext, classUse) + { + } + + /// Gets a value based on the current state of the keys CTRL and SHIFT. + /// The value based on the current state of the keys CTRL and SHIFT. + public User32.MouseButtonState KeyState { get; private set; } + + /// Gets a new working directory. This value is if the current working directory is to be used. + /// Returns a value. + public string NewWorkingDirectory { get; private set; } + + /// Gets the parameter values for the verb. + /// Returns a value. + public string Parameters { get; private set; } + + /// + /// Gets the screen coordinates at which the user right-clicked to invoke the shortcut menu from which a command was chosen. + /// Applications can use this information to present any UI. This is particularly useful in a multi-monitor situation. The default + /// position is the center of the default monitor. + /// + /// Returns a value. + public Point Position { get; private set; } + + /// Gets or sets the selected shell items. + /// The selected shell items. + public ShellItemArray SelectedItems { get; private set; } + + /// Gets a value indicating whether any UI associated with the selected Shell item should be displayed. + /// if display of any associated UI is blocked; otherwise, . + public bool UIDisplayBlocked { get; private set; } + + /// Gets the specified window's visual state. + /// Returns a value. + public ShowWindowCommand WindowState { get; private set; } + + /// Called in response to IExecuteCommand.Execute(). + public abstract void OnExecute(); + + /// + HRESULT IExecuteCommand.Execute() + { + QueueNonBlockingCallback(o => OnExecute()); + CancelTimeout(); + return HRESULT.S_OK; + } + + /// + HRESULT IObjectWithSelection.GetSelection(in Guid riid, out object ppv) + { + if (SelectedItems is null) + { + ppv = null; + return HRESULT.E_NOINTERFACE; + } + ppv = ShellUtil.QueryInterface(SelectedItems, riid); + return HRESULT.S_OK; + } + + /// + HRESULT IExecuteCommand.SetDirectory(string pszDirectory) + { + NewWorkingDirectory = pszDirectory; + return HRESULT.S_OK; + } + + /// + HRESULT IExecuteCommand.SetKeyState(User32.MouseButtonState grfKeyState) + { + KeyState = grfKeyState; + return HRESULT.S_OK; + } + + /// + HRESULT IExecuteCommand.SetNoShowUI(bool fNoShowUI) + { + UIDisplayBlocked = fNoShowUI; + return HRESULT.S_OK; + } + + /// + HRESULT IExecuteCommand.SetParameters(string pszParameters) + { + Parameters = pszParameters; + return HRESULT.S_OK; + } + + /// + HRESULT IExecuteCommand.SetPosition(Point pt) + { + Position = pt; + return HRESULT.S_OK; + } + + /// + HRESULT IObjectWithSelection.SetSelection(IShellItemArray psia) + { + SelectedItems = new ShellItemArray(psia); + return HRESULT.S_OK; + } + + /// + HRESULT IExecuteCommand.SetShowWindow(ShowWindowCommand nShow) + { + WindowState = nShow; + return HRESULT.S_OK; + } + } +} \ No newline at end of file