delphicontextmenujedi-code-library

It is possible to display the windows context menu for multiple files using DisplayContextMenu form JEDI JCL library?


It is possible to display the windows context menu for multiple files using DisplayContextMenu form JEDI JCL library ?

This is the code:

function DisplayContextMenu(const Handle: THandle; const FileName: string;
  Pos: TPoint): Boolean;
var
  ItemIdList: PItemIdList;
  Folder: IShellFolder;
begin
  Result := False;
  ItemIdList := PathToPidlBind(FileName, Folder);
  if ItemIdList <> nil then
  begin
    Result := DisplayContextMenuPidl(Handle, Folder, ItemIdList, Pos);
    PidlFree(ItemIdList);
  end;
end;

function DisplayContextMenuPidl(const Handle: THandle; const Folder: IShellFolder;
  Item: PItemIdList; Pos: TPoint): Boolean;
var
  Cmd: Cardinal;
  ContextMenu: IContextMenu;
  ContextMenu2: IContextMenu2;
  Menu: HMENU;
  CommandInfo: TCMInvokeCommandInfo;
  CallbackWindow: THandle;
begin
  Result := False;
  if (Item = nil) or (Folder = nil) then
    Exit;
  Folder.GetUIObjectOf(Handle, 1, Item, IID_IContextMenu, nil,
    Pointer(ContextMenu));
  if ContextMenu <> nil then
  begin
    Menu := CreatePopupMenu;
    if Menu <> 0 then
    begin
      if Succeeded(ContextMenu.QueryContextMenu(Menu, 0, 1, $7FFF, CMF_EXPLORE)) then
      begin
        CallbackWindow := 0;
        if Succeeded(ContextMenu.QueryInterface(IContextMenu2, ContextMenu2)) then
        begin
          CallbackWindow := CreateMenuCallbackWnd(ContextMenu2);
        end;
        ClientToScreen(Handle, Pos);
        Cmd := Cardinal(TrackPopupMenu(Menu, TPM_LEFTALIGN or TPM_LEFTBUTTON or
          TPM_RIGHTBUTTON or TPM_RETURNCMD, Pos.X, Pos.Y, 0, CallbackWindow, nil));
        if Cmd <> 0 then
        begin
          ResetMemory(CommandInfo, SizeOf(CommandInfo));
          CommandInfo.cbSize := SizeOf(TCMInvokeCommandInfo);
          CommandInfo.hwnd := Handle;
          CommandInfo.lpVerb := MakeIntResourceA(Cmd - 1);
          CommandInfo.nShow := SW_SHOWNORMAL;
          Result := Succeeded(ContextMenu.InvokeCommand(CommandInfo));
        end;
        if CallbackWindow <> 0 then
          DestroyWindow(CallbackWindow);
      end;
      DestroyMenu(Menu);
    end;
  end;
end;

Solution

  • To show the context menu of several items, you must modify the code a little bit.

    First you must allocate an array of PItemIDList and fill each element of the array and finally pass to the GetUIObjectOf method the array with the number of elements.

    Try this sample

    uses
      JclShell,
      ShlObj;
    
    function MenuCallback(Wnd: THandle; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
    var
      ContextMenu2: IContextMenu2;
    begin
      case Msg of
        WM_CREATE:
          begin
            ContextMenu2 := IContextMenu2(PCreateStruct(lParam).lpCreateParams);
            SetWindowLongPtr(Wnd, GWLP_USERDATA, LONG_PTR(ContextMenu2));
            Result := DefWindowProc(Wnd, Msg, wParam, lParam);
          end;
        WM_INITMENUPOPUP:
          begin
            ContextMenu2 := IContextMenu2(GetWindowLongPtr(Wnd, GWLP_USERDATA));
            ContextMenu2.HandleMenuMsg(Msg, wParam, lParam);
            Result := 0;
          end;
        WM_DRAWITEM, WM_MEASUREITEM:
          begin
            ContextMenu2 := IContextMenu2(GetWindowLongPtr(Wnd, GWLP_USERDATA));
            ContextMenu2.HandleMenuMsg(Msg, wParam, lParam);
            Result := 1;
          end;
      else
        Result := DefWindowProc(Wnd, Msg, wParam, lParam);
      end;
    end;
    
    function CreateMenuCallbackWnd(const ContextMenu: IContextMenu2): THandle;
    const
      IcmCallbackWnd = 'ICMCALLBACKWND';
    var
      WndClass: TWndClass;
    begin
      ZeroMemory(@WndClass, SizeOf(WndClass));
      WndClass.lpszClassName := PChar(IcmCallbackWnd);
      WndClass.lpfnWndProc := @MenuCallback;
      WndClass.hInstance := HInstance;
      Winapi.Windows.RegisterClass(WndClass);
      Result := CreateWindow(IcmCallbackWnd, IcmCallbackWnd, WS_POPUPWINDOW, 0,
        0, 0, 0, 0, 0, HInstance, Pointer(ContextMenu));
    end;
    
    type
      PArrayOfPItemIDList = ^TArrayOfPItemIDList;
      TArrayOfPItemIDList = array[0..0] of PItemIDList;
    
    function DisplayContextMenuPidl(const Handle: THandle; const Folder: IShellFolder;
      Item: PArrayOfPItemIDList; ItemsCount :Integer; Pos: TPoint): Boolean;
    var
      Cmd: Cardinal;
      ContextMenu: IContextMenu;
      ContextMenu2: IContextMenu2;
      Menu: HMENU;
      CommandInfo: TCMInvokeCommandInfo;
      CallbackWindow: THandle;
    begin
      Result := False;
      if (Item = nil) or (Folder = nil) then
        Exit;
      //pass the number of elements oif the array (ItemsCount)
      Folder.GetUIObjectOf(Handle, ItemsCount, Item[0], IID_IContextMenu, nil,
        Pointer(ContextMenu));
      if ContextMenu <> nil then
      begin
        Menu := CreatePopupMenu;
        if Menu <> 0 then
        begin
          if Succeeded(ContextMenu.QueryContextMenu(Menu, 0, 1, $7FFF, CMF_EXPLORE)) then
          begin
            CallbackWindow := 0;
            if Succeeded(ContextMenu.QueryInterface(IContextMenu2, ContextMenu2)) then
            begin
              CallbackWindow := CreateMenuCallbackWnd(ContextMenu2);
            end;
            ClientToScreen(Handle, Pos);
            Cmd := Cardinal(TrackPopupMenu(Menu, TPM_LEFTALIGN or TPM_LEFTBUTTON or
              TPM_RIGHTBUTTON or TPM_RETURNCMD, Pos.X, Pos.Y, 0, CallbackWindow, nil));
            if Cmd <> 0 then
            begin
              ZeroMemory(@CommandInfo, SizeOf(CommandInfo));
              CommandInfo.cbSize := SizeOf(TCMInvokeCommandInfo);
              CommandInfo.hwnd := Handle;
              CommandInfo.lpVerb := MakeIntResourceA(Cmd - 1);
              CommandInfo.nShow := SW_SHOWNORMAL;
              Result := Succeeded(ContextMenu.InvokeCommand(CommandInfo));
            end;
            if CallbackWindow <> 0 then
              DestroyWindow(CallbackWindow);
          end;
          DestroyMenu(Menu);
        end;
      end;
    end;
    
    function DisplayContextMenu(const Handle: THandle; const  FileNames: array of string;  Pos: TPoint): Boolean;
    var
      ItemIdList: PArrayOfPItemIDList;
      Folder: IShellFolder;
      ItemsCount : integer;
    
      procedure AllocItems;
      var
        i : integer;
      begin
        for i := 0 to  ItemsCount- 1 do
          ItemIdList[i] := PathToPidlBind(FileNames[i], Folder);
      end;
    
      procedure ReleaseItems;
      var
        i : integer;
      begin
        for i := 0 to  ItemsCount- 1 do
          PidlFree(ItemIdList[i]);
      end;
    
    begin
      Result := False;
      ItemsCount := Length(FileNames);
      if ItemsCount>0 then
      begin
        //Allocate the array
        ItemIdList := AllocMem(SizeOf(PItemIDList) * ItemsCount);
        try
           AllocItems; //fill each item 
          try
            Result := DisplayContextMenuPidl(Handle, Folder, ItemIdList, ItemsCount, Pos);
          finally
            ReleaseItems; //release the items 
          end;
        finally
           FreeMem(ItemIdList); //release the array
        end;
      end;
    end;
    

    And use like so

     DisplayContextMenu(Handle, ['C:\Foo\Bar.txt', 'C:\Foo\Bar2.txt'], Point(0, 0));