//
// Copyright (c) PlaceholderCompany. All rights reserved.
//
namespace SystemTrayMenu.Utilities
{
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using SystemTrayMenu.Helper;
using static SystemTrayMenu.Utilities.FormsExtensions;
///
/// source: https://www.codeproject.com/Articles/22012/Explorer-Shell-Context-Menu
/// modified to fit SystemTrayMenu.
///
/// "Stand-alone" shell context menu
///
/// It isn't really debugged but is mostly working.
/// Create an instance and call ShowContextMenu with a list of FileInfo for the files.
/// Limitation is that it only handles files in the same directory but it can be fixed
/// by changing the way files are translated into PIDLs.
///
/// Based on FileBrowser in C# from CodeProject
/// http://www.codeproject.com/useritems/FileBrowser.asp
///
/// Hooking class taken from MSDN Magazine Cutting Edge column
/// http://msdn.microsoft.com/msdnmag/issues/02/10/CuttingEdge/
///
/// Andreas Johansson
/// afjohansson@hotmail.com
/// http://afjohansson.spaces.live.com
/// .
///
///
/// ShellContextMenu scm = new ShellContextMenu();
/// FileInfo[] files = new FileInfo[1];
/// files[0] = new FileInfo(@"c:\windows\notepad.exe");
/// scm.ShowContextMenu(this.Handle, files, Cursor.Position);.
///
public class ShellContextMenu : NativeWindow
{
private const int MaxPath = 260;
private const uint CmdFirst = 1;
private const uint CmdLast = 30000;
private const int ResultOK = 0;
private static readonly int CbMenuItemInfo = Marshal.SizeOf(typeof(MENUITEMINFO));
private static readonly int CbInvokeCommand = Marshal.SizeOf(typeof(CMINVOKECOMMANDINFOEX));
private static Guid iidIShellFolder = new("{000214E6-0000-0000-C000-000000000046}");
private static Guid iidIContextMenu = new("{000214e4-0000-0000-c000-000000000046}");
private static Guid iidIContextMenu2 = new("{000214f4-0000-0000-c000-000000000046}");
private static Guid iidIContextMenu3 = new("{bcfce0a0-ec17-11d0-8d10-00a0c90f2719}");
private IContextMenu oContextMenu;
private IContextMenu2 oContextMenu2;
private IContextMenu3 oContextMenu3;
private IShellFolder oDesktopFolder;
private IShellFolder oParentFolder;
private IntPtr[] arrPIDLs;
private string strParentFolder;
///
/// Finalizes an instance of the class.
///
~ShellContextMenu()
{
ReleaseAll();
}
// Defines the values used with the IShellFolder::GetDisplayNameOf and IShellFolder::SetNameOf
// methods to specify the type of file or folder names used by those methods
[Flags]
private enum SHGNO
{
NORMAL = 0x0000,
INFOLDER = 0x0001,
FOREDITING = 0x1000,
FORADDRESSBAR = 0x4000,
FORPARSING = 0x8000,
}
// The attributes that the caller is requesting, when calling IShellFolder::GetAttributesOf
[Flags]
private enum SFGAO : uint
{
BROWSABLE = 0x8000000,
CANCOPY = 1,
CANDELETE = 0x20,
CANLINK = 4,
CANMONIKER = 0x400000, // HASSTORAGE = 0x400000, //STREAM = 0x400000,
CANMOVE = 2,
CANRENAME = 0x10,
CAPABILITYMASK = 0x177,
COMPRESSED = 0x4000000,
CONTENTSMASK = 0x80000000, // HASSUBFOLDER = 0x80000000,
DISPLAYATTRMASK = 0xfc000,
DROPTARGET = 0x100,
ENCRYPTED = 0x2000,
FILESYSANCESTOR = 0x10000000,
FILESYSTEM = 0x40000000,
FOLDER = 0x20000000,
GHOSTED = 0x8000,
HASPROPSHEET = 0x40,
HIDDEN = 0x80000,
ISSLOW = 0x4000,
LINK = 0x10000,
NEWCONTENT = 0x200000,
NONENUMERATED = 0x100000,
READONLY = 0x40000,
REMOVABLE = 0x2000000,
SHARE = 0x20000,
STORAGE = 8,
STORAGEANCESTOR = 0x800000,
STORAGECAPMASK = 0x70c50008,
VALIDATE = 0x1000000,
}
// Determines the type of items included in an enumeration.
// These values are used with the IShellFolder::EnumObjects method
[Flags]
private enum SHCONTF
{
FOLDERS = 0x0020,
NONFOLDERS = 0x0040,
INCLUDEHIDDEN = 0x0080,
INIT_ON_FIRST_NEXT = 0x0100,
NETPRINTERSRCH = 0x0200,
SHAREABLE = 0x0400,
STORAGE = 0x0800,
}
// Specifies how the shortcut menu can be changed when calling IContextMenu::QueryContextMenu
[Flags]
private enum CMF : uint
{
NORMAL = 0x00000000,
DEFAULTONLY = 0x00000001,
VERBSONLY = 0x00000002,
EXPLORE = 0x00000004,
NOVERBS = 0x00000008,
CANRENAME = 0x00000010,
NODEFAULT = 0x00000020,
INCLUDESTATIC = 0x00000040,
EXTENDEDVERBS = 0x00000100,
RESERVED = 0xffff0000,
}
// Flags specifying the information to return when calling IContextMenu::GetCommandString
[Flags]
private enum GCS : uint
{
VERBA = 0,
HELPTEXTA = 1,
VALIDATEA = 2,
VERBW = 4,
HELPTEXTW = 5,
VALIDATEW = 6,
}
// The cmd for a custom added menu item
private enum CMD_CUSTOM
{
ExpandCollapse = (int)CmdLast + 1,
}
// Flags used with the CMINVOKECOMMANDINFOEX structure
[Flags]
private enum CMIC : uint
{
HOTKEY = 0x00000020,
ICON = 0x00000010,
FLAG_NO_UI = 0x00000400,
UNICODE = 0x00004000,
NO_CONSOLE = 0x00008000,
ASYNCOK = 0x00100000,
NOZONECHECKS = 0x00800000,
SHIFT_DOWN = 0x10000000,
CONTROL_DOWN = 0x40000000,
FLAG_LOG_USAGE = 0x04000000,
PTINVOKE = 0x20000000,
}
// Specifies how the window is to be shown
[Flags]
private enum SW
{
HIDE = 0,
SHOWNORMAL = 1, // NORMAL = 1,
SHOWMINIMIZED = 2,
SHOWMAXIMIZED = 3, // MAXIMIZE = 3,
SHOWNOACTIVATE = 4,
SHOW = 5,
MINIMIZE = 6,
SHOWMINNOACTIVE = 7,
SHOWNA = 8,
RESTORE = 9,
SHOWDEFAULT = 10,
}
// Window message flags
[Flags]
private enum WM : uint
{
ACTIVATE = 0x6,
ACTIVATEAPP = 0x1C,
AFXFIRST = 0x360,
AFXLAST = 0x37F,
APP = 0x8000,
ASKCBFORMATNAME = 0x30C,
CANCELJOURNAL = 0x4B,
CANCELMODE = 0x1F,
CAPTURECHANGED = 0x215,
CHANGECBCHAIN = 0x30D,
CHAR = 0x102,
CHARTOITEM = 0x2F,
CHILDACTIVATE = 0x22,
CLEAR = 0x303,
CLOSE = 0x10,
COMMAND = 0x111,
COMPACTING = 0x41,
COMPAREITEM = 0x39,
CONTEXTMENU = 0x7B,
COPY = 0x301,
COPYDATA = 0x4A,
CREATE = 0x1,
CTLCOLORBTN = 0x135,
CTLCOLORDLG = 0x136,
CTLCOLOREDIT = 0x133,
CTLCOLORLISTBOX = 0x134,
CTLCOLORMSGBOX = 0x132,
CTLCOLORSCROLLBAR = 0x137,
CTLCOLORSTATIC = 0x138,
CUT = 0x300,
DEADCHAR = 0x103,
DELETEITEM = 0x2D,
DESTROY = 0x2,
DESTROYCLIPBOARD = 0x307,
DEVICECHANGE = 0x219,
DEVMODECHANGE = 0x1B,
DISPLAYCHANGE = 0x7E,
DRAWCLIPBOARD = 0x308,
DRAWITEM = 0x2B,
DROPFILES = 0x233,
ENABLE = 0xA,
ENDSESSION = 0x16,
ENTERIDLE = 0x121,
ENTERMENULOOP = 0x211,
ENTERSIZEMOVE = 0x231,
ERASEBKGND = 0x14,
EXITMENULOOP = 0x212,
EXITSIZEMOVE = 0x232,
FONTCHANGE = 0x1D,
GETDLGCODE = 0x87,
GETFONT = 0x31,
GETHOTKEY = 0x33,
GETICON = 0x7F,
GETMINMAXINFO = 0x24,
GETOBJECT = 0x3D,
GETSYSMENU = 0x313,
GETTEXT = 0xD,
GETTEXTLENGTH = 0xE,
HANDHELDFIRST = 0x358,
HANDHELDLAST = 0x35F,
HELP = 0x53,
HOTKEY = 0x312,
HSCROLL = 0x114,
HSCROLLCLIPBOARD = 0x30E,
ICONERASEBKGND = 0x27,
IME_CHAR = 0x286,
IME_COMPOSITION = 0x10F, // IME_KEYLAST = 0x10F,
IME_COMPOSITIONFULL = 0x284,
IME_CONTROL = 0x283,
IME_ENDCOMPOSITION = 0x10E,
IME_KEYDOWN = 0x290,
IME_KEYUP = 0x291,
IME_NOTIFY = 0x282,
IME_REQUEST = 0x288,
IME_SELECT = 0x285,
IME_SETCONTEXT = 0x281,
IME_STARTCOMPOSITION = 0x10D,
INITDIALOG = 0x110,
INITMENU = 0x116,
INITMENUPOPUP = 0x117,
INPUTLANGCHANGE = 0x51,
INPUTLANGCHANGEREQUEST = 0x50,
KEYDOWN = 0x100, // KEYFIRST = 0x100,
KEYLAST = 0x108,
KEYUP = 0x101,
KILLFOCUS = 0x8,
LBUTTONDBLCLK = 0x203,
LBUTTONDOWN = 0x201,
LBUTTONUP = 0x202,
LVM_GETEDITCONTROL = 0x1018,
LVM_SETIMAGELIST = 0x1003,
MBUTTONDBLCLK = 0x209,
MBUTTONDOWN = 0x207,
MBUTTONUP = 0x208,
MDIACTIVATE = 0x222,
MDICASCADE = 0x227,
MDICREATE = 0x220,
MDIDESTROY = 0x221,
MDIGETACTIVE = 0x229,
MDIICONARRANGE = 0x228,
MDIMAXIMIZE = 0x225,
MDINEXT = 0x224,
MDIREFRESHMENU = 0x234,
MDIRESTORE = 0x223,
MDISETMENU = 0x230,
MDITILE = 0x226,
MEASUREITEM = 0x2C,
MENUCHAR = 0x120,
MENUCOMMAND = 0x126,
MENUDRAG = 0x123,
MENUGETOBJECT = 0x124,
MENURBUTTONUP = 0x122,
MENUSELECT = 0x11F,
MOUSEACTIVATE = 0x21,
MOUSEFIRST = 0x200, // MOUSEMOVE = 0x200,
MOUSEHOVER = 0x2A1,
MOUSELAST = 0x20A, // MOUSEWHEEL = 0x20A,
MOUSELEAVE = 0x2A3,
MOVE = 0x3,
MOVING = 0x216,
NCACTIVATE = 0x86,
NCCALCSIZE = 0x83,
NCCREATE = 0x81,
NCDESTROY = 0x82,
NCHITTEST = 0x84,
NCLBUTTONDBLCLK = 0xA3,
NCLBUTTONDOWN = 0xA1,
NCLBUTTONUP = 0xA2,
NCMBUTTONDBLCLK = 0xA9,
NCMBUTTONDOWN = 0xA7,
NCMBUTTONUP = 0xA8,
NCMOUSEHOVER = 0x2A0,
NCMOUSELEAVE = 0x2A2,
NCMOUSEMOVE = 0xA0,
NCPAINT = 0x85,
NCRBUTTONDBLCLK = 0xA6,
NCRBUTTONDOWN = 0xA4,
NCRBUTTONUP = 0xA5,
NEXTDLGCTL = 0x28,
NEXTMENU = 0x213,
NOTIFY = 0x4E,
NOTIFYFORMAT = 0x55,
NULL = 0x0,
PAINT = 0xF,
PAINTCLIPBOARD = 0x309,
PAINTICON = 0x26,
PALETTECHANGED = 0x311,
PALETTEISCHANGING = 0x310,
PARENTNOTIFY = 0x210,
PASTE = 0x302,
PENWINFIRST = 0x380,
PENWINLAST = 0x38F,
POWER = 0x48,
PRINT = 0x317,
PRINTCLIENT = 0x318,
QUERYDRAGICON = 0x37,
QUERYENDSESSION = 0x11,
QUERYNEWPALETTE = 0x30F,
QUERYOPEN = 0x13,
QUEUESYNC = 0x23,
QUIT = 0x12,
RBUTTONDBLCLK = 0x206,
RBUTTONDOWN = 0x204,
RBUTTONUP = 0x205,
RENDERALLFORMATS = 0x306,
RENDERFORMAT = 0x305,
SETCURSOR = 0x20,
SETFOCUS = 0x7,
SETFONT = 0x30,
SETHOTKEY = 0x32,
SETICON = 0x80,
SETMARGINS = 0xD3,
SETREDRAW = 0xB,
SETTEXT = 0xC,
SETTINGCHANGE = 0x1A, // WININICHANGE = 0x1A,
SHOWWINDOW = 0x18,
SIZE = 0x5,
SIZECLIPBOARD = 0x30B,
SIZING = 0x214,
SPOOLERSTATUS = 0x2A,
STYLECHANGED = 0x7D,
STYLECHANGING = 0x7C,
SYNCPAINT = 0x88,
SYSCHAR = 0x106,
SYSCOLORCHANGE = 0x15,
SYSCOMMAND = 0x112,
SYSDEADCHAR = 0x107,
SYSKEYDOWN = 0x104,
SYSKEYUP = 0x105,
TCARD = 0x52,
TIMECHANGE = 0x1E,
TIMER = 0x113,
TVM_GETEDITCONTROL = 0x110F,
TVM_SETIMAGELIST = 0x1109,
UNDO = 0x304,
UNINITMENUPOPUP = 0x125,
USER = 0x400,
USERCHANGED = 0x54,
VKEYTOITEM = 0x2E,
VSCROLL = 0x115,
VSCROLLCLIPBOARD = 0x30A,
WINDOWPOSCHANGED = 0x47,
WINDOWPOSCHANGING = 0x46,
SH_NOTIFY = 0x0401,
}
// Specifies the content of the new menu item
[Flags]
private enum MFT : uint
{
GRAYED = 0x00000003, // DISABLED = 0x00000003,
CHECKED = 0x00000008,
SEPARATOR = 0x00000800,
RADIOCHECK = 0x00000200,
BITMAP = 0x00000004,
OWNERDRAW = 0x00000100,
MENUBARBREAK = 0x00000020,
MENUBREAK = 0x00000040,
RIGHTORDER = 0x00002000,
BYCOMMAND = 0x00000000,
BYPOSITION = 0x00000400,
POPUP = 0x00000010,
}
// Specifies the state of the new menu item
[Flags]
private enum MFS : uint
{
GRAYED = 0x00000003, // DISABLED = 0x00000003,
CHECKED = 0x00000008,
HILITE = 0x00000080,
ENABLED = 0x00000000, // UNCHECKED = 0x00000000, // UNHILITE = 0x00000000,
DEFAULT = 0x00001000,
}
// Specifies the content of the new menu item
[Flags]
private enum MIIM : uint
{
BITMAP = 0x80,
CHECKMARKS = 0x08,
DATA = 0x20,
FTYPE = 0x100,
ID = 0x02,
STATE = 0x01,
STRING = 0x40,
SUBMENU = 0x04,
TYPE = 0x10,
}
// Indicates the type of storage medium being used in a data transfer
[Flags]
private enum TYMED
{
ENHMF = 0x40,
FILE = 2,
GDI = 0x10,
HGLOBAL = 1,
ISTORAGE = 8,
ISTREAM = 4,
MFPICT = 0x20,
NULL = 0,
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214E6-0000-0000-C000-000000000046")]
private interface IShellFolder
{
// Translates a file object's or folder's display name into an item identifier list.
// Return value: error code, if any
[PreserveSig]
int ParseDisplayName(
IntPtr hwnd,
IntPtr pbc,
[MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName,
ref uint pchEaten,
out IntPtr ppidl,
ref SFGAO pdwAttributes);
// Allows a client to determine the contents of a folder by creating an item
// identifier enumeration object and returning its IEnumIDList interface.
// Return value: error code, if any
[PreserveSig]
int EnumObjects(
IntPtr hwnd,
SHCONTF grfFlags,
out IntPtr enumIDList);
// Retrieves an IShellFolder object for a subfolder.
// Return value: error code, if any
[PreserveSig]
int BindToObject(
IntPtr pidl,
IntPtr pbc,
ref Guid riid,
out IntPtr ppv);
// Requests a pointer to an object's storage interface.
// Return value: error code, if any
[PreserveSig]
int BindToStorage(
IntPtr pidl,
IntPtr pbc,
ref Guid riid,
out IntPtr ppv);
// Determines the relative order of two file objects or folders, given their
// item identifier lists. Return value: If this method is successful, the
// CODE field of the HRESULT contains one of the following values (the code
// can be retrived using the helper function GetHResultCode): Negative A
// negative return value indicates that the first item should precede
// the second (pidl1 < pidl2).
// Positive A positive return value indicates that the first item should
// follow the second (pidl1 > pidl2). Zero A return value of zero
// indicates that the two items are the same (pidl1 = pidl2).
[PreserveSig]
int CompareIDs(
IntPtr lParam,
IntPtr pidl1,
IntPtr pidl2);
// Requests an object that can be used to obtain information from or interact
// with a folder object.
// Return value: error code, if any
[PreserveSig]
int CreateViewObject(
IntPtr hwndOwner,
Guid riid,
out IntPtr ppv);
// Retrieves the attributes of one or more file objects or subfolders.
// Return value: error code, if any
[PreserveSig]
int GetAttributesOf(
uint cidl,
[MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl,
ref SFGAO rgfInOut);
// Retrieves an OLE interface that can be used to carry out actions on the
// specified file objects or folders.
// Return value: error code, if any
[PreserveSig]
int GetUIObjectOf(
IntPtr hwndOwner,
uint cidl,
[MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl,
ref Guid riid,
IntPtr rgfReserved,
out IntPtr ppv);
// Retrieves the display name for the specified file object or subfolder.
// Return value: error code, if any
[PreserveSig]
int GetDisplayNameOf(
IntPtr pidl,
SHGNO uFlags,
IntPtr lpName);
// Sets the display name of a file object or subfolder, changing the item
// identifier in the process.
// Return value: error code, if any
[PreserveSig]
int SetNameOf(
IntPtr hwnd,
IntPtr pidl,
[MarshalAs(UnmanagedType.LPWStr)] string pszName,
SHGNO uFlags,
out IntPtr ppidlOut);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214e4-0000-0000-c000-000000000046")]
private interface IContextMenu
{
// Adds commands to a shortcut menu
[PreserveSig]
int QueryContextMenu(
IntPtr hmenu,
uint iMenu,
uint idCmdFirst,
uint idCmdLast,
CMF uFlags);
// Carries out the command associated with a shortcut menu item
[PreserveSig]
int InvokeCommand(
ref CMINVOKECOMMANDINFOEX info);
// Retrieves information about a shortcut menu command,
// including the help string and the language-independent,
// or canonical, name for the command
[PreserveSig]
int GetCommandString(
uint idcmd,
GCS uflags,
uint reserved,
[MarshalAs(UnmanagedType.LPArray)] byte[] commandstring,
int cch);
}
[ComImport]
[Guid("000214f4-0000-0000-c000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IContextMenu2
{
// Adds commands to a shortcut menu
[PreserveSig]
int QueryContextMenu(
IntPtr hmenu,
uint iMenu,
uint idCmdFirst,
uint idCmdLast,
CMF uFlags);
// Carries out the command associated with a shortcut menu item
[PreserveSig]
int InvokeCommand(
ref CMINVOKECOMMANDINFOEX info);
// Retrieves information about a shortcut menu command,
// including the help string and the language-independent,
// or canonical, name for the command
[PreserveSig]
int GetCommandString(
uint idcmd,
GCS uflags,
uint reserved,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder commandstring,
int cch);
// Allows client objects of the IContextMenu interface to
// handle messages associated with owner-drawn menu items
[PreserveSig]
int HandleMenuMsg(
uint uMsg,
IntPtr wParam,
IntPtr lParam);
}
[ComImport]
[Guid("bcfce0a0-ec17-11d0-8d10-00a0c90f2719")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IContextMenu3
{
// Adds commands to a shortcut menu
[PreserveSig]
int QueryContextMenu(
IntPtr hmenu,
uint iMenu,
uint idCmdFirst,
uint idCmdLast,
CMF uFlags);
// Carries out the command associated with a shortcut menu item
[PreserveSig]
int InvokeCommand(
ref CMINVOKECOMMANDINFOEX info);
// Retrieves information about a shortcut menu command,
// including the help string and the language-independent,
// or canonical, name for the command
[PreserveSig]
int GetCommandString(
uint idcmd,
GCS uflags,
uint reserved,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder commandstring,
int cch);
// Allows client objects of the IContextMenu interface to
// handle messages associated with owner-drawn menu items
[PreserveSig]
int HandleMenuMsg(
uint uMsg,
IntPtr wParam,
IntPtr lParam);
// Allows client objects of the IContextMenu3 interface to
// handle messages associated with owner-drawn menu items
[PreserveSig]
int HandleMenuMsg2(
uint uMsg,
IntPtr wParam,
IntPtr lParam,
IntPtr plResult);
}
///
/// Shows the context menu.
///
/// FileInfos (should all be in same directory).
/// Where to show the menu.
public void ShowContextMenu(FileInfo[] files, Point pointScreen)
{
// Release all resources first.
ReleaseAll();
arrPIDLs = GetPIDLs(files);
ShowContextMenu(pointScreen);
}
///
/// Shows the context menu.
///
/// DirectoryInfos (should all be in same directory).
/// Where to show the menu.
public void ShowContextMenu(DirectoryInfo[] dirs, Point pointScreen)
{
// Release all resources first.
ReleaseAll();
arrPIDLs = GetPIDLs(dirs);
ShowContextMenu(pointScreen);
}
///
/// Shows the context menu.
///
/// Where to show the menu.
public void ShowContextMenu(Point pointScreen)
{
IntPtr pMenu = IntPtr.Zero,
iContextMenuPtr = IntPtr.Zero,
iContextMenuPtr2 = IntPtr.Zero,
iContextMenuPtr3 = IntPtr.Zero;
try
{
if (arrPIDLs == null)
{
ReleaseAll();
return;
}
if (!GetContextMenuInterfaces(oParentFolder, arrPIDLs, out iContextMenuPtr))
{
ReleaseAll();
return;
}
pMenu = DllImports.NativeMethods.User32CreatePopupMenu();
#if TODO // WPF: Implement Control and replace active code with disabled one
int nResult = oContextMenu.QueryContextMenu(
pMenu,
0,
CmdFirst,
CmdLast,
CMF.EXPLORE | CMF.NORMAL | ((Control.ModifierKeys & Key.Shift) != 0 ? CMF.EXTENDEDVERBS : 0));
#else
int nResult = oContextMenu.QueryContextMenu(
pMenu,
0,
CmdFirst,
CmdLast,
CMF.EXPLORE | CMF.NORMAL);
#endif
Marshal.QueryInterface(iContextMenuPtr, ref iidIContextMenu2, out iContextMenuPtr2);
Marshal.QueryInterface(iContextMenuPtr, ref iidIContextMenu3, out iContextMenuPtr3);
oContextMenu2 = (IContextMenu2)Marshal.GetTypedObjectForIUnknown(iContextMenuPtr2, typeof(IContextMenu2));
oContextMenu3 = (IContextMenu3)Marshal.GetTypedObjectForIUnknown(iContextMenuPtr3, typeof(IContextMenu3));
uint nSelected = DllImports.NativeMethods.User32TrackPopupMenuEx(
pMenu,
DllImports.NativeMethods.TPM.RETURNCMD,
(int)pointScreen.X,
(int)pointScreen.Y,
Handle,
IntPtr.Zero);
// First invoke menu item, then destroy menu
// (otherwise: Right click on items to choose "Send to" doesn't work #225)
if (nSelected != 0)
{
InvokeCommand(oContextMenu, nSelected, strParentFolder, pointScreen);
}
DllImports.NativeMethods.User32DestroyMenu(pMenu);
pMenu = IntPtr.Zero;
}
catch
{
throw;
}
finally
{
if (pMenu != IntPtr.Zero)
{
DllImports.NativeMethods.User32DestroyMenu(pMenu);
}
if (iContextMenuPtr != IntPtr.Zero)
{
Marshal.Release(iContextMenuPtr);
}
if (iContextMenuPtr2 != IntPtr.Zero)
{
Marshal.Release(iContextMenuPtr2);
}
if (iContextMenuPtr3 != IntPtr.Zero)
{
Marshal.Release(iContextMenuPtr3);
}
ReleaseAll();
}
}
///
/// Free the PIDLs.
///
/// Array of PIDLs (IntPtr).
protected static void FreePIDLs(IntPtr[] arrPIDLs)
{
if (arrPIDLs != null)
{
for (int n = 0; n < arrPIDLs.Length; n++)
{
if (arrPIDLs[n] != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(arrPIDLs[n]);
arrPIDLs[n] = IntPtr.Zero;
}
}
}
}
///
/// This method receives WindowMessages. It will make the "Open With" and "Send To" work
/// by calling HandleMenuMsg and HandleMenuMsg2. It will also call the OnContextMenuMouseHover
/// method of Browser when hovering over a ContextMenu item.
///
protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (oContextMenu2 != null &&
(msg == (int)WM.INITMENUPOPUP ||
msg == (int)WM.MEASUREITEM ||
msg == (int)WM.DRAWITEM))
{
if (oContextMenu2.HandleMenuMsg(
(uint)msg, wParam, lParam) == ResultOK)
{
handled = true;
return IntPtr.Zero;
}
}
if (oContextMenu3 != null &&
msg == (int)WM.MENUCHAR)
{
if (oContextMenu3.HandleMenuMsg2(
(uint)msg, wParam, lParam, IntPtr.Zero) == ResultOK)
{
handled = true;
return IntPtr.Zero;
}
}
handled = false;
return IntPtr.Zero;
}
///
/// Get the PIDLs.
///
/// Array of FileInfo.
/// Array of PIDLs.
protected IntPtr[] GetPIDLs(FileInfo[] arrFI)
{
if (arrFI == null || arrFI.Length == 0)
{
return null;
}
IShellFolder oParentFolder = GetParentFolder(arrFI[0].DirectoryName);
if (oParentFolder == null)
{
return null;
}
IntPtr[] arrPIDLs = new IntPtr[arrFI.Length];
int n = 0;
foreach (FileInfo fi in arrFI)
{
// Get the file relative to folder
uint pchEaten = 0;
SFGAO pdwAttributes = 0;
IntPtr pPIDL = IntPtr.Zero;
int nResult = oParentFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, fi.Name, ref pchEaten, out pPIDL, ref pdwAttributes);
if (nResult != ResultOK)
{
FreePIDLs(arrPIDLs);
return null;
}
arrPIDLs[n] = pPIDL;
n++;
}
return arrPIDLs;
}
///
/// Get the PIDLs.
///
/// Array of DirectoryInfo.
/// Array of PIDLs.
protected IntPtr[] GetPIDLs(DirectoryInfo[] arrFI)
{
if (arrFI == null || arrFI.Length == 0 || arrFI[0].Parent == null)
{
return null;
}
IShellFolder oParentFolder = GetParentFolder(arrFI[0].Parent.FullName);
if (oParentFolder == null)
{
return null;
}
IntPtr[] arrPIDLs = new IntPtr[arrFI.Length];
int n = 0;
foreach (DirectoryInfo fi in arrFI)
{
// Get the file relative to folder
uint pchEaten = 0;
SFGAO pdwAttributes = 0;
IntPtr pPIDL = IntPtr.Zero;
int nResult = oParentFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, fi.Name, ref pchEaten, out pPIDL, ref pdwAttributes);
if (nResult != ResultOK)
{
FreePIDLs(arrPIDLs);
return null;
}
arrPIDLs[n] = pPIDL;
n++;
}
return arrPIDLs;
}
private static void InvokeCommand(IContextMenu contextMenu, uint nCmd, string strFolder, Point pointInvoke)
{
#if TODO
CMINVOKECOMMANDINFOEX invoke = new()
{
CbSize = CbInvokeCommand,
LpVerb = (IntPtr)(nCmd - CmdFirst),
LpDirectory = strFolder,
LpVerbW = (IntPtr)(nCmd - CmdFirst),
LpDirectoryW = strFolder,
FMask = CMIC.UNICODE | CMIC.PTINVOKE |
((Control.ModifierKeys & Key.Control) != 0 ? CMIC.CONTROL_DOWN : 0) |
((Control.ModifierKeys & Key.Shift) != 0 ? CMIC.SHIFT_DOWN : 0),
PtInvoke = new POINT(pointInvoke.X, pointInvoke.Y),
NShow = SW.SHOWNORMAL,
};
_ = contextMenu.InvokeCommand(ref invoke);
#endif
}
/// Gets the interfaces to the context menu.
/// Parent folder.
/// PIDLs.
/// true if it got the interfaces, otherwise false.
private bool GetContextMenuInterfaces(IShellFolder oParentFolder, IntPtr[] arrPIDLs, out IntPtr ctxMenuPtr)
{
int nResult = oParentFolder.GetUIObjectOf(
IntPtr.Zero,
(uint)arrPIDLs.Length,
arrPIDLs,
ref iidIContextMenu,
IntPtr.Zero,
out ctxMenuPtr);
if (nResult == ResultOK)
{
oContextMenu = (IContextMenu)Marshal.GetTypedObjectForIUnknown(ctxMenuPtr, typeof(IContextMenu));
/*IntPtr pUnknownContextMenu2 = IntPtr.Zero;
if (S_OK == Marshal.QueryInterface(pUnknownContextMenu, ref IID_IContextMenu2, out pUnknownContextMenu2))
{
_oContextMenu2 = (IContextMenu2)Marshal.GetTypedObjectForIUnknown(pUnknownContextMenu2, typeof(IContextMenu2));
}
IntPtr pUnknownContextMenu3 = IntPtr.Zero;
if (S_OK == Marshal.QueryInterface(pUnknownContextMenu, ref IID_IContextMenu3, out pUnknownContextMenu3))
{
_oContextMenu3 = (IContextMenu3)Marshal.GetTypedObjectForIUnknown(pUnknownContextMenu3, typeof(IContextMenu3));
}*/
return true;
}
else
{
ctxMenuPtr = IntPtr.Zero;
oContextMenu = null;
return false;
}
}
///
/// Release all allocated interfaces, PIDLs.
///
private void ReleaseAll()
{
if (oContextMenu != null)
{
Marshal.ReleaseComObject(oContextMenu);
oContextMenu = null;
}
if (oContextMenu2 != null)
{
Marshal.ReleaseComObject(oContextMenu2);
oContextMenu2 = null;
}
if (oContextMenu3 != null)
{
Marshal.ReleaseComObject(oContextMenu3);
oContextMenu3 = null;
}
if (oDesktopFolder != null)
{
Marshal.ReleaseComObject(oDesktopFolder);
oDesktopFolder = null;
}
if (oParentFolder != null)
{
Marshal.ReleaseComObject(oParentFolder);
oParentFolder = null;
}
if (arrPIDLs != null)
{
FreePIDLs(arrPIDLs);
arrPIDLs = null;
}
}
///
/// Gets the desktop folder.
///
/// IShellFolder for desktop folder.
private IShellFolder GetDesktopFolder()
{
if (oDesktopFolder == null)
{
// Get desktop IShellFolder
int nResult = DllImports.NativeMethods.Shell32SHGetDesktopFolder(out IntPtr pUnkownDesktopFolder);
if (nResult != ResultOK)
{
throw new ShellContextMenuException("Failed to get the desktop shell folder");
}
oDesktopFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(pUnkownDesktopFolder, typeof(IShellFolder));
}
return oDesktopFolder;
}
///
/// Gets the parent folder.
///
/// Folder path.
/// IShellFolder for the folder (relative from the desktop).
private IShellFolder GetParentFolder(string folderName)
{
if (oParentFolder == null)
{
IShellFolder oDesktopFolder = GetDesktopFolder();
if (oDesktopFolder == null)
{
return null;
}
// Get the PIDL for the folder file is in
uint pchEaten = 0;
SFGAO pdwAttributes = 0;
int nResult = oDesktopFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, folderName, ref pchEaten, out IntPtr pPIDL, ref pdwAttributes);
if (nResult != ResultOK)
{
return null;
}
IntPtr pStrRet = Marshal.AllocCoTaskMem((MaxPath * 2) + 4);
Marshal.WriteInt32(pStrRet, 0, 0);
_ = this.oDesktopFolder.GetDisplayNameOf(pPIDL, SHGNO.FORPARSING, pStrRet);
StringBuilder strFolder = new(MaxPath);
_ = DllImports.NativeMethods.ShlwapiStrRetToBuf(pStrRet, pPIDL, strFolder, MaxPath);
Marshal.FreeCoTaskMem(pStrRet);
strParentFolder = strFolder.ToString();
// Get the IShellFolder for folder
nResult = oDesktopFolder.BindToObject(pPIDL, IntPtr.Zero, ref iidIShellFolder, out IntPtr pUnknownParentFolder);
// Free the PIDL first
Marshal.FreeCoTaskMem(pPIDL);
if (nResult != ResultOK)
{
return null;
}
oParentFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(pUnknownParentFolder, typeof(IShellFolder));
}
return oParentFolder;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct CWPSTRUCT
{
public IntPtr Lparam;
public IntPtr Wparam;
public int Message;
public IntPtr Hwnd;
}
// Contains extended information about a shortcut menu command
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct CMINVOKECOMMANDINFOEX
{
public int CbSize;
public CMIC FMask;
public IntPtr Hwnd;
public IntPtr LpVerb;
[MarshalAs(UnmanagedType.LPStr)]
public string LpParameters;
[MarshalAs(UnmanagedType.LPStr)]
public string LpDirectory;
public SW NShow;
public int DwHotKey;
public IntPtr HIcon;
[MarshalAs(UnmanagedType.LPStr)]
public string LpTitle;
public IntPtr LpVerbW;
[MarshalAs(UnmanagedType.LPWStr)]
public string LpParametersW;
[MarshalAs(UnmanagedType.LPWStr)]
public string LpDirectoryW;
[MarshalAs(UnmanagedType.LPWStr)]
public string LpTitleW;
public POINT PtInvoke;
}
// Contains information about a menu item
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct MENUITEMINFO
{
private readonly int cbSize;
private readonly MIIM fMask;
private readonly MFT fType;
private readonly MFS fState;
private readonly uint wID;
private readonly IntPtr hSubMenu;
private readonly IntPtr hbmpChecked;
private readonly IntPtr hbmpUnchecked;
private readonly IntPtr dwItemData;
[MarshalAs(UnmanagedType.LPTStr)]
private readonly string dwTypeData;
private readonly int cch;
private readonly IntPtr hbmpItem;
public MENUITEMINFO(string text)
{
cbSize = CbMenuItemInfo;
dwTypeData = text;
cch = text.Length;
fMask = 0;
fType = 0;
fState = 0;
wID = 0;
hSubMenu = IntPtr.Zero;
hbmpChecked = IntPtr.Zero;
hbmpUnchecked = IntPtr.Zero;
dwItemData = IntPtr.Zero;
hbmpItem = IntPtr.Zero;
}
}
// A generalized global memory handle used for data transfer operations by the
// IAdviseSink, IDataObject, and IOleCache interfaces
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct STGMEDIUM
{
public TYMED Tymed;
public IntPtr HBitmap;
public IntPtr HMetaFilePict;
public IntPtr HEnhMetaFile;
public IntPtr HGlobal;
public IntPtr LpszFileName;
public IntPtr Pstm;
public IntPtr Pstg;
public IntPtr PUnkForRelease;
}
// Defines the x- and y-coordinates of a point
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
X = x;
Y = y;
}
}
}
}