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.
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.
In Microsoft Windows, pressing the Application key or Shift+F10 opens a context menu for the region that has focus.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With