如何正确发布从“加载”过程执行的事件?

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过程的数据正确性。这个过程会产生哪些问题?

C# - Winform: 在窗体加载的过程中截获异常,如何不让窗体显示出来。

jquery-事件之自动执行

JS引擎线程的执行过程的三个阶段

类的执行过程