【问题标题】:Delphi: Detect when a new form has been createdDelphi:检测何时创建了新表单
【发布时间】:2012-07-20 08:32:57
【问题描述】:

我想检测何时创建了新表单。

现在我使用Screen.ActiveFormChange 事件并检查Screen.CustomForms 中的新表单,但ActiveFormChange 在表单的OnShow 事件之后被触发。

我想在OnShow 被解雇之前检测到表单。有没有办法在不修改Vcl.Forms 单元的情况下做到这一点?

我想检测所有表单(还有 Delphi 模态消息等),因此无法从自定义类继承所有表单(如果我错了,请纠正我)。

或者,是否可以检测到某个TComponent.FComponents 列表中添加了新组件?

【问题讨论】:

  • 通常您可以控制表单的创建,为什么“需要”检测?
  • 并非如此。如果调用 ShowMessage,对话窗口会在 Dialogs 单元内的某处创建。
  • 您的代码正在调用 showmessage,对吗?所以你有控制权......你可以为此创建一个包装函数。 (DetectShowMessage 或类似的东西)
  • 我认为您需要使用类助手来破解该私有方法
  • Mike Lischke 的TThemeManager 使用了一系列技术来检测程序中的新表单。 (它必须了解新的表单,这样它才能检测其组件的绘制例程并应用 XP 主题。)可能值得检查该代码以了解它是如何完成的。

标签: delphi vcl


【解决方案1】:

您可以使用SetWindowsHookEx 函数来安装WH_CBT Hook,然后您必须实现CBTProc callback 函数并最终截取此hook 的可能代码值之一。在这种情况下,您可以尝试使用HCBT_ACTIVATEHCBT_CREATEWND

检查此示例以获取 HCBT_ACTIVATE 代码。

var
 hhk: HHOOK;

function CBT_FUNC(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
const
  ClassNameBufferSize = 1024;
var
 hWindow: HWND;
 RetVal : Integer;
 ClassNameBuffer: Array[0..ClassNameBufferSize-1] of Char;
begin
   Result := CallNextHookEx(hhk, nCode, wParam, lParam);
   if nCode<0 then exit;
   case nCode of
     HCBT_ACTIVATE:
     begin
       hWindow := HWND(wParam);
       if (hWindow>0) then
       begin
          RetVal := GetClassName(wParam, ClassNameBuffer, SizeOf(ClassNameBuffer));
          if RetVal>0 then
          begin
            //do something  
            OutputDebugString(ClassNameBuffer);                     
          end;
       end;
     end;
   end;

end;

Procedure InitHook();
var
  dwThreadID : DWORD;
begin
  dwThreadID := GetCurrentThreadId;
  hhk := SetWindowsHookEx(WH_CBT, @CBT_FUNC, hInstance, dwThreadID);
  if hhk=0 then RaiseLastOSError;
end;

Procedure KillHook();
begin
  if (hhk <> 0) then
    UnhookWindowsHookEx(hhk);
end;

initialization
  InitHook();

finalization
  KillHook();

end.

注意:如果您使用 HCBT_CREATEWND 代码,您将 拦截系统创建的任何窗口,而不仅仅是“表单”。

【讨论】:

  • 谢谢,这是一个非常好的方法。虽然它有一些缺点:使用 HCBT_ACTIVATE 在 OnShow 事件之后绑定“//做某事”代码。而当使用HCBT_CREATEWND时,delphi的FindControl(hWindow)什么也找不到。
  • 这是因为HCBT_CREATEWNDHWND被创建时被触发,在VCL有机会将它与TWinControl对象关联之前。
  • 这绝对正确,但仍然是一个缺点......尽管如此,在我看来,它是一个比使用 OnIdle 更好的解决方案。对于 VCL 表单,它的效果与 Screen.ActiveFormChange 大致相同。
【解决方案2】:

Application.OnIdle 中跟踪Screen.CustomFormCount

  private
    FPrevFormCount: Integer;
  end;

procedure TForm1.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
begin
  if Screen.CustomFormCount > FPrevFormCount then
    Caption := Caption + ' +1';
  if Screen.CustomFormCount <> FPrevFormCount then
    FPrevFormCount := Screen.CustomFormCount;
end;

procedure TForm1.TestButton1Click(Sender: TObject);
begin
  TForm2.Create(Self).Show;
end;

procedure TForm1.TestButton2Click(Sender: TObject);
begin
  ShowMessage('Also trackable?');  // Yes!
end;

procedure TForm1.TestButton3Click(Sender: TObject);
begin
  OpenDialog1.Execute; // Doesn't update Screen.CustomFormCount
end;

Windows 管理和显示的本机对话框(TOpenDialogTFontDialog 等...)是在 VCL 之外创建的,为了跟踪它们,您需要一个黑客单元。那就试试this one吧。

【讨论】:

  • OnIdle 不像我在问题中描述的那样工作 - OnIdle 在 OnShow 之后被触发(使用主窗体测试)。我认为像 David 在 cmets 中建议的那样挂钩 VCL 是实现这个工作的唯一正确方法。但无论如何感谢您注意到本机对话框。
【解决方案3】:

感谢大卫,我找到了解决方案:线索是用您自己的方法替换 Screen.AddForm 方法。这些 SO 答案中描述了如何做到这一点:

再次感谢!

【讨论】:

  • 您是否尝试使用 SetWindowsHookEx 函数安装 WH_CBT 挂钩?
  • 好的,我会发布关于这个话题的答案。
猜你喜欢
  • 2011-10-09
  • 1970-01-01
  • 1970-01-01
  • 2013-07-08
  • 1970-01-01
  • 1970-01-01
  • 2012-08-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多