Delphi是否为表单创建通知提供事件处理程序?

Delphi是否为表单创建通知提供事件处理程序?,delphi,forms,dynamic,aop,Delphi,Forms,Dynamic,Aop,Delphi是否为表单创建提供了某种类型的事件或钩子(或者更一般地说,表单生命周期事件) 因此,如果在代码中的某个地方创建并显示了一个表单(模式或非模式、动态或在通常的应用程序启动阶段),Delphi会调用一个事件处理程序,允许在表单显示之前记录/分析/修改表单 我知道有一些选项涉及引入基本表单类或自定义表单创建过程,但是对于已经有许多表单的现有应用程序来说,最好有一个非侵入性选项来添加类似于面向方面编程(AOP)中横切关注点的内容 例如,如果我有一些用于使用情况统计跟踪的代码,其中注入了额外的

Delphi是否为表单创建提供了某种类型的事件或钩子(或者更一般地说,表单生命周期事件)

因此,如果在代码中的某个地方创建并显示了一个表单(模式或非模式、动态或在通常的应用程序启动阶段),Delphi会调用一个事件处理程序,允许在表单显示之前记录/分析/修改表单

我知道有一些选项涉及引入基本表单类或自定义表单创建过程,但是对于已经有许多表单的现有应用程序来说,最好有一个非侵入性选项来添加类似于面向方面编程(AOP)中横切关注点的内容

例如,如果我有一些用于使用情况统计跟踪的代码,其中注入了额外的事件处理程序,我可以简单地为每个表单添加此功能,开发人员不必更改应用程序代码,只需添加类似于此的代码

...
   Application.OnNewForm := MyNewFormCreated;
...

procedure TMyApp.MyNewFormCreated(Sender: TCustomForm);
begin
  // iterate over components and do other stuff with the new form
  ...
end;

目前我能想到的最适合您需要的选项是
屏幕。OnActiveFormChange
事件,每次当前活动表单更改时都会触发该事件。但这可能太晚了,无法满足您的需要。

在运行时,您可以覆盖
TCustomForm.DoCreate
TCustomFrame.Create方法,例如:

type
  THookedForm = class(TCustomForm)
    procedure HookedDoCreate;
  end;

  THookedFrame = class(TCustomFrame)
    constructor Create(AOwner: TComponent); override;
  end;

var
  OriginalForm, OriginalFrame: TPatchCode;

procedure PatchCreate;
begin
  if OriginalForm[0]<>0 then
    exit; // patch once
  RedirectCode(@THookedForm.DoCreate,@THookedForm.HookedDoCreate,@OriginalForm);
  RedirectCode(@THookedFrame.Create,@THookedFrame.Create,@OriginalFrame);
end;


// hook logic was inspired from GetText()

{ THookedForm }

procedure THookedForm.HookedDoCreate;
// translate form contents just before an OnCreate handler would be called
begin
  try
  try
    if Language<>nil then begin
      DisableAlign;
      DisableAutoRange;
      try
        Language.FormTranslateOne(self); // translate form
      finally
        EnableAlign;
        EnableAutoRange;
      end;
    end;
  finally
    RedirectCodeRestore(@THookedForm.DoCreate,OriginalForm); // disable Hook
    try
      DoCreate;  // call normal DoCreate event
    finally
      RedirectCode(@THookedForm.DoCreate,@THookedForm.HookedDoCreate);
    end;
  end;
  except
    on Exception do; // ignore all raised exception
  end;
end;

{ THookedFrame }

constructor THookedFrame.Create(AOwner: TComponent);
// translate frame contents just after constructor has been called
begin
  RedirectCodeRestore(@THookedFrame.Create,OriginalFrame); // disable Hook
  try
    inherited Create(AOwner); // call normal constructor
  finally
    RedirectCode(@THookedFrame.Create,@THookedFrame.Create);
  end;
  if Language=nil then exit;
  DisableAlign;
  DisableAutoRange;
  try
    Language.FormTranslateOne(self); // translate frame
  finally
    EnableAlign;
    EnableAutoRange;
  end;
end;


....

initialization
  PatchCreate;
类型
THookedForm=类(TCustomForm)
程序创建;
结束;
THookedFrame=类(TCustomFrame)
构造函数创建(AOwner:TComponent);推翻
结束;
变量
原始格式,原始帧:TPatchCode;
程序补丁创建;
开始
如果原始形式[0]0,则
退出;//修补一次
重定向代码(@THookedForm.DoCreate、@THookedForm.HookedDoCreate、@OriginalForm);
重定向代码(@THookedFrame.Create、@THookedFrame.Create、@OriginalFrame);
结束;
//钩子逻辑的灵感来自GetText()
{THookedForm}
过程THookedForm.hookedToCreate;
//在调用OnCreate处理程序之前转换表单内容
开始
尝试
尝试
如果是Languagenil,那么就开始吧
不结盟;
破坏范围;
尝试
语言。FormTranslateOne(self);//翻译形式
最后
使能林;
使能范围;
结束;
结束;
最后
重定向代码还原(@THookedForm.DoCreate,OriginalForm);//禁用挂钩
尝试
DoCreate;//调用普通DoCreate事件
最后
重定向代码(@THookedForm.DoCreate、@THookedForm.hookedtocreate);
结束;
结束;
除了
在异常情况下执行;//忽略所有引发的异常
结束;
结束;
{THookedFrame}
构造函数THookedFrame.Create(AOwner:TComponent);
//在调用构造函数之后立即转换框架内容
开始
重定向代码还原(@THookedFrame.Create,OriginalFrame);//禁用挂钩
尝试
继承的创建(AOOwner);//调用正规构造函数
最后
重定向代码(@THookedFrame.Create,@THookedFrame.Create);
结束;
如果语言=零,则退出;
不结盟;
破坏范围;
尝试
语言。FormTranslateOne(self);//平移帧
最后
使能林;
使能范围;
结束;
结束;
....
初始化
拼凑创造;
因此,每次创建TForm实例时都会调用您自己的
DoCreate
事件


此代码是从中提取的,您可以在中找到修补程序(适用于Windows和Linux/BSD)。

如果不选择基于公共超类的实现,这是我的选择,那就太晚了,总比不回答:-)

  • 使用通知框架

通知服务
Bitbucket项目前景看好。你可以得到它。它基于TNotify。

没有内置任何内容。最好的解决方案是有一个公共基类。@David:是的,这可能是最好的解决方案。请回答这个问题。@Andreas这不是一个答案,因为@mjn说公共基类不是我们想要的。你可以在最新版本的Rtti单元中找到
TVirtualMethodInterceptor
。@Barry TVirtualMethodInterceptor有一些重要的限制:1。它基于RTTI,不适用于私有/受保护的方法。2.它基于虚拟方法表的替换,所以它只适用于虚拟方法(不适用于动态/静态…),但是“太频繁”呢?这将在每次更改活动窗体时触发。例如,如果在整个应用程序的生命周期中只有两个表单,但在这761次之间切换,则事件将触发大约761次…使用
TListOfKnownForms
@mjn解决。您确定正确实现了TListOfKnownForms吗?如何识别表单实例(保留指针不是一个解决方案,如果创建表单,第二个实例可能与第一个实例在内存中的位置相同)?什么是
TPatchEvent
?我想这是记录。@ArnaudBouchez:你的代码不起作用了。调用
THookedForm中的
DoCreate
。HookedDoCreate
将再次调用
THookedForm.HookedDoCreate
,它将无休止地重复调用DoCreate,直到堆栈溢出。@Chaucheyang此代码多年来一直用于生产。这里没有无限循环@Arnaud刚刚尝试使用XE6(Win x32平台)。正如Chau提到的,有一个无限循环,它在StackOverflow中失败(这很有趣)错误。@Arnaud代码中还有一个问题-指针到整数的转换对于x64平台是不正确的,在某些情况下可能会导致崩溃。公共类不是选项,因为它需要更改所有现有应用程序的所有现有形式。我看不出建议的已注册事件处理程序列表有什么帮助,您必须更改(每个应用程序的)每个表单,以便将消息从表单(创建表单时)传递到框架。
// Arnaud Bouchez provided great code, but he cut some important pieces of own code.
// And what is more important - he didn't try to run it even once before posting :)
// There is correct unit (copy-pasted from another project & tested with XE6/Win.x32)
// It works for Windows x32 and x64 platforms.

unit HookCreateFrm;

interface

implementation

uses
  Windows, Classes, Forms, IdGlobal, SysUtils;

type
  THookedForm = class(TCustomForm)
    procedure HookedDoCreate;
  end;

  THookedFrame = class(TCustomFrame)
    constructor Create(AOwner: TComponent); override;
  end;

  PPatchEvent = ^TPatchEvent;
  // asm opcode hack to patch an existing routine
  TPatchEvent = packed record
    Jump: byte;
    Offset: integer;
  end;

var
  PatchForm, OriginalForm: TPatchEvent;
  PatchPositionForm: PPatchEvent = nil;
  PatchFrame, OriginalFrame: TPatchEvent;
  PatchPositionFrame: PPatchEvent = nil;

procedure PatchCreate;
var ov: cardinal;
begin
  // hook TForm:
  PatchPositionForm := PPatchEvent(@THookedForm.DoCreate);
  OriginalForm := PatchPositionForm^;
  PatchForm.Jump := $E9; // Jmp opcode
  PatchForm.Offset := PByte(@THookedForm.HookedDoCreate)-PByte(PatchPositionForm)-5;
  if not VirtualProtect(PatchPositionForm, 5, PAGE_EXECUTE_READWRITE, @ov) then
    RaiseLastOSError;
  PatchPositionForm^ := PatchForm; // enable Hook
  // hook TFrame:
  PatchPositionFrame := PPatchEvent(@TCustomFrame.Create);
  OriginalFrame := PatchPositionFrame^;
  PatchFrame.Jump := $E9; // Jmp opcode
  PatchFrame.Offset := PByte(@THookedFrame.Create)-PByte(PatchPositionFrame)-5;
  if not VirtualProtect(PatchPositionFrame, 5, PAGE_EXECUTE_READWRITE, @ov) then
    RaiseLastOSError;
  PatchPositionFrame^ := PatchFrame; // enable Hook
end;

// hook logic was inspired from GetText()

{ THookedForm }

procedure THookedForm.HookedDoCreate;
begin

  // do what you want before original DoCreate

  PatchPositionForm^ := OriginalForm;
  try
    DoCreate;
  finally
    PatchPositionForm^ := PatchForm;
  end;

  // do what you want after original DoCreate

end;

{ THookedFrame }

constructor THookedFrame.Create(AOwner: TComponent);
begin

  // do what you want before original DoCreate

  PatchPositionFrame^ := OriginalFrame;
  try
    inherited Create(AOwner);
  finally
    PatchPositionFrame^ := PatchFrame;
  end;

  // do what you want after original Create

end;

initialization
  PatchCreate;

end.