【问题标题】: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 事件?如果是这样,还有什么问题?

更多信息:

  1. 我使用的是 Delphi 2007(不知道是否重要)。
  2. 对不同的父类执行相同的操作也会出现该错误(不仅适用于TFrame 后代)。

【问题讨论】:

    标签: delphi events components design-time


    【解决方案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 中的任何更改(如果您知道该控件,则在用户自定义组时不会触发任何事件)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-03
    • 1970-01-01
    • 2019-04-09
    • 2021-12-15
    相关资源
    最近更新 更多