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