Loop through IContextMenu

前端 未结 2 1967
执念已碎
执念已碎 2021-01-14 10:37

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 D         


        
相关标签:
2条回答
  • 2021-01-14 11:27

    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;
    
    0 讨论(0)
  • 2021-01-14 11:31

    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..

    0 讨论(0)
提交回复
热议问题