3

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).
Fabrizio
  • 7,603
  • 6
  • 44
  • 104

1 Answers1

8

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;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 2
    The `Assigned` function is supposed to protect against that already. [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.](http://community.embarcadero.com/blogs/entry/assigned-or-not-assigned-that-is-the-question-28836) Unless there's something special about firing event handlers from `Loaded`, I think there's more to this question. – Rob Kennedy Oct 10 '16 at 18:05
  • @RobKennedy I think you are right, thank you very much. I think there is something more going on. I can't explain it fully though, but I can reproduce problems from the `Loaded` method without any tests for `Assigned` and without any method pointers. – David Heffernan Oct 10 '16 at 19:25
  • I've accepted your answer because adding "not (csDesigning in ComponentState)" condition had resolved the AV error and I thought it was the very main reason why I had AV at designtime. According to your updated answer, "Assigned(...)" condition is enough for "normal" events. I've checked TCustomForm.DoShow event and I can confirm what you said, it fires OnShow event like that "if Assigned(FOnShow) then FOnShow(Self);". I'll ask specific question about executing events from methods like Loaded. Thanks for have updated your answer. – Fabrizio Oct 11 '16 at 07:01
  • Why do you want to use `Loaded` in this way? I very much suspect that whatever is motivating you to do that, using `Loaded` in this way is the wrong solution. – David Heffernan Oct 11 '16 at 07:05
  • I'm using Loaded in TFrame descendants for initializing attributes. I also found the same problem using the LayoutChanged method, from DevExpress TdxTileControl component. In this case I'm using LayoutChanged for detecting any changes in the TdxTileControl (If you know that control, there is no event fired when the user customizes groups). – Fabrizio Oct 11 '16 at 07:45