获取对话框窗体的引用(ShowMessage、MsgDialog等)

5
有没有任何事件可以使用,以便在屏幕上出现 ShowMessage 时捕获该时刻?我还需要传递对已显示消息的 TForm 的引用。
到目前为止,我尝试过 OnDeactivate,但似乎 ShowMessage 不会触发它...
在.NET中,有一个应用程序方法可以捕获每个MessageBox(Application.AddFilterMessage或类似的东西),我需要Delphi中的类似功能。
我想要实现的是: 必须在对话框窗口出现时(或只是模态窗口,但不那么方便)捕获该时刻。然后我需要执行几个指令。这些指令的目标是给我提供刚刚接收到的DialogWindow的引用,以便例如获取其中的按钮数。

2
尽管David的代码可能会实现你想要的功能,但我的第一反应是你可能正在寻找一种解决方案来解决某些问题。根据你需要完成的任务,还可能有更好的方法可供选择。这是一个调试步骤,一个为了解决错误的第三方控制器而采用的解决方案,测试程序还是其他什么? - MikeD
3
也许你可以使用TApplication.OnModalBeginTApplicationEvents.OnModalBegin来解决问题。 - Uli Gerhardt
@UlrichGerhardt 我认为这是最好的答案,适用于(显然)Delphi 2010及更高版本。 - Ondrej Kelle
那个OnModalBegin在第一次测试后看起来很有前途。如果我能让它工作,你应该将其发布为答案:) - Jacek Kwiecień
OnModalBegin并不差,但它无法捕获显示窗口对话框的直接调用,并且它不会让事件处理程序了解被激活的模态窗体的任何信息。 - David Heffernan
是的,该死,我需要获取对话框窗口的实例:/ - Jacek Kwiecień
4个回答

6
在现代Windows版本上,使用现代的Delphi版本时,ShowMessage会显示一个Windows对话框窗口。您可以使用WH_CBT挂钩来捕获该对话框窗口的激活。
function CBTProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
  wnd: HWND;
  ClassName: string;
begin
  if nCode=HCBT_ACTIVATE then
  begin
    wnd := wParam;
    SetLength(ClassName, 256);
    SetLength(ClassName, GetClassName(wnd, PChar(ClassName), Length(ClassName)));
    if (ClassName='#32770') or (ClassName='TMessageForm') then
      Beep;
  end;
  Result := CallNextHookEx(0, nCode, wParam, lParam);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Hook: HHOOK;
begin
  Hook := SetWindowsHookEx(WH_CBT, CBTProc, HInstance, GetCurrentThreadId);
  if Hook=0 then
    RaiseLastOSError;
  try
    ShowMessage('hello');
  finally
    if not UnhookWindowsHookEx(Hook) then
      RaiseLastOSError;
  end;
end;

请注意,实际窗口类名称因系统而异。在XP上,类名称将是TMessageForm,因为对话框实际上是一个Delphi TForm。然而,在Vista及以后的版本中,对话框是一个标准的窗口消息框对话框,其窗口类名为#32770
我已经展示了这个包装在单个ShowMessage调用周围,但如果您想挂钩应用程序中显示的所有消息对话框,可以在启动时安装它。

我确实想钩住我的应用程序中显示的所有消息。但是我还需要获得对话框窗口的实例。我读到不能使用句柄获取它。 - Jacek Kwiecień
2
+1. ShowMessage(对话框)创建了一个窗口类 TMessageForm。我认为你还需要在 CBTProc 中调用 Result := CallNextHookEx - kobik
1
#32770 类是通过 MessageBox(Windows)创建的。 - kobik
@JacekKwiecień 在钩子过程中的 wnd 本地变量是被激活的窗口句柄。 - David Heffernan
@David Heffernan 我知道这可能是一个新手问题。我创建了一个带有一个窗体的测试项目。我想要所有的通信都被挂钩。我应该把CBTProc函数放在哪里? - Jacek Kwiecień
显示剩余10条评论

5

你也可以在主窗体的OnCreate事件中安装一个应用程序范围的钩子(在OnDestroy中卸载):

procedure TMainForm.FormCreate(Sender: TObject);
begin
  ...
  Application.HookMainWindow(ApplicationHook);
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  ...
  Application.UnhookMainWindow(ApplicationHook);
end;

function TMainForm.ApplicationHook(var Message: TMessage): Boolean;
var
  I: Integer;
begin
  Result := False;
  if (Message.Msg = WM_ENABLE) and not TWMEnable(Message).Enabled then // disabling
    for I := 0 to Screen.FormCount - 1 do
      with Screen.Forms[I] do
        if Enabled and (ClassNameIs('TMessageForm') or // ShowMessage, MessageDlg
          ClassNameIs('TForm') or // InputQuery
          ClassNameIs('TMyLoginDialog')) then // your own dialogs, etc.
        begin
          Screen.Forms[I].Position := poScreenCenter; // for example
          Result := True;
          Break;
        end;
end;

我不确定这个代码能否在现代 Delphi 中运行。在现代 Delphi 中,ShowMessage 会弹出一个标准的窗口消息框而不是 TForm 的子类。但如果你想要挂钩一个 TForm,那么这段代码看起来很不错。 - David Heffernan
在Delphi XE2中,@DavidHeffernan提到ShowMessageMessageDlg仍然使用TMessageForm。你可能指的是MessageBox API。这将不包括在Screen.Forms中,所以你需要针对这种情况采用不同的策略。 - Ondrej Kelle
@DavidHeffernan 我正在运行 Windows 7 Ultimate 64 位操作系统。 - Ondrej Kelle
@DavidHeffernan 是的,我正在使用经典主题。感谢您的评论! - Ondrej Kelle
同样感谢您指出我的代码在XP和经典主题上无法工作。我已经修复了这个问题,但我认为在Windows对话框中无法实现这里概述的方法。 - David Heffernan
显示剩余2条评论

2
为什么不直接使用OnActiveFormChange?
procedure TForm3.FormCreate(Sender: TObject);
begin
  Screen.OnActiveFormChange := ScreenActiveFormChange;
end;

procedure TForm3.ScreenActiveFormChange(Sender: TObject);
begin
  if Screen.ActiveForm is TOKRightDlg then
    Screen.ActiveForm.Caption := 'Found';
end;

procedure TForm3.Button1Click(Sender: TObject);
begin
  with TOKRightDlg.Create(nil) do
  try
    ShowModal;
  finally
    Free;
  end;
end;

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接