1+ using Files . Common ;
2+ using System ;
3+ using System . Collections . Generic ;
4+ using System . Diagnostics ;
5+ using System . Drawing ;
6+ using System . IO ;
7+ using System . Linq ;
8+ using System . Runtime . InteropServices ;
9+ using Vanara . InteropServices ;
10+ using Vanara . PInvoke ;
11+ using Vanara . Windows . Shell ;
12+
13+ namespace FilesFullTrust
14+ {
15+ public class ContextMenu : Win32ContextMenu , IDisposable
16+ {
17+ private Shell32 . IContextMenu cMenu ;
18+ private User32 . SafeHMENU hMenu ;
19+ public List < string > ItemsPath { get ; }
20+
21+ public ContextMenu ( Shell32 . IContextMenu cMenu , User32 . SafeHMENU hMenu , IEnumerable < string > itemsPath )
22+ {
23+ this . cMenu = cMenu ;
24+ this . hMenu = hMenu ;
25+ this . ItemsPath = itemsPath . ToList ( ) ;
26+ this . Items = new List < Win32ContextMenuItem > ( ) ;
27+ }
28+
29+ public bool InvokeVerb ( string verb )
30+ {
31+ if ( string . IsNullOrEmpty ( verb ) )
32+ {
33+ return false ;
34+ }
35+
36+ try
37+ {
38+ var currentWindows = Win32API . GetDesktopWindows ( ) ;
39+ var pici = new Shell32 . CMINVOKECOMMANDINFOEX ( ) ;
40+ pici . lpVerb = new SafeResourceId ( verb , CharSet . Ansi ) ;
41+ pici . nShow = ShowWindowCommand . SW_SHOWNORMAL ;
42+ pici . cbSize = ( uint ) Marshal . SizeOf ( pici ) ;
43+ cMenu . InvokeCommand ( pici ) ;
44+ Win32API . BringToForeground ( currentWindows ) ;
45+ return true ;
46+ }
47+ catch ( Exception ex ) when (
48+ ex is COMException
49+ || ex is UnauthorizedAccessException )
50+ {
51+ Debug . WriteLine ( ex ) ;
52+ }
53+ return false ;
54+ }
55+
56+ public void InvokeItem ( int itemID )
57+ {
58+ if ( itemID < 0 )
59+ {
60+ return ;
61+ }
62+
63+ try
64+ {
65+ var currentWindows = Win32API . GetDesktopWindows ( ) ;
66+ var pici = new Shell32 . CMINVOKECOMMANDINFOEX ( ) ;
67+ pici . lpVerb = Macros . MAKEINTRESOURCE ( itemID ) ;
68+ pici . nShow = ShowWindowCommand . SW_SHOWNORMAL ;
69+ pici . cbSize = ( uint ) Marshal . SizeOf ( pici ) ;
70+ cMenu . InvokeCommand ( pici ) ;
71+ Win32API . BringToForeground ( currentWindows ) ;
72+ }
73+ catch ( Exception ex ) when (
74+ ex is COMException
75+ || ex is UnauthorizedAccessException )
76+ {
77+ Debug . WriteLine ( ex ) ;
78+ }
79+ }
80+
81+ #region FactoryMethods
82+
83+ public static ContextMenu GetContextMenuForFiles ( string [ ] filePathList , Shell32 . CMF flags , Func < string , bool > itemFilter = null )
84+ {
85+ List < ShellItem > shellItems = new List < ShellItem > ( ) ;
86+ try
87+ {
88+ foreach ( var fp in filePathList . Where ( x => ! string . IsNullOrEmpty ( x ) ) )
89+ {
90+ shellItems . Add ( new ShellItem ( fp ) ) ;
91+ }
92+
93+ return GetContextMenuForFiles ( shellItems . ToArray ( ) , flags , itemFilter ) ;
94+ }
95+ catch ( Exception ex ) when ( ex is ArgumentException || ex is FileNotFoundException )
96+ {
97+ // Return empty context menu
98+ return null ;
99+ }
100+ finally
101+ {
102+ foreach ( var si in shellItems )
103+ {
104+ si . Dispose ( ) ;
105+ }
106+ }
107+ }
108+
109+ private static ContextMenu GetContextMenuForFiles ( ShellItem [ ] shellItems , Shell32 . CMF flags , Func < string , bool > itemFilter = null )
110+ {
111+ if ( shellItems == null || ! shellItems . Any ( ) )
112+ {
113+ return null ;
114+ }
115+
116+ using var sf = shellItems . First ( ) . Parent ; // HP: the items are all in the same folder
117+ Shell32 . IContextMenu menu = sf . GetChildrenUIObjects < Shell32 . IContextMenu > ( null , shellItems ) ;
118+ var hMenu = User32 . CreatePopupMenu ( ) ;
119+ menu . QueryContextMenu ( hMenu , 0 , 1 , 0x7FFF , flags ) ;
120+ var contextMenu = new ContextMenu ( menu , hMenu , shellItems . Select ( x => x . ParsingName ) ) ;
121+ ContextMenu . EnumMenuItems ( menu , hMenu , contextMenu . Items , itemFilter ) ;
122+ return contextMenu ;
123+ }
124+
125+ #endregion FactoryMethods
126+
127+ private static void EnumMenuItems (
128+ Shell32 . IContextMenu cMenu ,
129+ HMENU hMenu ,
130+ List < Win32ContextMenuItem > menuItemsResult ,
131+ Func < string , bool > itemFilter = null )
132+ {
133+ var itemCount = User32 . GetMenuItemCount ( hMenu ) ;
134+ var mii = new User32 . MENUITEMINFO ( ) ;
135+ mii . cbSize = ( uint ) Marshal . SizeOf ( mii ) ;
136+ mii . fMask = User32 . MenuItemInfoMask . MIIM_BITMAP
137+ | User32 . MenuItemInfoMask . MIIM_FTYPE
138+ | User32 . MenuItemInfoMask . MIIM_STRING
139+ | User32 . MenuItemInfoMask . MIIM_ID
140+ | User32 . MenuItemInfoMask . MIIM_SUBMENU ;
141+ for ( uint ii = 0 ; ii < itemCount ; ii ++ )
142+ {
143+ var menuItem = new ContextMenuItem ( ) ;
144+ var container = new SafeCoTaskMemString ( 512 ) ;
145+ mii . dwTypeData = ( IntPtr ) container ;
146+ mii . cch = ( uint ) container . Capacity - 1 ; // https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723
147+ var retval = User32 . GetMenuItemInfo ( hMenu , ii , true , ref mii ) ;
148+ if ( ! retval )
149+ {
150+ container . Dispose ( ) ;
151+ continue ;
152+ }
153+ menuItem . Type = ( MenuItemType ) mii . fType ;
154+ menuItem . ID = ( int ) ( mii . wID - 1 ) ; // wID - idCmdFirst
155+ if ( menuItem . Type == MenuItemType . MFT_STRING )
156+ {
157+ Debug . WriteLine ( "Item {0} ({1}): {2}" , ii , mii . wID , mii . dwTypeData ) ;
158+ menuItem . Label = mii . dwTypeData ;
159+ menuItem . CommandString = GetCommandString ( cMenu , mii . wID - 1 ) ;
160+ if ( itemFilter != null && ( itemFilter ( menuItem . CommandString ) || itemFilter ( menuItem . Label ) ) )
161+ {
162+ // Skip items implemented in UWP
163+ container . Dispose ( ) ;
164+ continue ;
165+ }
166+ if ( mii . hbmpItem != HBITMAP . NULL && ! Enum . IsDefined ( typeof ( HBITMAP_HMENU ) , ( ( IntPtr ) mii . hbmpItem ) . ToInt64 ( ) ) )
167+ {
168+ using var bitmap = Win32API . GetBitmapFromHBitmap ( mii . hbmpItem ) ;
169+ if ( bitmap != null )
170+ {
171+ byte [ ] bitmapData = ( byte [ ] ) new ImageConverter ( ) . ConvertTo ( bitmap , typeof ( byte [ ] ) ) ;
172+ menuItem . IconBase64 = Convert . ToBase64String ( bitmapData , 0 , bitmapData . Length ) ;
173+ }
174+ }
175+ if ( mii . hSubMenu != HMENU . NULL )
176+ {
177+ Debug . WriteLine ( "Item {0}: has submenu" , ii ) ;
178+ var subItems = new List < Win32ContextMenuItem > ( ) ;
179+ try
180+ {
181+ ( cMenu as Shell32 . IContextMenu2 ) ? . HandleMenuMsg ( ( uint ) User32 . WindowMessage . WM_INITMENUPOPUP , ( IntPtr ) mii . hSubMenu , new IntPtr ( ii ) ) ;
182+ }
183+ catch ( NotImplementedException )
184+ {
185+ // Only for dynamic/owner drawn? (open with, etc)
186+ }
187+ EnumMenuItems ( cMenu , mii . hSubMenu , subItems , itemFilter ) ;
188+ menuItem . SubItems = subItems ;
189+ Debug . WriteLine ( "Item {0}: done submenu" , ii ) ;
190+ }
191+ }
192+ else
193+ {
194+ Debug . WriteLine ( "Item {0}: {1}" , ii , mii . fType . ToString ( ) ) ;
195+ }
196+ container . Dispose ( ) ;
197+ menuItemsResult . Add ( menuItem ) ;
198+ }
199+ }
200+
201+ private static string GetCommandString ( Shell32 . IContextMenu cMenu , uint offset , Shell32 . GCS flags = Shell32 . GCS . GCS_VERBW )
202+ {
203+ if ( offset > 5000 )
204+ {
205+ // Hackish workaround to avoid an AccessViolationException on some items,
206+ // notably the "Run with graphic processor" menu item of NVidia cards
207+ return null ;
208+ }
209+ SafeCoTaskMemString commandString = null ;
210+ try
211+ {
212+ commandString = new SafeCoTaskMemString ( 512 ) ;
213+ cMenu . GetCommandString ( new IntPtr ( offset ) , flags , IntPtr . Zero , commandString , ( uint ) commandString . Capacity - 1 ) ;
214+ Debug . WriteLine ( "Verb {0}: {1}" , offset , commandString ) ;
215+ return commandString . ToString ( ) ;
216+ }
217+ catch ( Exception ex ) when ( ex is InvalidCastException || ex is ArgumentException )
218+ {
219+ // TODO: investigate this..
220+ Debug . WriteLine ( ex ) ;
221+ return null ;
222+ }
223+ catch ( Exception ex ) when ( ex is COMException || ex is NotImplementedException )
224+ {
225+ // Not every item has an associated verb
226+ return null ;
227+ }
228+ finally
229+ {
230+ commandString ? . Dispose ( ) ;
231+ }
232+ }
233+
234+ #region IDisposable Support
235+
236+ private bool disposedValue = false ; // To detect redundant calls
237+
238+ protected virtual void Dispose ( bool disposing )
239+ {
240+ if ( ! disposedValue )
241+ {
242+ if ( disposing )
243+ {
244+ // TODO: dispose managed state (managed objects).
245+ if ( Items != null )
246+ {
247+ foreach ( var si in Items )
248+ {
249+ ( si as IDisposable ) ? . Dispose ( ) ;
250+ }
251+
252+ Items = null ;
253+ }
254+ }
255+
256+ // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
257+ if ( hMenu != null )
258+ {
259+ User32 . DestroyMenu ( hMenu ) ;
260+ hMenu = null ;
261+ }
262+ if ( cMenu != null )
263+ {
264+ Marshal . ReleaseComObject ( cMenu ) ;
265+ cMenu = null ;
266+ }
267+
268+ disposedValue = true ;
269+ }
270+ }
271+
272+ ~ ContextMenu ( )
273+ {
274+ Dispose ( false ) ;
275+ }
276+
277+ public void Dispose ( )
278+ {
279+ Dispose ( true ) ;
280+ GC . SuppressFinalize ( this ) ;
281+ }
282+
283+ #endregion IDisposable Support
284+
285+ public enum HBITMAP_HMENU : long
286+ {
287+ HBMMENU_CALLBACK = - 1 ,
288+ HBMMENU_MBAR_CLOSE = 5 ,
289+ HBMMENU_MBAR_CLOSE_D = 6 ,
290+ HBMMENU_MBAR_MINIMIZE = 3 ,
291+ HBMMENU_MBAR_MINIMIZE_D = 7 ,
292+ HBMMENU_MBAR_RESTORE = 2 ,
293+ HBMMENU_POPUP_CLOSE = 8 ,
294+ HBMMENU_POPUP_MAXIMIZE = 10 ,
295+ HBMMENU_POPUP_MINIMIZE = 11 ,
296+ HBMMENU_POPUP_RESTORE = 9 ,
297+ HBMMENU_SYSTEM = 1
298+ }
299+ }
300+
301+ public class ContextMenuItem : Win32ContextMenuItem , IDisposable
302+ {
303+ public ContextMenuItem ( )
304+ {
305+ this . SubItems = new List < Win32ContextMenuItem > ( ) ;
306+ }
307+
308+ public void Dispose ( )
309+ {
310+ if ( SubItems != null )
311+ {
312+ foreach ( var si in SubItems )
313+ {
314+ ( si as IDisposable ) ? . Dispose ( ) ;
315+ }
316+
317+ SubItems = null ;
318+ }
319+ }
320+ }
321+ }
0 commit comments