How do I loop through all items and sub items of a IContextMenu and list all available verbs? So far, I have this working code extracted from JCL:
function DisplayContextMenuPidl(const Handle: THandle; const Folder: IShellFolder; Item: PItemIdList; Pos: TPoint): Boolean;
var
Cmd: Cardinal;
ContextMenu: IContextMenu;
ContextMenu2: IContextMenu2;
Menu: HMENU;
CommandInfo: TCMInvokeCommandInfo;
CallbackWindow: THandle;
vteste : string;
begin
Result := False;
if (Item = nil) or (Folder = nil) then
Exit;
Folder.GetUIObjectOf(Handle, 1, Item, IID_IContextMenu, nil,
Pointer(ContextMenu));
if ContextMenu <> nil then
begin
Menu := CreatePopupMenu;
if Menu <> 0 then
begin
if Succeeded(ContextMenu.QueryContextMenu(Menu, 0, 1, $7FFF, CMF_EXPLORE)) then
begin
CallbackWindow := 0;
if Succeeded(ContextMenu.QueryInterface(IContextMenu2, ContextMenu2)) then
begin
CallbackWindow := CreateMenuCallbackWnd(ContextMenu2);
end;
ClientToScreen(Handle, Pos);
Cmd := Cardinal(TrackPopupMenu(Menu, TPM_LEFTALIGN or TPM_LEFTBUTTON or TPM_RIGHTBUTTON or TPM_RETURNCMD, Pos.X, Pos.Y, 0, CallbackWindow, nil));
if Cmd <> 0 then
begin
ResetMemory(CommandInfo, SizeOf(CommandInfo));
CommandInfo.cbSize := SizeOf(TCMInvokeCommandInfo);
CommandInfo.hwnd := Handle;
CommandInfo.lpVerb := MakeIntResourceA(Cmd - 1);
CommandInfo.nShow := SW_SHOWNORMAL;
Result := Succeeded(ContextMenu.InvokeCommand(CommandInfo));
end;
if CallbackWindow <> 0 then
DestroyWindow(CallbackWindow);
end;
DestroyMenu(Menu);
end;
end;
end;
This code works fine and it shows the context menu. I need to adapt it so it can list (maybe a log file) all the menu and submenus verbs.
EDIT
To clarify lets assume I have this context menu:
I want to log something like this:
Item verb
open= open
properties= properties
send to= sendto
send to bluetooh= xxx
EDIT
If somebody has another way of getting the verbs or call a item by its display text I would also appreciate it.
To enumerate the items of the context menu you can use the Windows Menu functions
: GetMenuItemCount , GetMenuItemInfo, GetSubMenu.
using these functions i wrote this function
uses
JclShell,
ShlObj;
function DisplayContextMenuInfo( const Folder: IShellFolder; Item: PItemIdList; List :TStrings): Boolean;
function GetMenuItemCaption(const hSubMenu: HMENU; const MenuId: Integer): string;
var
MenuItemInfo: TMenuItemInfo;
begin
MenuItemInfo.cbSize := SizeOf(MenuItemInfo);
MenuItemInfo.fMask := MIIM_STRING;
SetLength(Result, 1024*Sizeof(Char));
MenuItemInfo.dwTypeData := PChar(Result);
MenuItemInfo.cch := Length(Result)-1;
if not GetMenuItemInfo(hSubMenu, MenuId, False, MenuItemInfo) then
RaiseLastOSError;
SetLength(Result, MenuItemInfo.cch*Sizeof(Char));
end;
Procedure LogGetMenuInfo(Menu: HMENU);
var
i : Integer;
ItemsCount : Integer;
MenuId : Cardinal;
Caption : string;
begin
ItemsCount:=GetMenuItemCount(Menu);
List.Add(Format('Number of items %d ',[ItemsCount]));
for i:= 0 to ItemsCount - 1 do
begin
MenuId:=GetMenuItemID(Menu,i);
case MenuId of
Cardinal(-1) : begin
List.Add('');
List.Add(Format('Sub Menu',[]));
LogGetMenuInfo(GetSubMenu(Menu,i));
end;
0 :
else
begin
Caption:=GetMenuItemCaption(Menu, MenuId);
List.Add(Format('MenuId (Cmd) %d Caption %s ',[MenuId,Caption]))
end;
end;
end;
end;
var
ContextMenu: IContextMenu;
Menu: HMENU;
begin
Result := False;
if (Item = nil) or (Folder = nil) then Exit;
Folder.GetUIObjectOf(0, 1, Item, IID_IContextMenu, nil, Pointer(ContextMenu));
if ContextMenu <> nil then
begin
Menu := CreatePopupMenu;
try
if Menu <> 0 then
if Succeeded(ContextMenu.QueryContextMenu(Menu, 0, 1, $7FFF, CMF_EXPLORE)) then
LogGetMenuInfo(Menu);
finally
DestroyMenu(Menu);
end;
end;
end;
and call in this way
var
ItemIdList: PItemIdList;
Folder : IShellFolder;
FileName : string;
begin
FileName:= 'C:\Users\Dexter\Downloads\VirtualTreeview.pdf';
ItemIdList := PathToPidlBind(FileName, Folder);
if ItemIdList <> nil then
begin
DisplayContextMenuInfo( Folder, ItemIdList, Memo1.lines);
PidlFree(ItemIdList);
end;
end;
this will fill the TStrings passed as parameter with info like this :
Number of items 26
MenuId (Cmd) 141 Caption Open with Adobe Reader X
MenuId (Cmd) 142 Caption &Open
MenuId (Cmd) 143 Caption &Print
MenuId (Cmd) 146 Caption Run &Sandboxed
MenuId (Cmd) 140 Caption Analizar con &AVG
Sub Menu
Number of items 28
MenuId (Cmd) 105 Caption Add To Send To
MenuId (Cmd) 106 Caption Add To Templates
MenuId (Cmd) 107 Caption Change Date && Time
MenuId (Cmd) 108 Caption Change Extension: pdf
MenuId (Cmd) 109 Caption Choose Program
MenuId (Cmd) 110 Caption Command Prompt
MenuId (Cmd) 111 Caption Copy/Move To Folder
MenuId (Cmd) 112 Caption Copy Path
MenuId (Cmd) 113 Caption Delete On Reboot
MenuId (Cmd) 114 Caption Duplicate File
MenuId (Cmd) 115 Caption Encrypt File
MenuId (Cmd) 116 Caption Explore Rooted
MenuId (Cmd) 117 Caption Extended Delete
MenuId (Cmd) 118 Caption Extended Search && Replace
now using this function you can pass the menuid (cmd) which you want execute
function InvokeContextMenuCommand(const Comnand: Cardinal; const Folder: IShellFolder; Item: PItemIdList): Boolean;
var
ContextMenu : IContextMenu;
CommandInfo : TCMInvokeCommandInfo;
Menu : HMENU;
CallbackWindow: THandle;
begin
Result := False;
if Comnand=0 then exit;
if (Item = nil) or (Folder = nil) then Exit;
Folder.GetUIObjectOf(0, 1, Item, IID_IContextMenu, nil, Pointer(ContextMenu));
if ContextMenu <> nil then
begin
Menu := CreatePopupMenu;
try
if Menu <> 0 then
if Succeeded(ContextMenu.QueryContextMenu(Menu, 0, 1, $7FFF, CMF_EXPLORE)) then
begin
CallbackWindow:=0;
TrackPopupMenu(Menu, TPM_LEFTALIGN or TPM_LEFTBUTTON or TPM_RIGHTBUTTON or TPM_RETURNCMD, 0, 0, 0, CallbackWindow, nil);
ZeroMemory(@CommandInfo, SizeOf(CommandInfo));
CommandInfo.cbSize := SizeOf(TCMInvokeCommandInfo);
CommandInfo.hwnd := 0;
CommandInfo.lpVerb := MakeIntResourceA(Comnand - 1);
CommandInfo.nShow := SW_SHOWNORMAL;
Result := Succeeded(ContextMenu.InvokeCommand(CommandInfo));
end;
finally
DestroyMenu(Menu);
end;
end;
end;
call in this way
var
ItemIdList: PItemIdList;
Folder : IShellFolder;
FileName : string;
begin
FileName:= 'C:\Users\Dexter\Downloads\VirtualTreeview.pdf';
ItemIdList := PathToPidlBind(FileName, Folder);
if ItemIdList <> nil then
begin
//calling the 141 Menuid = `Open with Adobe Reader X`
InvokeContextMenuCommand(141,Folder, ItemIdList);
PidlFree(ItemIdList);
end;
end;
After you call QueryContextMenu
your menu will be mostly populated. You know your menu's handle, so can iterate its items and get the information you need.
function DisplayContextMenuPidl(const Handle: THandle; const Folder: IShellFolder; Item: PItemIdList; Pos: TPoint): Boolean;
//++
procedure RecurseItems(const Menu: HMENU; Strings: TStrings; Indent: Integer = 0);
function GetItemString(Parent: HMENU; Item: Integer): string;
begin
SetLength(Result, GetMenuString(Parent, Item, nil, 0, MF_BYPOSITION) + 1);
GetMenuString(Parent, Item, PChar(Result), Length(Result), MF_BYPOSITION);
end;
var
i: Integer;
ItemInfo: TMenuItemInfo;
begin
for i := 0 to GetMenuItemCount(Menu) - 1 do begin
FillChar(ItemInfo, SizeOf(ItemInfo), 0);
ItemInfo.cbSize := SizeOf(ItemInfo);
ItemInfo.fMask := MIIM_SUBMENU or MIIM_TYPE;
GetMenuItemInfo(Menu, i, True, ItemInfo);
if ItemInfo.fType <> MFT_SEPARATOR then
Strings.Add(StringOfChar('-', Indent * 2) + GetItemString(Menu, i));
if ItemInfo.hSubMenu <> 0 then
RecurseItems(ItemInfo.hSubMenu, Strings, Indent + 1);
end;
end;
//--
var
Cmd: Cardinal;
ContextMenu: IContextMenu;
ContextMenu2: IContextMenu2;
...
begin
Menu := CreatePopupMenu;
if Menu <> 0 then
begin
if Succeeded(ContextMenu.QueryContextMenu(Menu, 0, 1, $7FFF, CMF_EXPLORE)) then
//++
Memo1.Clear;
RecurseItems(Menu, Memo1.Lines);
//--
begin
CallbackWindow := 0;
..
It won't really make any difference if you get items' text after you retrieve an 'IContextMenu2' interface or not, because sub menus like 'Send To' or 'New' are not populated until their parent menu item is selected. There's no way in that routine you'll have access to them. Note below the two items that have failed to expand in the sample output of the above code:
&Open
Run as &administrator
Troubleshoot compatibilit&y
7-Zip
--Open archive
--Extract files...
--Extract Here
--Test archive
--Add to archive...
S&hare with
--
Pin to Tas&kbar
Pin to Start Men&u
Restore previous &versions
Se&nd to
--
Cu&t
&Copy
Create &shortcut
&Delete
P&roperties
Messages to show the sub-items will be passing through your CallbackWindow's WndProc, like WM_INITMENUPOPUP, WM_ENTERIDLE, WM_MEASUREITEM, WM_DRAWITEM. But I don't think trying to extract the information there would make any sense at all..