windows - 等待 IContextMenu.InvokeCommand 启动的进程

标签 windows delphi winapi delphi-10.2-tokyo

我有一个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 事件在用户关闭文件后写回对文件所做的任何更改。

我现在借助 JclShell.DisplayContextMenu()(使用 IContextMenu)显示 Windows 上下文菜单。

我的目标:WAITING上下文菜单中选择的已执行操作(例如“属性”、“删除”...)完成(或以任何方式收到通知),以便我可以检查临时文件的更改以将这些更改写回,或者在删除的情况下删除 TListItem

因为 CMINVOKECOMMANDINFO 不像 SHELLEXECUTEINFO 那样返回进程句柄,所以我无法以相同的方式执行此操作。

MakeIntResource(commandId-1) 分配给 SHELLEXECUTEINFO.lpVerb 使得对 ShellExecuteEx() 的调用因 EAccessViolation< 而崩溃SHELLEXECUTEINFO 似乎不支持此方法。

我尝试使用 IContextMenu.GetCommandString() 获取命令字符串,并从 TrackPopupMenu() 获取命令 ID,稍后将其传递给 SHELLEXECUTEINFO.lpVerb ,但是 GetCommandString() 不会为某些被点击的项目返回命令。

工作菜单项:

properties, edit, copy, cut, print, 7z: add to archive (verb is 'SevenZipCompress', wont return processHandle), KapserskyScan (verb is 'KL_scan', wont return processHandle)

不工作:

anything within "open with" or "send to"

这仅仅是 IContextMenu 实现的错误吗?

也许这与我使用AnsiString有关?不过,我无法让 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;

我已经在 How to host an IContextMenu 上阅读了 Raymond Chen 的博客。 ,以及对 MSDN 的研究(例如 CMINVOKECOMMANDINFOGetCommandString()SHELLEXECUTEINFOTrackPopupMenu() ),但我可能遗漏了一些微不足道的东西。

最佳答案

我最终使用了 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;

关于windows - 等待 IContextMenu.InvokeCommand 启动的进程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54674447/

相关文章:

c++ - _GetExceptDLLinfo 是什么?

c++ - Ofstream 在 Windows 临时目录中创建一个文件

Delphi - 使用接口(interface)内的接口(interface)时,我出现内存泄漏,但不知道为什么

delphi - 编译Delphi 2010项目时,什么可能导致“"Required Package ' IndyCore'未找到”?

c++ - C++ 标准库在任何时候都包含每个平台的 native 头文件吗?

windows - 是否有免费/开源的带有图形进度的类似 wget 的 Windows 程序?

windows - 安装 CPAN 模块时出现问题

macos - 无法在带有 XE2 Update 4 的 OS X 10.7.3 上调试 FireMonkey 应用程序

c++ - 使用 CreateProcess 函数创建 "dir"命令失败,错误代码为 2

windows - MFC中如何在CTreeCtrl列表中添加图片