【问题标题】:Get WM_LBUTTONDOWN message while ShellExecAndWait?在 ShellExecAndWait 时获取 WM_LBUTTONDOWN 消息?
【发布时间】:2017-11-16 16:14:17
【问题描述】:

在 Delphi IDE 中,创建一个 VCL Forms Application。然后在表单上添加一个TApplicationEvents 组件和一个TButton。然后添加这两个事件处理程序:

uses
  JclShell;

procedure TForm3.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
begin
  if Msg.Message = WM_LBUTTONDOWN then
  begin
    Self.Caption := 'WM_LBUTTONDOWN';
  end
  else if Msg.Message = WM_LBUTTONUP then
  begin
    Self.Caption := 'WM_LBUTTONUP';
  end
end;

procedure TForm3.Button1Click(Sender: TObject);
begin
  JclShell.ShellExecAndWait('C:\Windows\system32\notepad.exe');
  Self.Caption := 'Notepad closed';
end;

现在点击按钮。会发生以下情况:

  1. “WM_LBUTTONDOWN”出现在表单的标题栏上。

  2. “WM_LBUTTONUP”出现在表单的标题栏上。

  3. 记事本被执行。

然后再次单击启动另一个记事本实例的按钮,但这次没有在表单的标题栏上写任何内容。

显然,程序卡在JclShell.ShellExecAndWait 中,只有在记事本关闭时才会返回。因此,当记事本关闭时,再次单击鼠标会写入表单的标题栏。

所以我们可以看到,当记事本在JclShell.ShellExecAndWait 中运行时,程序中的一切都正常工作:您甚至可以在记事本在JclShell.ShellExecAndWait 中运行时进行数学计算。只有 ApplicationEvents1Message 在记事本运行时不会被触发。

那么当记事本在JclShell.ShellExecAndWait 中运行时,我怎样才能得到WM_LBUTTONDOWN 消息?

【问题讨论】:

  • 您需要知道记事本何时关闭吗?
  • 当然,我需要知道。

标签: delphi delphi-10.1-berlin jedi


【解决方案1】:

JclShell.ShellExecAndWait 等待衍生进程退出时,它使用以下基本消息泵:

while WaitForSingleObject(Sei.hProcess, 10) = WAIT_TIMEOUT do
  repeat
    Msg.hwnd := 0;
    Res := PeekMessage(Msg, Sei.Wnd, 0, 0, PM_REMOVE);
    if Res then
    begin
      TranslateMessage(Msg);
      DispatchMessage(Msg);
    end;
  until not Res;
CloseHandle(Sei.hProcess);

我说基本,因为 VCL 消息泵的循环部分如下所示:

 if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then
  begin
    Unicode := (Msg.hwnd = 0) or IsWindowUnicode(Msg.hwnd);
    if Unicode then
      MsgExists := PeekMessageW(Msg, 0, 0, 0, PM_REMOVE)
    else
      MsgExists := PeekMessageA(Msg, 0, 0, 0, PM_REMOVE);

    if MsgExists then
    begin
      Result := True;
      if Msg.Message <> WM_QUIT then
      begin
        Handled := False;
        if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
        if not IsPreProcessMessage(Msg) and not IsHintMsg(Msg) and
          not Handled and not IsMDIMsg(Msg) and
          not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
        begin
          TranslateMessage(Msg);
          if Unicode then
            DispatchMessageW(Msg)
          else
            DispatchMessageA(Msg);
        end;
      end
      else
      begin
  {$IF DEFINED(CLR)}
        if Assigned(FOnShutDown) then FOnShutDown(self);
        DoneApplication;
  {$IFEND}
        FTerminate := True;
      end;
    end;

代码行

if Assigned(FOnMessage) then FOnMessage(Msg, Handled);

是将消息通过TApplicationEvents 分配给Application.OnMessageTMultiCaster 传递给您的TApplicationEvents.OnMessage。这不是在 JCL 源代码中完成的。 其他问题可能是 JCL 目前不了解有关消息的 UniCode 并且没有处理 WM_QUIT

如何处理这也取决于您想要实现的目标。您为什么要首先收到此消息?

我的意思是可以更改 JCL 源 - 如果您愿意这样做 - 并将 VCL.Forms 添加到使用中,然后调用事件处理程序(如果像 VCL 那样分配):

while WaitForSingleObject(Sei.hProcess, 10) = WAIT_TIMEOUT do
  repeat
    Msg.hwnd := 0;
    Res := PeekMessage(Msg, Sei.Wnd, 0, 0, PM_REMOVE);
    if Res then
    begin
      Handled := False;
      if Assigned(Application.OnMessage) then
        Application.OnMessage(Msg, Handled);
      if not Handled then
      begin
        TranslateMessage(Msg);
        DispatchMessage(Msg);
      end;
    end;
  until not Res;
CloseHandle(Sei.hProcess);

或者甚至调用Application.ProcessMessages,以进行与 VCL 相同的消息处理:

while WaitForSingleObject(Sei.hProcess, 10) = WAIT_TIMEOUT do
  Application.ProcessMessages;
CloseHandle(Sei.hProcess);

这是有效的,我看不到评论者建议的任何副作用。但在以这种方式更改 JCL 源之前,我可能会实现自己的ShellExecAndWait。根据您想要实现的目标,消息的正常发送应该仍然有效。因此,如果您的 TFrom 具有例如

procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;

已实现,应该被调用。但是,仅当消息针对您的表单时才会出现这种情况。如果单击按钮,则生成的消息将被定向到按钮本身。然后你需要实现你自己的后代类。

也许是完全不同的建议?

如果您的目标是让某些控件在 JclShell.ShellExecAndWait 等待时不被单击,并且使用消息处理是您实现这一目标的方法,那么您可能可以尝试其他方法。

为什么不禁用它们?这也会给用户一个可见的指示,表明点击是不行的。

  Button1.Enabled := False;
  try
    JclShell.ShellExecAndWait('C:\Windows\system32\notepad.exe');
    Self.Caption := 'Notepad closed';
  finally
    Button1.Enabled := True;
  end;

如果您担心创建TAction 的多个控件,请将其分配给您要禁用的每个控件,并与TActionEnabled 属性一起禁用所有这些控件。

或者,在打开 notepad.exe 时隐藏您的表单

  Hide;
  try
    JclShell.ShellExecAndWait('C:\Windows\system32\notepad.exe');
    Self.Caption := 'Notepad closed';
  finally
    Show;
  end;

或者您可以删除事件处理程序

var
  ATmpOnClick: TNotifyEvent;
begin
  ATmpOnClick := Button1.OnClick;
  Button1.OnClick := nil;
  try
    JclShell.ShellExecAndWait('C:\Windows\system32\notepad.exe');
    Self.Caption := 'Notepad closed';
  finally
    Button1.OnClick := ATmpOnClick;
  end;
end;

【讨论】:

  • 请注意,这可能仍然不起作用,因为ShellExecAndWait() 循环正在按 HWND 过滤消息,因此PeekMessage() 不会为其他 HWND 传递消息,例如按钮。更好的解决方案是将ShellExecuteAndWait() 调用移动到单独的工作线程,该工作线程会在记事本关闭时通知主 UI 线程
  • 如果你要在 JCL 源中添加“表单”,我认为你可以直接调用 Application.ProcessMessage 而不删除消息,并且将执行应用程序的所有处理。
  • @user1580348:如果你要调用Application.ProcessMessages(),那么删除整个PeekMessage()循环,因为ProcessMessages()会为你调用PeekMessage() (@987654350 @ 将永远是 False),例如:while WaitForSingleObject(Sei.hProcess, 10) = WAIT_TIMEOUT do Application.ProcessMessages; 这就是你所需要的。
  • @user1580348:但是,您应该改用MsgWaitForMultipleObjects()。只有在实际有消息等待处理时才调用ProcessMessages(),例如:var Res: DWORD; ... repeat Res := MsgWaitForMultipleObjects(1, Sei.hProcess, False, INFINITE, QS_ALLINPUT); if Res = WAIT_OBJECT_0+1 Then Application.ProcessMessages; until Res = WAIT_OBJECT_0;
  • 你还需要等待吗?这里有很多工作来解决等待手柄的问题,当我们不知道为什么我们在等待notepad.exe
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-22
  • 2018-06-05
  • 2013-04-23
  • 1970-01-01
  • 1970-01-01
  • 2018-11-29
  • 1970-01-01
相关资源
最近更新 更多