How to access Windows shell context menu items?

前端 未结 3 1693
半阙折子戏
半阙折子戏 2020-12-16 01:35

In Windows Explorer, you right click on a file, a context menu shows up which contains built-in items like \'Send to...\' and/or 3rd party actions such as \'zip file with Wi

相关标签:
3条回答
  • 2020-12-16 02:08

    Here is an exmple how the operating system logic behind the "Send To ... | Mail recipient" context menu item can be used from a Delphi application to open the default mail client, displaying a new mail with the passed (selected) files attached:

    How can I simulate ‘Send To…’ with Delphi?

    0 讨论(0)
  • 2020-12-16 02:25

    Short answer

    Try the ShellBrowser Components from JAM Software. They have a component that will let you show Explorer's context menu with your own commands mixed in from a TPopupMenu.


    Long answer

    Getting the Explorer menu, querying all of its properties, and hosting them in your own menu is possible, but you really should be comfortable reading/writing low-level Win32 code and a working knowledge of C will help. You'll also need to watch out for some gotchas (covered below). I strongly recommend reading Raymond Chen's How to host an IContextMenu series for a lot of the technical details.

    The easier approach is to query for the IContextMenu interface, then the HMENU, then use TrackPopupMenu to let have Windows show the menu, then call InvokeCommand at the end.

    Some of the code below is untested or has been modified from what we're using, so proceed at your own risk.

    Here's how you get the IContextMenu, for a group of files within a base folder:

    function GetExplorerMenu(AHandle: HWND; const APath: string;
      AFilenames: TStrings): IContextMenu;
    var
      Desktop, Parent: IShellFolder;
      FolderPidl: PItemIDList;
      FilePidls: array of PItemIDList;
      PathW: WideString;
      i: Integer;
    begin
      // Retrieve the Desktop's IShellFolder interface
      OleCheck(SHGetDesktopFolder(Desktop));
      // Retrieve the parent folder's PItemIDList and then it's IShellFolder interface
      PathW := WideString(IncludeTrailingPathDelimiter(APath));
      OleCheck(Desktop.ParseDisplayName(AHandle, nil, PWideChar(PathW),
        Cardinal(nil^), FolderPidl, Cardinal(nil^)));
      try
        OleCheck(Desktop.BindToObject(FolderPidl, nil, IID_IShellFolder, Parent));
      finally
        SHFree(FolderPidl);
      end;
      // Retrieve PIDLs for each file, relative the the parent folder
      SetLength(FilePidls, AFilenames.Count);
      try
        FillChar(FilePidls[0], SizeOf(PItemIDList) * AFilenames.Count, 0);
        for i := 0 to AFilenames.Count-1 do begin
          PathW := WideString(AFilenames[i]);
          OleCheck(Parent.ParseDisplayName(AHandle, nil, PWideChar(PathW),
            Cardinal(nil^), FilePidls[i], Cardinal(nil^)));
        end;
        // Get the context menu for the files from the parent's IShellFolder
        OleCheck(Parent.GetUIObjectOf(AHandle, AFilenames.Count, FilePidls[0],
          IID_IContextMenu, nil, Result));
      finally
        for i := 0 to Length(FilePidls) - 1 do
          SHFree(FilePidls[i]);
      end;
    end;
    

    To get the actual menu items you need to call IContextMenu.QueryContextMenu. You can destroy the returned HMENU using DestroyMenu.

    function GetExplorerHMenu(const AContextMenu: IContextMenu): HMENU;
    const
      MENUID_FIRST = 1;
      MENUID_LAST = $7FFF;
    var
      OldMode: UINT;
    begin
      OldMode := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
      try
        Result := CreatePopupMenu;
        AContextMenu.QueryContextMenu(Result, 0, MENUID_FIRST, MENUID_LAST, CMF_NORMAL);
      finally
        SetErrorMode(OldMode);
      end;
    end;
    

    Here's how you actually call the command that the user has selected from the menu:

    procedure InvokeCommand(const AContextMenu: IContextMenu; AVerb: PChar);
    const
      CMIC_MASK_SHIFT_DOWN   = $10000000;
      CMIC_MASK_CONTROL_DOWN = $20000000;
    var
      CI: TCMInvokeCommandInfoEx;
    begin
      FillChar(CI, SizeOf(TCMInvokeCommandInfoEx), 0);
      CI.cbSize := SizeOf(TCMInvokeCommandInfo);
      CI.hwnd := GetOwnerHandle(Owner);
      CI.lpVerb := AVerb;
      CI.nShow := SW_SHOWNORMAL;
      // Ignore return value for InvokeCommand.  Some shell extensions return errors
      // from it even if the command worked.
      try
        AContextMenu.InvokeCommand(PCMInvokeCommandInfo(@CI)^)
      except on E: Exception do
        MessageDlg(Owner, E.Message, mtError, [mbOk], 0);
      end;
    end;
    
    procedure InvokeCommand(const AContextMenu: IContextMenu; ACommandID: UINT);
    begin
      InvokeCommand(AContextMenu, MakeIntResource(Word(ACommandID)));
    end;
    

    Now you can use the GetMenuItemInfo function to get the caption, bitmap, etc, but a much easier approach is to call TrackPopupMenu and let Windows show the popup menu. That would look something like this:

    procedure ShowExplorerMenu(AForm: TForm; AMousePos: TPoint; 
      const APath: string; AFilenames: TStrings; );
    var
      ShellMenu: IContextMenu;
      Menu: HMENU;
      MenuID: LongInt;
    begin
      ShellMenu := GetExplorerMenu(AForm.Handle, APath, AFilenames);
      Menu := GetExplorerHMenu(ShellMenu);
      try
        MenuID := TrackPopupMenu(Menu, TPM_LEFTALIGN or TPM_TOPALIGN or TPM_RETURNCMD, 
          AMousePos.X, AMousePos.Y, 0, AForm.Handle, nil);
        InvokeCommand(ShellMenu, MenuID - MENUID_FIRST);
      finally
        DestroyMenu(Menu);
      end;
    end;
    

    If you actually want to extract the menu items/captions and add them to your own popup menu (we use Toolbar 2000 and do exactly that), here are the other big issues you'll run into:

    • The "Send To" menu, and any others that are built on-demand won't work unless you handle messages and pass them to the IContextMenu2/IContextMenu3 interfaces.
    • Menu bitmaps are in a couple of different formats. Delphi doesn't handle Vista high-color ones without coaxing, and the older ones are blended onto the background color using an XOR.
    • Some menu items are owner-drawn, so you have to capture paint messages and have them paint to your own canvas.
    • Hint strings won't work unless you manually query for them.
    • You'll need to manage the lifetime of the IContextMenu and HMENU and only release them once the popup menu has been closed.
    0 讨论(0)
  • 2020-12-16 02:27

    The key to obtain the Shell Context menu is use the IContextMenu interface.

    check this great article Shell context menu support for more details.

    UPDATE

    for delphi examples you can see the JclShell unit from the JEDI JCL (check the DisplayContextMenu function) and the ShellCtrls unit included in the samples folder of Delphi.

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