Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pop-up the Windows context menu for a given file using Delphi?

I want to write the following procedure / function:

procedure ShowSysPopup(aFile: string; x, y: integer);

Which will build and show (at the coordinates x and y) the right-click shell menu which one sees in the Windows Explorer for the given file. I'm not so interested in the 'showing' part but more in how one can build such a menu.

like image 400
John Thomas Avatar asked Oct 17 '09 11:10

John Thomas


People also ask

How do I show the Windows context menu?

To open the context menu, right-click (or press and hold) on a file or folder in File Explorer. To find commands from the previous Windows 10 context menu: Right-click (or press and hold) on a file or folder to open the context menu. Select Show more options.

Where do I find context menu?

In Microsoft Windows, pressing the Application key or Shift+F10 opens a context menu for the region that has focus.


2 Answers

I've made a quick solution for you. add these units to the "Uses" section:

... ShlObj, ActiveX, ComObj

and here is your procedure, I just add new parameter "HND" to carry the handle of the TWinControl that you will use to display the context Menu.

procedure ShowSysPopup(aFile: string; x, y: integer; HND: HWND);
var
  Root: IShellFolder;
  ShellParentFolder: IShellFolder;
  chEaten,dwAttributes: ULONG;
  FilePIDL,ParentFolderPIDL: PItemIDList;
  CM: IContextMenu;
  Menu: HMenu;
  Command: LongBool;
  ICM2: IContextMenu2;

  ICI: TCMInvokeCommandInfo;
  ICmd: integer;
  P: TPoint;
Begin
  OleCheck(SHGetDesktopFolder(Root));//Get the Desktop IShellFolder interface

  OleCheck(Root.ParseDisplayName(HND, nil,
    PWideChar(WideString(ExtractFilePath(aFile))),
    chEaten, ParentFolderPIDL, dwAttributes)); // Get the PItemIDList of the parent folder

  OleCheck(Root.BindToObject(ParentFolderPIDL, nil, IShellFolder,
  ShellParentFolder)); // Get the IShellFolder Interface  of the Parent Folder

  OleCheck(ShellParentFolder.ParseDisplayName(HND, nil,
    PWideChar(WideString(ExtractFileName(aFile))),
    chEaten, FilePIDL, dwAttributes)); // Get the relative  PItemIDList of the File

  ShellParentFolder.GetUIObjectOf(HND, 1, FilePIDL, IID_IContextMenu, nil, CM); // get the IContextMenu Interace for the file

  if CM = nil then Exit;
  P.X := X;
  P.Y := Y;

  Windows.ClientToScreen(HND, P);

  Menu := CreatePopupMenu;

  try
    CM.QueryContextMenu(Menu, 0, 1, $7FFF, CMF_EXPLORE or CMF_CANRENAME);
    CM.QueryInterface(IID_IContextMenu2, ICM2); //To handle submenus.
    try
      Command := TrackPopupMenu(Menu, TPM_LEFTALIGN or TPM_LEFTBUTTON or TPM_RIGHTBUTTON or
        TPM_RETURNCMD, p.X, p.Y, 0, HND, nil);
    finally
      ICM2 := nil;
    end;

    if Command then
    begin
      ICmd := LongInt(Command) - 1;
      FillChar(ICI, SizeOf(ICI), #0);
      with ICI do
      begin
        cbSize := SizeOf(ICI);
        hWND := 0;
        lpVerb := MakeIntResourceA(ICmd);
        nShow := SW_SHOWNORMAL;
      end;
      CM.InvokeCommand(ICI);
    end;
  finally
     DestroyMenu(Menu)
  end;
End;

modify/add the initialization, finalization section like this

initialization
  OleInitialize(nil);
finalization
  OleUninitialize;

and here how you can use this procedure:

procedure TForm2.Button1Click(Sender: TObject);
begin
  ShowSysPopup(Edit1.Text,Edit1.Left,Edit1.Top, Handle);
end;

I hope this will work for you.

Regards,

Edit: if you want to show context menu for more than one file check this article in my blog

like image 52
Issam Ali Avatar answered Oct 03 '22 04:10

Issam Ali


Are you sure that's what you want to do? Because if you do, you are effectively going to have to reproduce all of the code in the Windows Shell and all of it's behaviour and interactions with a whole host of code.

The context menu is basically constructed by "shell extensions". These are COM DLL's registered with the system. When the context menu is invoked, the shell follows a set of rules that determine where it should look (in the registry) for extension DLL's.

I found this to be a useful guide to these rules.

But finding the extension DLL's is not even half the story. For each DLL the shell then instantiates the COM object(s) registered by that DLL and makes calls to those objects which the DLL's respond to by either configuring or invoking menu commands.

The shell itself does not build the menu, nor is the information required to build the menu available to be queried or read directly from anywhere - the menu is constructed entirely dynamically by the shell extensions.

The shell passes a handle to the menu to each extension, along with some information telling the extension what command ID's it should use for any items it adds to that menu. The extension can add pretty much whatever it likes to the menu handle it is given, including sub-menus etc, and it may well add different items depending on properties of the current file select, not just file extensions (e.g. the Tortoise SVN client adds different menu items according to what is relevant to the current SVN status of those files).

So if you want to build such a menu yourself, as I say, you will have to replicate the entire shell extension framework (or at least those parts of it that initialize the menu's, assuming for some reason you don't then want or need to invoke the menu commands themselves) in your own code.

Perhaps it might help if you explain why you wish to do this and what you are trying to achieve. There might be an easier way to go about it.

like image 23
Deltics Avatar answered Oct 03 '22 05:10

Deltics