如何正确发布从“加载”过程执行的事件?
Posted
技术标签:
【中文标题】如何正确发布从“加载”过程执行的事件?【英文标题】:How to properly publish an event executed from the 'Loaded' procedure? 【发布时间】:2017-02-18 23:08:57 【问题描述】:在仅运行时的包中,我定义了一个 TFrame
后代,它发布了 OnLoaded
事件:
type
TMyMethod = procedure() of object;
TMyFrame = class(TFrame)
protected
FOnLoaded : TMyMethod;
procedure Loaded(); override;
published
property OnLoaded : TMyMethod read FOnLoaded write FOnLoaded;
end;
implementation
$R *.dfm
procedure TMyFrame.Loaded();
begin
inherited;
if(Assigned(FOnLoaded))
then FOnLoaded();
end;
在仅设计时的包中,我注册了TMyFrame
组件,如下所示:
unit uMyRegistrations;
interface
uses
Classes, uMyFrame;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('MyTestComponents', [
TMyFrame
]);
end;
我已经安装了 designtime 包,我可以在工具面板中找到TMyFrame
,它的OnLoaded
事件显示在对象检查器中。
我已将TMyFrame
拖入表单,然后通过在对象检查器中双击来分配OnLoaded
事件。
分配事件后,我注意到每次尝试在 Delphi 中打开表单文件时都会出现访问冲突错误消息(它让我打开“.pas”文件,但我无法切换到可视化设计器视图)。
我是否正确发布了OnLoaded
事件?如果是这样,还有什么问题?
更多信息:
-
我使用的是 Delphi 2007(不知道是否重要)。
对不同的父类执行相同的操作也会出现该错误(不仅适用于
TFrame
后代)。
【问题讨论】:
【参考方案1】:更新(不那么虚假)答案
您接受了我原来的答案,但我写的不正确。 Rob Kennedy 指出了前 Embarcadero 开发人员 Allen Bauer 关于Assigned
主题的article。
Allen 解释说Assigned
函数只测试方法指针中两个指针中的一个指针。设计时的 IDE 通过将标记值分配给任何已发布的方法属性(即事件)来利用这一点。这些标记值具有nil
用于方法指针中的两个指针之一(Assigned
检查的那个),以及标识另一个指针中的属性值的索引。
这意味着当您在设计时调用Assigned
时会返回False
。只要您在调用它们之前使用Assigned
检查已发布的方法指针,那么您将永远不会在设计时调用它们。
所以我原来写的不可能是真的。
所以我挖得更深了。我使用了以下非常简单的代码,用 XE7 进行测试:
type
TMyControl = class(TGraphicControl)
protected
FSize: Integer;
procedure Loaded; override;
end;
....
procedure TMyControl.Loaded;
begin
inherited;
FSize := InstanceSize;
end;
....
procedure Register;
begin
RegisterComponents('MyTestComponents', [TMyControl]);
end;
只要执行Loaded
方法,这足以在设计时在 IDE 中引发 AV。
我的结论是,IDE 在流式传输时做了一些相当卑鄙的事情,当调用 Loaded
方法时,您的对象不适合使用。但我真的没有比这更好的理解了。
原始(非常虚假)答案
您不能在设计时执行事件处理程序,您的代码就是这样做的。原因是在设计时事件处理程序的代码不可用。
控件的代码可用,IDE 已加载它——但实现事件处理程序的代码不可用。该代码不是设计时包的一部分,它是当前在 IDE 中打开的项目的一部分。毕竟,它甚至可能还没有编译!
Loaded
方法应该像这样防御这种情况:
procedure TMyFrame.Loaded();
begin
inherited;
if not (csDesigning in ComponentState) and Assigned(FOnLoaded) then
FOnLoaded();
end;
【讨论】:
Assigned
函数应该已经可以防止这种情况发生。 At design time, the IDE sets the event handler to a special value so it knows what to write in the DFM resource, but that Assigned
will consider to be unassigned. 除非从 Loaded
触发事件处理程序有什么特别之处,否则我认为这个问题还有更多。
@RobKennedy 我认为你是对的,非常感谢。我认为还有更多的事情发生。虽然我无法完全解释它,但我可以从 Loaded
方法重现问题,而无需对 Assigned
进行任何测试,也无需任何方法指针。
我接受了您的回答,因为添加“not (csDesigning in ComponentState)”条件解决了 AV 错误,我认为这是我在设计时使用 AV 的主要原因。根据您更新的答案,“已分配(...)”条件足以处理“正常”事件。我检查了 TCustomForm.DoShow 事件,我可以确认你所说的,它会触发 OnShow 事件,例如“如果分配(FOnShow)然后 FOnShow(Self);”。我将询问有关从 Loaded 等方法执行事件的具体问题。感谢您更新了您的答案。
为什么要这样使用Loaded
?我非常怀疑无论是什么促使你这样做,以这种方式使用Loaded
是错误的解决方案。
我在 TFrame 后代中使用加载来初始化属性。我还使用 DevExpress TdxTileControl 组件中的 LayoutChanged 方法发现了同样的问题。在这种情况下,我使用 LayoutChanged 来检测 TdxTileControl 中的任何更改(如果您知道该控件,则在用户自定义组时不会触发任何事件)。以上是关于如何正确发布从“加载”过程执行的事件?的主要内容,如果未能解决你的问题,请参考以下文章
如何正确执行包含过程且在 Objectscript/Intersystems IRIS 中没有返回的查询
解答如何保障ETL过程的数据正确性。这个过程会产生哪些问题?