/// <summary> /// Carry out the command associated with a shortcut menu item. /// </summary> /// <param name="pici">A pointer to a CMINVOKECOMMANDINFO or CMINVOKECOMMANDINFOEX structure containing information about the command.</param> public void InvokeCommand(IntPtr pici) { var isUnicode = false; // Determine which structure is being passed in, CMINVOKECOMMANDINFO or // CMINVOKECOMMANDINFOEX based on the cbSize member of lpcmi. Although // the lpcmi parameter is declared in Shlobj.h as a CMINVOKECOMMANDINFO // structure, in practice it often points to a CMINVOKECOMMANDINFOEX // structure. This struct is an extended version of CMINVOKECOMMANDINFO // and has additional members that allow Unicode strings to be passed. var ici = (CMINVOKECOMMANDINFO)Marshal.PtrToStructure(pici, typeof(CMINVOKECOMMANDINFO)); var iciex = new CMINVOKECOMMANDINFOEX(); if (ici.cbSize == Marshal.SizeOf(typeof(CMINVOKECOMMANDINFOEX))) { if ((ici.fMask & CMIC.CMIC_MASK_UNICODE) != 0) { isUnicode = true; iciex = (CMINVOKECOMMANDINFOEX)Marshal.PtrToStructure(pici, typeof(CMINVOKECOMMANDINFOEX)); } } // Determines whether the command is identified by its offset or verb. // There are two ways to identify commands: // // 1) The command's verb string // 2) The command's identifier offset // // If the high-order word of lpcmi->lpVerb (for the ANSI case) or // lpcmi->lpVerbW (for the Unicode case) is nonzero, lpVerb or lpVerbW // holds a verb string. If the high-order word is zero, the command // offset is in the low-order word of lpcmi->lpVerb. var activeCommand = default(ActiveCommand); if (!isUnicode && NativeHelpers.GetHighWord(ici.verb.ToInt32()) != 0) { // For the ANSI case, if the high-order word is not zero, the command's // verb string is in lpcmi->lpVerb. // Is the verb supported by this context menu extension? var verb = Marshal.PtrToStringAnsi(ici.verb); activeCommand = GetActiveCommand(verb); } else if (isUnicode && NativeHelpers.GetHighWord(iciex.verbW.ToInt32()) != 0) { // For the Unicode case, if the high-order word is not zero, the // command's verb string is in lpcmi->lpVerbW. // Is the verb supported by this context menu extension? var verb = Marshal.PtrToStringAnsi(iciex.verbW); activeCommand = GetActiveCommand(verb); } else { // If the command cannot be identified through the verb string, then // check the identifier offset. // Is the command identifier offset supported by this context menu // extension? var offset = NativeHelpers.GetLowWord(ici.verb.ToInt32()); activeCommand = GetActiveCommand(offset); } if (activeCommand != null) { var context = new CommandContext(this.selectedShellItems, this.isExtendedContextMenu); try { activeCommand.Command.Execute(context); } catch (Exception exc) { Logger.LogError($"Error invoking command \"{activeCommand.Command.Name}\": {exc.ToString()}"); } } else { // If the verb is not recognized by the context menu handler, it // must return E_FAIL to allow it to be passed on to the other // context menu handlers that might implement that verb. Marshal.ThrowExceptionForHR(WinError.E_FAIL); } }
/// <summary> /// Add commands to a shortcut menu. /// </summary> /// <param name="hMenu">A handle to the shortcut menu.</param> /// <param name="iMenu">The zero-based position at which to insert the first new menu item.</param> /// <param name="idCmdFirst">The minimum value that the handler can specify for a menu item ID.</param> /// <param name="idCmdLast">The maximum value that the handler can specify for a menu item ID.</param> /// <param name="uFlags">Optional flags that specify how the shortcut menu can be changed.</param> /// <returns> /// If successful, returns an HRESULT value that has its severity value set /// to SEVERITY_SUCCESS and its code value set to the offset of the largest /// command identifier that was assigned, plus one. /// </returns> public int QueryContextMenu(IntPtr hMenu, uint iMenu, uint idCmdFirst, uint idCmdLast, uint uFlags) { // If uFlags include CMF_DEFAULTONLY then we should not do anything. if (((uint)CMF.CMF_DEFAULTONLY & uFlags) != 0) { return(NativeHelpers.MakeHResult(WinError.SEVERITY_SUCCESS, 0, 0)); } // Check if the user wants to see the extended context menu (i.e. pressing Shift while opening the context menu). this.isExtendedContextMenu = ((uint)CMF.CMF_EXTENDEDVERBS & uFlags) != 0; this.activeCommands.Clear(); var maxItems = (idCmdLast - idCmdFirst) + 1 - 2 /* Subtract 2 for the separators */; if (maxItems > 0) { var menuIndex = iMenu; // Add a separator. var beginSeparator = new MENUITEMINFO(); beginSeparator.cbSize = (uint)Marshal.SizeOf(beginSeparator); beginSeparator.fMask = MIIM.MIIM_TYPE; beginSeparator.fType = MFT.MFT_SEPARATOR; if (!NativeMethods.InsertMenuItem(hMenu, menuIndex++, true, ref beginSeparator)) { return(Marshal.GetHRForLastWin32Error()); } // Add the commands. var context = new CommandContext(this.selectedShellItems, this.isExtendedContextMenu); var commandOffset = default(uint); foreach (var command in CommandFactory.GetAvailableCommands()) { try { var commandState = command.GetState(context); if (commandState != null && commandState.IsVisible) { var verb = Guid.NewGuid().ToString(); // Create a unique verb to be able to retrieve the right command later on based on that verb. var activeCommand = new ActiveCommand(command, verb, commandOffset++); this.activeCommands.Add(activeCommand); var mii = new MENUITEMINFO(); mii.cbSize = (uint)Marshal.SizeOf(mii); mii.fMask = MIIM.MIIM_BITMAP | MIIM.MIIM_STRING | MIIM.MIIM_FTYPE | MIIM.MIIM_ID | MIIM.MIIM_STATE; mii.wID = idCmdFirst + activeCommand.Offset; mii.fType = MFT.MFT_STRING; mii.dwTypeData = commandState.MenuText; if (!commandState.IsEnabled) { mii.fState = MFS.MFS_DISABLED; } else if (commandState.IsChecked) { mii.fState = MFS.MFS_CHECKED; } else { mii.fState = MFS.MFS_ENABLED; } if (!NativeMethods.InsertMenuItem(hMenu, menuIndex++, true, ref mii)) { return(Marshal.GetHRForLastWin32Error()); } if (this.activeCommands.Count >= maxItems) { break; } } } catch (Exception exc) { Logger.LogError($"Error querying command \"{command.Name}\": {exc.ToString()}"); } } // Add a separator. var endSeparator = new MENUITEMINFO(); endSeparator.cbSize = (uint)Marshal.SizeOf(endSeparator); endSeparator.fMask = MIIM.MIIM_TYPE; endSeparator.fType = MFT.MFT_SEPARATOR; if (!NativeMethods.InsertMenuItem(hMenu, menuIndex++, true, ref endSeparator)) { return(Marshal.GetHRForLastWin32Error()); } } // Return an HRESULT value with the severity set to SEVERITY_SUCCESS. // Set the code value to the offset of the largest command identifier // that was assigned, plus one (1). return(NativeHelpers.MakeHResult(WinError.SEVERITY_SUCCESS, 0, (uint)this.activeCommands.Count)); }