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