delphieventscomponentsdesign-time

How to properly publish an event executed from the 'Loaded' procedure?


In a runtime only package, I've defined a TFrame descendant which publishes the OnLoaded event:

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;

In a designtime only package, I've registered TMyFrame component as follows:

unit uMyRegistrations;

interface

uses
  Classes, uMyFrame;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('MyTestComponents', [
    TMyFrame
  ]);
end;

I've installed the designtime package, I can find TMyFrame in the tool palette and its OnLoaded event is shown in the object inspector.

I've dragged a TMyFrame into a form, then I've assigned the OnLoaded event by doubleclicking from the object inspector. After assigning the event, I noticed that an access violation error message appears each time I try to open the form's file in Delphi (It let me open the ".pas" file, but I can't switch to visual designer view).

enter image description here

Did I correctly published the OnLoaded event? If so, what else is wrong?

Further Informations:

  1. I'm using Delphi 2007 (don't know if it matters).
  2. The error also appears by doing the same thing with different parent classes (Not only for TFrame descendants).

Solution

  • Updated (somewhat less bogus) answer

    You accepted my original answer, but what I wrote was not correct. Rob Kennedy pointed to an article by former Embarcadero developer Allen Bauer on the topic of Assigned.

    Allen explains that the Assigned function only tests one pointer of the two pointers in a method pointer. The IDE at design time takes advantage of this by assigning sentinel values to any published method properties (i.e. events). These sentinel values have nil for one of the two pointers in the method pointer (the one that Assigned checks), and an index identifying the property value in the other pointer.

    All this means that False is returned when you call Assigned at design time. So long as you check published method pointers with Assigned before calling them, then you will never call them at design time.

    So what I originally wrote cannot be true.

    So I dug a bit deeper. I used the following very simple code, testing with 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;
    

    This was enough to cause an AV in the IDE at design time whenever the Loaded method was executed.

    My conclusion is that the IDE does some rather underhand things when streaming, and your objects are not in a fit state to use when the Loaded method is called. But I don't really have a better understanding than that.


    Original (very bogus) answer

    You must not execute event handlers at design time, and your code does just that. The reason being that at design time the event handler's code is not available.

    The control's code is available, the IDE has loaded it – but the code that implements the event handler is not. That code is not part of the design time package, it is part of the project that is currently open in the IDE. After all, it might not even compile yet!

    The Loaded method should defend against this like so:

    procedure TMyFrame.Loaded();
    begin
      inherited;
      if not (csDesigning in ComponentState) and Assigned(FOnLoaded) then 
        FOnLoaded();
    end;