How to tell Delphi to not include non-published properties in DFM?
Asked Answered
E

1

7

I have a custom control:

type
   TContosoFrobber = class(TCustomControl)
   private
   end;

Internally, my component creates a control:

type
   TContosoFrobber = class(TCustomControl)
   private
      FMyDateTimePicker: TDateTimePicker;
   public
      constructor Create(AOwner : TComponent); override;
      property DateTimePicker: TDateTimePicker read FMyDateTimePicker;
   end;

constructor TContosoFrobber.Create(AOwner: TComponent);
begin
   inherted Create(AOwner);

   FMyControl := TMyDateTimePicker.Create(AOwner);
end;

where TMyDateTimePicker is a simple descendant of TDateTimePicker

TMyDateTimePicker = class(TDateTimePicker)
protected
end;

So sum up what i have done:

  • declare a private variable of TDateTimePicker
  • expose it as a public (i.e. non-published) property of type TDateTimePicker
  • through polymorphism, the control is actually a descendant of TDateTimePicker

And that all worked - until i recently when i re-installed Delphi XE6 (on Windows 10).

The DFM

Which is why i couldn't understand why i got the error at design-time:

Class TMyDateTimePicker not found

Why is it trying to find that class? That class in an implementation detail; it's not published for streaming. How is the streaming system even trying to create it!? So i check the DFM:

  object cfBeachBall: TContosoFrobber
     Left = 445
     Top = 25
     Width = 101
     Height = 22
     ...snip...
     object TMyDateTimePicker
        Left = 0
        Top = 0
        Width = 101
        Height = 22
        Date = 37306.581535243100000000
        Time = 37306.581535243100000000
        TabOrder = 0
        TabStop = False
     end
  end

Why is the TInternalDateTimePicker ending up in the dfm:

  • the property is public, not published

How do i stop the form streaming system from placing a non-published property in the dfm?

Even worse is the buggy IDE

Not only is it sometimes including a property it shouldn't. Sometimes it is including the property it shouldn't twice:

  object cfPlasticBag: TContosoFrobber
     Left = 445
     Top = 25
     Width = 101
     Height = 22
     ...snip...
     object TMyDateTimePicker
        Left = 0
        Top = 0
        Width = 101
        Height = 22
        Date = 37306.581535243100000000
        Time = 37306.581535243100000000
        TabOrder = 0
        TabStop = False
     end
     object TMyDateTimePicker
        Left = 0
        Top = 0
        Width = 101
        Height = 22
        Date = 37306.581535243100000000
        Time = 37306.581535243100000000
        TabOrder = 0
        TabStop = False
     end
  end
  • how do i stop the dfm from containing non-published properties?
  • how do i stop the dfm from including it twice?

Hack workaround

I know the horrible hack: tell the DFM about the control it should not have any business knowing about:

initialization
    RegisterClass(TMyDateTimePicker);

finalization
    UnRegisterClass(TMyDateTimePicker);

end.

Now the dfm contains a control it has no business knowing about. Any time i save a form it will contain references to things it shouldn't. And worst of all: i validated it's mistaken belief in a TMyDateTimePicker.

Why did Delphi XE6 not do this before the reinstall? Perhaps i need to install the last updated of the long-since unsupported version of Delphi?

Known bug?

enter image description here

Ernie answered 14/9, 2016 at 19:40 Comment(1)
Try not setting an owner. Pass nil as its owner when creating it. (Of course, you'd have to explicitly free it in this case too).Panarabism
A
12

The problem is that you are assigning the wrong Owner to the TDateTimePicker. You are assigning your TContosoFrobber's Owner instead of the TContosoFrobber itself:

constructor TContosoFrobber.Create(AOwner: TComponent);
begin
  inherted Create(AOwner);
  FMyControl := TMyDateTimePicker.Create(Self); // <-- not AOwner!
end;

Or: as Jerry Doge mentioned, you can use a nil Owner instead. You just have to Free() the TDataTimePicker manually:

type
  TContosoFrobber = class(TCustomControl)
  private
    FMyDateTimePicker: TDateTimePicker;
  public
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
    ...
  end;

constructor TContosoFrobber.Create(AOwner: TComponent);
begin
  inherted Create(AOwner);
  FMyControl := TMyDateTimePicker.Create(nil);
end;

destructor TContosoFrobber.Destroy;
begin
  FMyControl.Free;
  inherted Destroy;
end;
Action answered 14/9, 2016 at 20:4 Comment(4)
Whenever I do things like this, I typically pass nil and have never had a problem. Can you explain what difference it actually makes?Panarabism
When TDataModule/TFrame/TForm are saved to a DFM, any components they directly own that do not have a Parent assigned get streamed as well. In the example given, the TMyDateTimePicker object has no Parent assigned. If its Owner is set to the Form, that could account for the issue. Setting TContosoFrobber as the Owner avoids the problem (and avoids needing an explicit Free during destruction).Action
Any insight why it would have worked for eleven years (D5 and XE6)? I'm thinking about Delphi's expanded RTTI type system, how you now have the option to RTTI not just published propertyes, and perhaps a stray define or compiler option somewhere. It would be nice if the solution (and this does fix) explained it all.Ernie
@IanBoyd: DFM streaming has always worked this way, all the way back to D5. DFM streaming does not use Extended RTTI (which was introduced in D2010). I don't know why you are only just now seeing this behavior, but the original code was wrong for any version anyway.Action

© 2022 - 2024 — McMap. All rights reserved.