views:

304

answers:

3

Hi,

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.

+2  A: 

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.

Deltics
Looks like there may be a way to do it without replicating all that shell code after all. I found this but it's a .NET example. http://www.andrewvos.com/?p=420
Deltics
A: 

Although I agree with Deltics that it is a lot of work, the information required for most (if not all) of the items is freely available in the registry. The guide listed in Deltics answer look good and will give you most of the items. A lot can be looked up form basic entries in the registry whereas others need calls to COM objects.

Toby Allen
+8  A: 

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

... ShlObj, ActiveX, ComObj, shellapi

add this declration to the "Type" section:

  IShellCommandVerb = interface
    ['{7D2A7245-2376-4D33-8008-A130935A2E8B}']
    procedure ExecuteCommand(Verb: string; var Handled: boolean);
    procedure CommandCompleted(Verb: string; Succeeded: boolean);
  end;

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;
  ZVerb: array[0..255] of AnsiChar;
  Verb: string;
  Handled: boolean;
  SCV: IShellCommandVerb;
  HR: HResult;
  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;
      HR := CM.GetCommandString(ICmd, GCS_VERBA, nil, ZVerb, SizeOf(ZVerb));
      Verb := StrPas(ZVerb);
      Handled := False;
      if Supports(nil, IShellCommandVerb, SCV) then
      begin
        HR := 0;
        SCV.ExecuteCommand(Verb, Handled);
      end;

      if not Handled then
      begin
        FillChar(ICI, SizeOf(ICI), #0);
        with ICI do
        begin
          cbSize := SizeOf(ICI);
          hWND := 0;
          lpVerb := MakeIntResourceA(ICmd);
          nShow := SW_SHOWNORMAL;
        end;
        HR := CM.InvokeCommand(ICI);
      end;

      if Assigned(SCV) then
        SCV.CommandCompleted(Verb, HR = S_OK);
    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,

Issam Ali