Подождите, пока процесс запущен IContextMenu.InvokeCommand - PullRequest
0 голосов
/ 13 февраля 2019

У меня есть TListView, элементы которого являются файлами, которые пользователь может открыть, дважды щелкнув по ним.

Чтобы сделать это, я сохраняю файл во временной папке Windows, запускаю поток, который открывает сохраненный файл с помощью ShellExecuteEx(), и позволяет ему ждать ShellExecuteInfo.hProcess, например:

TNotifyThread = class(TThread)
private
  FFileName: string;
  FFileAge: TDateTime;
public
  constructor Create(const FileName: string; OnClosed: TNotifyEvent); overload;
  procedure Execute; override;

  property FileName: String read FFileName;
  property FileAge: TDateTime read FFileAge;
end;

{...}

constructor TNotifyThread.Create(const FileName: string; OnClosed: TNotifyEvent);
begin
  inherited Create(True);
  if FileExists(FileName) then
    FileAge(FileName, FFileAge);

  FreeOnTerminate := True;
  OnTerminate := OnClosed;
  FFileName := FileName;

  Resume;
end;

procedure TNotifyThread.Execute;
var
  se: SHELLEXECUTEINFO;
  ok: boolean;
begin
  with se do
  begin
    cbSize := SizeOf(SHELLEXECUTEINFO);
    fMask := SEE_MASK_INVOKEIDLIST or SEE_MASK_NOCLOSEPROCESS or SEE_MASK_NOASYNC;
    lpVerb := PChar('open');
    lpFile := PChar(FFileName);
    lpParameters := nil;
    lpDirectory := PChar(ExtractFilePath(ParamStr(0)));
    nShow := SW_SHOW;
  end;

  if ShellExecuteEx(@se) then
  begin
    WaitForSingleObject(se.hProcess, INFINITE);
    if se.hProcess <> 0 then
      CloseHandle(se.hProcess);
  end;
end;

Таким образом, я могу использовать событие TThread.OnTerminate для записи любых изменений, внесенных в файл после его закрытия пользователем.

Теперь я показываю контекстное меню Windows с помощью JclShell.DisplayContextMenu()(который использует IContextMenu).

МОЯ ЦЕЛЬ: дождаться завершения выполненного действия (например, «Свойства», «Удалить», ..), выбранного в контекстном меню (или получить уведомление)любым способом), чтобы я мог проверить временный файл на наличие изменений, чтобы записать их обратно, или удалить TListItem в случае удаления.

, поскольку CMINVOKECOMMANDINFO не возвращаетдескриптор процесса, как SHELLEXECUTEINFO делает, я не могу сделать это таким же образом.

Назначение MakeIntResource(commandId-1) на SHELLEXECUTEINFO.lpVerb сделало вызов ShellExecuteEx() сбой с EAccessViolation.Этот метод кажется неподдерживаемым для SHELLEXECUTEINFO.

Я пытался получить командную строку с IContextMenu.GetCommandString() и идентификатор команды из TrackPopupMenu(), чтобы позже передать ее в SHELLEXECUTEINFO.lpVerb, но GetCommandString() не будетt возвращать команды для некоторых элементов, по которым щелкнули.

рабочие элементы меню:

свойства, редактировать, копировать, вырезать, печатать, 7z: добавить в архив (глагол«SevenZipCompress», не возвращает processHandle), KapserskyScan (глагол «KL_scan», не возвращает processHandle)

не работает:

что-либов «open with» или «send to»

Это просто ошибка реализации IContextMenu?

Может быть, это как-то связано с моим использованием AnsiStrings?Я не мог заставить GCS_VERBW работать, хотя.Есть ли лучшие способы надежного получения CommandString, чем этот?

function CustomDisplayContextMenuPidlWithoutExecute(const Handle: THandle; 
const Folder: IShellFolder;
  Item: PItemIdList; Pos: TPoint): String;
var
  ContextMenu: IContextMenu;
  ContextMenu2: IContextMenu2;
  Menu: HMENU;
  CallbackWindow: THandle;
  LResult: AnsiString;
  Cmd: Cardinal;
begin
  Result := '';
  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
          SetLength(LResult, MAX_PATH);
          cmd := ContextMenu.GetCommandString(Cmd-1, GCS_VERBA, nil, LPSTR(LResult), MAX_PATH);
          Result := String(LResult);
        end;
        if CallbackWindow <> 0 then
          DestroyWindow(CallbackWindow);
      end;
      DestroyMenu(Menu);
    end;
  end;
end;

Я читал блог Раймонда Чена на Как разместить IContextMenu , а также исследовал на MSDN (дляпример CMINVOKECOMMANDINFO, GetCommandString(), SHELLEXECUTEINFO и TrackPopupMenu()), но я мог пропустить что-то тривиальное.

1 Ответ

0 голосов
/ 06 марта 2019

В итоге я использовал TJvChangeNotify для мониторинга временной папки Windows, сохраняя отслеживаемые файлы в TDictionary<FileName:String, LastWrite: TDateTime>.

Так что всякий раз, когда TJvChangeNotify запускает OnChangeNotify событие, я могу проверить, какие из моих отслеживаемых файлов были удалены (путем проверки существования) или изменены (путем сравнения времени последней записи).

Пример ChangeNotifyEvent:

procedure TFileChangeMonitor.ChangeNotifyEvent(Sender: TObject; Dir: string;
  Actions: TJvChangeActions);
var
  LFile: TPair<String, TDateTime>;
  LSearchRec: TSearchRec;
  LFoundErrorCode: Integer;
begin
  for LFile in FMonitoredFiles do
  begin
    LFoundErrorCode := FindFirst(LFile.Key, faAnyFile, LSearchRec);
    try
      if LFoundErrorCode = NOERROR then
      begin
        if LSearchRec.TimeStamp > LFile.Value then
        begin
          // do something with the changed file
          {...}

          // update last write time
          FMonitoredFiles.AddOrSetValue(LFile.Key, LSearchRec.TimeStamp);
        end;
      end // 
      else if (LFoundErrorCode = ERROR_FILE_NOT_FOUND) then
      begin
        // do something with the deleted file
        {...}

        // stop monitoring the deleted file
        FMonitoredFiles.Remove(LFile.Key);
      end;
    finally
      System.SysUtils.FindClose(LSearchRec);
    end;
  end;
end;
...