3

I am having trouble reloading a saved compound component in Delphi where the subcomponent (a TChart) appears to be being created twice. When I create my component in code, it works fine.

My component is a TPanel hosting a client aligned TChart and I create the single series and its data myself. Here is my code:

type

  TMyChart = class( TPanel )
  PRIVATE
    FChart : TChart;
  PROTECTED
    procedure Notification( AComponent : TComponent; Operation : TOperation ); override;
  PUBLIC
    constructor Create( AOwner : TComponent ); override;
    destructor  Destroy; override;
  PUBLISHED
    property Chart : TChart
               read FChart;
  end;


{ TMyChart }

constructor TMyChart.Create(AOwner: TComponent);
begin
  inherited;
  Width := 400;
  Height := 150;
  FChart := TChart.Create( Self );
  FChart.Name := '';
  FChart.SetSubComponent( True );
  FChart.FreeNotification( Self );
  FChart.Parent := Self;
  FChart.Align := alClient;
  FChart.AddSeries( TLineSeries.Create( FChart ));
  FChart.Series[0].FillSampleValues(100);
end;

destructor TMyChart.Destroy;
begin
  FreeAndNil( FChart );
  inherited;
end;

procedure TMyChart.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  if (AComponent = FChart) and (Operation = opRemove) then
    FChart := nil;
end;

initialization

  RegisterClass( TMyChart );
  RegisterClass( TChart );
  RegisterClass( TLineSeries );

end.

I've followed the example in ExtCtrls 'TLabeledEdit' to include a FreeNotification for the subcomponent TChart.

I stream this out using TStream.WriteComponent and get the following DFM:

object Mychart1: TMyChart
  Left = 0
  Top = 0
  Width = 400
  Height = 150
  Caption = 'Mychart1'
  TabOrder = 3
  Chart.Left = 1
  Chart.Top = 1
  Chart.Width = 398
  Chart.Height = 148
  Chart.Align = alClient
  Chart.TabOrder = 0
  Chart.ColorPaletteIndex = 13
  object TChart
    Left = 1
    Top = 1
    Width = 398
    Height = 148
    Align = alClient
    TabOrder = 0
    ColorPaletteIndex = 13
    object TLineSeries
      Marks.Arrow.Visible = True
      Marks.Callout.Brush.Color = clBlack
      Marks.Callout.Arrow.Visible = True
      Marks.ShapeStyle = fosRoundRectangle
      Marks.Visible = False
      Brush.BackColor = clDefault
      Pointer.InflateMargins = True
      Pointer.Style = psRectangle
      Pointer.Visible = False
      XValues.Name = 'X'
      XValues.Order = loAscending
      YValues.Name = 'Y'
      YValues.Order = loNone
    end
  end
end

Everything I require is there. I reload this DFM using ReadComponent as follows:

procedure LoadComponentFromFile( var AComponent : TComponent );
var
  MS : TMemoryStream;
  SS : TStringStream;
begin
  MS := TMemoryStream.Create;
  SS := TStringStream.Create;
  try
    SS.LoadFromFile( GetSpecialFolderPath( CSIDL_DESKTOP ) + '\test.txt' );
    SS.Position := 0;
    ObjectTextToBinary( SS, MS );
    MS.Position := 0;
    AComponent := MS.ReadComponent( nil );
  finally
    MS.Free;
    SS.Free;
  end;
end;

procedure TForm8.Button3Click(Sender: TObject);
begin
  FreeAndNil( FMyChart );

  LoadComponentFromFile( TComponent(FMyChart) );

  FMyChart.Parent := Self;
end;

After this I seem to get TWO TCharts created - one with the correct series data (constructed by TMyChart.Create) and another (in front of this) with empty axes, presumably where ReadComponent is constructing 'object TChart'. I have proven this by placing a breakpoint in 'TMyChart.Create' and another in the source code for TChart in 'TCustomChart.Create'. When calling the loader code it hits the TMyChart.Create breakpoint once, first, and then hits the TCustomChart.Create breakpoint twice. I can see that I need to tell TStream.ReadComponent not to recreate my subcomponent TChart but after many trials and reading of documentation on component streaming I cant see how to structure it. Can anyone comment please? Many thanks.

Brian Frost
  • 13,334
  • 11
  • 80
  • 154
  • 2
    `FChart` being created seems to be missing. Is this the real code? – NGLN Feb 10 '13 at 21:02
  • @NGLN: FChart is a field of TMyChart shown at the top of my question. The code is in two parts with the reader call and button click at the bottom. – Brian Frost Feb 10 '13 at 21:57
  • 5
    You're missing the point: In the constructor of the `TMyChart` component, the subcomponent `FChart` is not created! There should have been a `FChart := TChart.Create(Self)`! – NGLN Feb 10 '13 at 22:05
  • I'm really sorry for the confusion and you are right. I've corrected the code - this was an error from a last desperate test just before posting on SO. – Brian Frost Feb 11 '13 at 06:14
  • I have also improved my explanation of the problem and described my procedure for identifying the dual creation of TChart. – Brian Frost Feb 11 '13 at 06:28
  • 1
    Does dropping this component on the form result in the same problem? Try to name the subcomponent, as `TLabeledEdit does`. And I'm unsure about the need for those calls to `RegisterClass` in the initialization section. – NGLN Feb 11 '13 at 07:22
  • @NGLN: Good idea. Registering TMyChart as a component and dropping it on a form works fine, but the DFM ('View as text') has changed. FMyChart is now not marked as 'object TChart' as in my listing but simply 'Chart.xxx = yyyy'. – Brian Frost Feb 11 '13 at 08:50

0 Answers0