Как получить доступ к элементам контекстного меню оболочки Windows?

в Проводнике Windows щелкните правой кнопкой мыши файл, появится контекстное меню, которое содержит встроенные элементы, такие как "отправить"..."и / или сторонние действия, такие как "zip-файл с Winzip". Мой вопрос:

  • как получить полный список доступных пунктов меню для конкретного файла?
  • для каждого пункта меню, Как получить подпись?
  • как вызвать определенное действие пункта меню для определенного файла диска?

спасибо вперед!

[EDIT]: в то время как другая информация абсолютно полезна, решение Delphi будет высоко оценено!

3 ответов


ключ для получения контекстного меню оболочки используется IContextMenu интерфейс.

проверьте эту замечательную статью Shell context menu support для получения более подробной информации.

обновление

для примеров delphi вы можете увидеть JclShell блок от Jedi JCL (проверьте DisplayContextMenu функция) и блок ShellCtrls, включенный в папку samples Delphi.


короткий ответ:

попробовать Компоненты ShellBrowser из программного обеспечения JAM. У них есть компонент, который позволит вам показать контекстное меню Проводника с вашими собственными командами, смешанными из TPopupMenu.


ответ

получение меню Проводника, запрос всех его свойств и размещение их в вашем собственном меню возможно, но вам действительно должно быть удобно читать / писать низкоуровневый код Win32 и рабочее знание C поможет. Вам также нужно будет следить за некоторыми gotchas (см. ниже). Я настоятельно рекомендую читать как разместить IContextMenu серия для много технических деталей.

на легче подход для запроса интерфейс IContextMenu, то с ним hmenu, затем с помощью TrackPopupMenu позволить иметь Windows показать меню, затем вызвать InvokeCommand в конце.

некоторые из кода ниже не проверено или было изменено из того, что мы используем, поэтому действуйте на свой страх и риск.

вот как вы получить IContextMenu, для группы файлов в базовой папки:

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;

чтобы получить актуальные пункты меню, вам нужно позвонить IContextMenu.QueryContextMenu. Вы можете уничтожить возвращенный HMENU, используя DestroyMenu.

function GetExplorerHMenu(const AContextMenu: IContextMenu): HMENU;
const
  MENUID_FIRST = 1;
  MENUID_LAST = FFF;
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;

вот как вы на самом деле вызываете команду, выбранную пользователем из меню:

procedure InvokeCommand(const AContextMenu: IContextMenu; AVerb: PChar);
const
  CMIC_MASK_SHIFT_DOWN   = 000000;
  CMIC_MASK_CONTROL_DOWN = 000000;
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;

теперь вы можете использовать GetMenuItemInfo функция для получения заголовка, растрового изображения и т. д., Но гораздо проще вызвать TrackPopupMenu и пусть окна показывают всплывающее меню. Это будет выглядеть примерно так:

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;

если вы действительно хотите извлечь пункты меню/подписи и добавить их в свое собственное всплывающее меню (мы используем Toolbar 2000 и делаем именно это), вот другие большие проблемы, которые вы будете запускать в:

  • меню "Отправить" и любые другие, которые построены по требованию, не будут работать, если вы не обрабатываете сообщения и не передаете их интерфейсам IContextMenu2/IContextMenu3.
  • растровые изображения меню находятся в нескольких различных форматах. Delphi не обрабатывает Vista с высоким цветом без уговоров, а старые смешиваются с цветом фона с помощью XOR.
  • некоторые пункты меню нарисованы владельцем, поэтому вам нужно захватить сообщения краски и иметь их нарисуйте свой собственный холст.
  • строки подсказок не будут работать, если вы вручную не запросите их.
  • вам нужно будет управлять временем жизни IContextMenu и HMENU и выпускать их только после закрытия всплывающего меню.

вот пример того, как логика операционной системы за "отправить ... Пункт контекстного меню" / mail recipient " можно использовать из приложения Delphi для открытия почтового клиента по умолчанию, отображая новую почту с переданными (выбранными) файлами:

как я могу имитировать "отправить..." с Delphi?