Home Component Writing
New Blog Posts: Merging Reports - Part 1 and Part 2

constructor gets called twice

edited December 2005 in Component Writing
Hi,

I created a footer component for RAP. Basically when a user drops such a
footer on the report, it creates a TppRegion and 3 system variables and a
TppLabel in the TppRegion. The create is like this:
----------------------------------------------------------
constructor TRbFooter.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
Self.Height := 0.25;
Self.Width :=8;
Self.Pen.Style := psClear;
CreateSystemVariablesAndTppLabel; --> gets called again
Self.ParentWidth := True;
end;

----------------------------------------------------------

It works fine in Design tab when drop the component on the report. But when
I switch to Preview and then switch back to Design, the Create constructor
gets called again and I get duplicated system variables and TppLabels.

Any idea about how to fix it?

Comments

  • edited December 2005

    1. When a report that includes RAP is previewed, the report definition is
    saved to a temp stream - just before preview. When returning to the Design
    workspace from Preview, then the report definition is loaded from the temp
    stream so that is it restored to its prior state. This is necessary because
    there is the possibility the RAP code modified the report layout while the
    report was generating.

    2. Your custom region component - like all RB components must be
    persistent - be able to be read and written to/from a stream. RB uses
    standard Delphi object peristence. This is used for the above and for saving
    the report layout as part of a .dfm or to an .rtm file.

    When your component is written to a stream - all the components are saved -
    the Region, SystemVariable, Label etc. When your component is loaded from
    stream, the constructor fires and creates additional SystemVariable and
    Label components.

    In your custom region class - overrde the public AfterDesignerCreate method
    and call CreateSystemVariablesAndTppLabel from that method, rather than
    from the constructor. The AfterDesignerCreate method is called by the report
    designer, when the component is added to the report layout. (RB's
    TppTeeChart components use this technique).

    3. When you create the SystemVariables and Label, make sure that the Owner
    is the same as the Owner of your component. And set the
    SystemVariable.Region and Label.Region to be your customer component.

    mySystemVariable.Region := TppSystemVariable.Create(Owner);
    mySystemVariable.Region := Self;






    Best regards,

    Nard Moseley
    Digital Metaphors
    www.digital-metaphors.com
  • edited December 2005
    Hi,

    That works. Except one thing- I switch to Preview, then back to Design,
    then back to Preivew again. Here I get a Access Violation error at
    SaveReportState in procedure SetPreviewPageActive in ppDsgner.pas.

    I know it is saving it to a stream. But why the first time saving to
    stream works while the second time doesn't? Is it something related to my
    component?

    Looking forward to your help!

    Thanks!

  • edited December 2005

    - One option is to post the source code to the entire custom component class
    here and I can review it. I might be able to determine what is causing the
    error from looking at your code

    - The other option is create a simple, minimal example project that we can
    run here in the Delphi debugger and send to support@digital-metaphors.com in
    zip format




    Best regards,

    Nard Moseley
    Digital Metaphors
    www.digital-metaphors.com
  • edited December 2005
    Sure, here you go

    //BEGIN OF COMPONENT------------------------------------------------------
    unit rrRbFooter;

    interface

    uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
    inifiles, ExtCtrls,
    ppRegion, ppClass, ppBands, ppSubRpt, ppTypes, ppVar, ppCtrls, ppcomm;

    type


    { TrrRbFooter }
    TrrRbFooter = class(TppRegion)
    private
    //FReportName: TppDBText;
    FReportName: TppSystemVariable;
    FPageNo: TppSystemVariable;
    FReportSource: TppLabel;
    FDateTime : TppSystemVariable;

    FReportNameVisible : Boolean;
    FPageNoVisible : Boolean;
    FReportSourceVisible :Boolean;
    FDateTimeVisible :Boolean;

    FReportNameVarType :TppVarType;
    FPageNoVarType :TppVarType;
    FReportSourceCaption :String;
    FDateTimeVarType :TppVarType;

    FFooterFont :TFont;
    FFooterFontSize :Integer;

    FFontSynChronous:Boolean;

    FOnGetText :TppGetTextEvent;

    //SynchroFontTimer:TTimer;

    function GetPageNovisible:Boolean;
    Procedure SetPageNoVisible(Value:Boolean);
    function GetReportNameVisible:Boolean;
    Procedure SetReportNameVisible(Value:Boolean);
    function GetReportSourceVisible:Boolean;
    Procedure SetReportSourceVisible(Value:Boolean);
    function GetDatetimeVisible:Boolean;
    Procedure SetDateTimeVisible(Value:Boolean);

    function GetReportNameVarType:TppVarType;
    procedure SetReportNameVarType(Value:TppVarType);
    function GetPageNoVarType:TppVarType;
    procedure SetPageNoVarType(Value:TppVarType);
    function GetDateTimeVarType:TppVarType;
    procedure SetDateTimeVarType(Value:TppVarType);

    function GetReportScCaption:String;
    Procedure SetReportScCaption(Value:String);

    function GetFooterFont:TFont;
    procedure SetFooterFont(Value:TFont);
    Procedure SetFooterFontSize(Value:Integer);

    Procedure SetFontSynchronous(Value:Boolean);
    procedure InitMmbObjs;
    Procedure SetObjPosition(Sender: Tobject);
    procedure OnObjGetText(Sender: TObject; var Text: String);
    //Procedure OnSynChroFontTimer(Sender: TObject);
    protected

    //function GetCaption: String; override;

    public

    constructor Create(aOwner: TComponent); override;
    destructor Destroy; override;
    procedure Generate; override;
    procedure AfterDesignerCreate; override;
    Procedure ResetObjPosition;
    procedure SynChroObjFont;
    published
    property ReportNameVisible : Boolean read GetReportNameVisible write
    SetReportNameVisible default True;
    property PageNoVisible : Boolean read GetPageNovisible write
    SetPageNoVisible default True ;
    property ReportSourceVisible :Boolean read GetReportSourceVisible
    write SetReportSourceVisible default True;
    property DateTimeVisible :Boolean read GetDatetimeVisible write
    SetDateTimeVisible default True;
    property ReportSourceCaption :String read GetReportScCaption write
    SetReportScCaption ;
    property Font :TFont read GetFooterFont write SetFooterFont;
    property FooterFontSize :Integer read FFooterFontSize write
    SetFooterFontSize Default 8;
    property FontSynChronous:Boolean read FFontSynChronous write
    SetFontSynChronous;
    end;

    implementation

    {$R rrRbFooter.res}

    { TrrRbFooter.Create }
    {------------------------------------------------------------------------------}
    constructor TrrRbFooter.Create(aOwner: TComponent);
    begin
    inherited Create(aOwner);
    Self.Height := 0.3;
    self.BottomOffset:=0;
    Self.Width :=8;
    Self.Pen.Style := psClear;
    Self.ParentWidth := True;
    Self.FontSynChronous := True;
    end;

    {------------------------------------------------------------------------------}
    { TrrRbFooter.Destroy }

    destructor TrrRbFooter.Destroy;
    begin
    try
    if FReportName<>nil then
    begin
    FreportName.Free;
    end;
    if FPageNo<>nil then
    begin
    FPageNo.Free;
    end;
    if FReportSource<>nil then
    begin
    FReportSource.Free;
    end;
    if FDateTime<>nil then
    begin
    FDateTime.Free;
    end;
    if Assigned(FFooterFont) then
    begin
    FFooterFont.Free;
    FFooterFont := nil;
    end;
    except
    end;

    inherited Destroy;

    end;

    procedure TrrRbFooter.Generate;
    begin
    inherited Generate;
    end;

    procedure TrrRbFooter.InitMmbObjs;
    begin
    //FReportName := TppDBText.Create(Self.Owner);
    FReportName:=TppSystemVariable.Create(self.Owner);
    FReportName.Region := Self;
    //FReportName.DataPipeline:=FReportName.Band.Report.Template.DatabaseSettings.DataPipeline;
    //FReportName.DataField:='Name';
    FReportName.VarType := vtDocumentName;
    FReportName.UserName:= 'ReportName';
    FReportName.AutoSize := True;
    FReportName.OnGetText := OnObjGetText;
    //Self.AddObject(FReportName);

    //self.Caption := FloatToStr(Self.Width);
    FPageNo := TppsystemVariable.Create(Self.Owner);
    FpageNo.Region := Self;
    FpageNo.VarType := vtPageSet;
    FPageNo.UserName :='PageNo';
    FpageNo.AutoSize := True;
    FPageNo.OnGetText := OnObjGetText;
    //self.AddObject(FpageNo);

    FReportsource := TppLabel.Create(Self.Owner);
    FReportsource.Region := Self;
    FReportsource.UserName :='Reportsource';
    FReportsource.Caption := 'RentRight Property Management Software';
    FReportsource.AutoSize := True;
    FReportSource.OnGetText := OnObjGetText;
    //Self.AddObject(FReportsource);

    FDateTime := TppsystemVariable.Create(Self.Owner);
    FDateTime.Region := Self;
    FDateTime.VarType := vtDateTime;
    FDateTime.UserName :='DateTime';
    FDateTime.AutoSize := True;
    FDateTime.OnGetText := OnObjGetText;
    //Self.AddObject(FdateTime);
    FDateTime.Width:= 1.06;
    SetFooterFontSize(8);
    end;


    Procedure TrrRbFooter.ResetObjPosition;
    begin
    if FReportName<>nil then
    SetObjPosition(FReportName);
    if FPageNo<>nil then
    SetObjPosition(FPageNo);
    if FReportSource<>nil then
    SetObjPosition(FReportsource);
    if FDateTime<>nil then
    SetObjPosition(FdateTime);
    end;

    Procedure TrrRbFooter.SetObjPosition(Sender: Tobject);
    var
    ObjTop:Single;
    begin
    if Self.Height>FReportName.Height then
    ObjTop := Self.Top+(Self.Height-FReportName.Height)/2
    else
    ObjTop := Self.Top+0.01;

    //if TppDBText(Sender)=FReportName then
    if TppSystemVariable(Sender)=FReportName then
    begin
    FReportName.AutoSize := True;
    FReportName.Top := ObjTop;
    FReportName.Left:= Self.Left +0.01;
    end
    else if TppsystemVariable(Sender)=FPageNo then
    begin
    FpageNo.AutoSize := True;
    FpageNo.Top := ObjTop;
    FpageNo.Left:= (Self.Width-FpageNo.Width-self.Left)/2;
    end
    else if TppLabel(Sender)=FReportsource then
    begin
    FReportsource.AutoSize := True;
    FReportsource.Top := ObjTop;
    FReportsource.Left:= (Self.Width-FReportsource.Width-self.Left)*3/4;
    end
    else if TppsystemVariable(Sender)=FdateTime then
    begin
    FDateTime.AutoSize := True;
    FDateTime.Top := ObjTop;
    //FDateTime.Width:= 1.05;
    FDateTime.Left:= Self.Width-FDateTime.Width-self.Left-0.01;
    end;
    end;

    procedure TrrRbFooter.OnObjGetText(Sender: TObject; var Text: String);
    begin
    SetObjPosition(Sender);

    end;

    function TrrRbFooter.GetFooterFont:Tfont;
    begin
    if FFooterFont=nil then
    FFooterFont := Tfont.Create;
    Result := FFooterFont;
    end;

    procedure TrrRbFooter.SetFooterFont(Value:TFont);
    begin
    if FFooterFont=nil then
    FFooterFont := Tfont.Create;
    FFooterFont.Assign(Value);

    end;

    Procedure TrrRbFooter.SetFooterFontSize(Value:Integer);
    begin
    FFooterFontSize :=Value;
    if FReportName<>nil then
    FReportName.Font.Size := Value ;
    if FPageNo<>nil then
    FPageNo.Font.Size:= Value;
    if FReportSource<>nil then
    FReportSource.Font.Size := Value ;
    if FDateTime<>nil then
    FDateTime.Font.Size:= Value;

    ResetObjPosition;
    end;

    procedure TrrRbFooter.SynChroObjFont;
    begin
    if FReportName<>nil then
    FReportName.Font.Assign(Self.Font);
    if FPageNo<>nil then
    FpageNo.Font.Assign(Self.Font);
    if FReportsource<>nil then
    FReportSource.Font.Assign(Self.Font);
    if FDateTime<>nil then
    FDateTime.Font.Assign(Self.Font);
    FooterFontSize:= Font.Size;
    ResetObjPosition;
    end;

    Procedure TrrRbFooter.SetFontSynchronous(Value:Boolean);
    begin
    FFontSynChronous := Value;
    if Value then
    SynChroObjFont;
    end;

    function TrrRbFooter.GetPageNoVisible:Boolean;
    begin
    Result := FPageNo.Visible;
    FPageNoVisible :=FPageNo.Visible;
    end;
    Procedure TrrRbFooter.SetPageNoVisible(Value:Boolean);
    begin
    FPagenovisible:= Value;
    if FPageNo<>nil then
    FPageNo.Visible := Value;
    end;


    function TrrRbFooter.GetReportNameVisible:Boolean;
    begin
    Result := FReportName.Visible;
    FReportNameVisible :=FReportName.Visible;
    end;
    Procedure TrrRbFooter.SetReportNameVisible(Value:Boolean);
    begin
    FReportNameVisible:= Value;
    if FReportName<>nil then
    FReportName.Visible := Value;
    end;


    function TrrRbFooter.GetReportSourceVisible:Boolean;
    begin
    Result := FReportSource.Visible;
    FReportSourceVisible :=FReportSource.Visible;
    end;


    Procedure TrrRbFooter.SetReportSourceVisible(Value:Boolean);
    begin
    FReportSourceVisible:= Value;
    if FReportSource<>nil then
    FReportSource.Visible := Value;
    end;

    function TrrRbFooter.GetDatetimeVisible:Boolean;
    begin
    Result := FDateTime.Visible;
    FDateTimeVisible :=FDateTime.Visible;
    end;
    Procedure TrrRbFooter.SetDateTimeVisible(Value:Boolean);
    begin
    FDateTimeVisible:= Value;
    if FDateTime<>nil then
    FDateTime.Visible := Value;
    end;

    function TrrRbFooter.GetReportNameVarType:TppVarType;
    begin
    { if FreportName<>nil then
    FReportNameVarType := FReportName.VarType;
    Result := FReportNameVarType; }
    end;

    procedure TrrRbFooter.SetReportNameVarType(Value:TppVarType);
    begin
    { FReportNameVarType := Value;
    if FReportName<>nil then
    FReportName.VarType := FReportNameVarType; }
    end;

    function TrrRbFooter.GetPageNoVarType:TppVarType;
    begin
    if FpageNo<>nil then
    FpagenoVarType := FPageNo.VarType;
    Result := FpageNoVarType;
    end;

    procedure TrrRbFooter.SetPageNoVarType(Value:TppVarType);
    begin
    FPageNoVarType := Value;
    if FPageNo<>nil then
    FPageNo.VarType := FPageNoVarType;
    end;


    function TrrRbFooter.GetDateTimeVarType:TppVarType;
    begin
    if FDateTime<>nil then
    FDateTimeVarType := FDateTime.VarType;
    Result := FDateTimeVarType;
    end;

    procedure TrrRbFooter.SetDateTimeVarType(Value:TppVarType);
    begin
    FDateTimeVarType := Value;
    if FDateTime<>nil then
    FDateTime.VarType := FDateTimeVarType;
    end;

    function TrrRbFooter.GetReportScCaption:String;
    begin
    if FReportSource<>nil then
    FReportSourceCaption := FReportSource.Caption
    else
    FReportSourceCaption :='';
    Result := FReportSourceCaption ;
    end;
    Procedure TrrRbFooter.SetReportScCaption(Value:String);
    begin
    FReportSourcecaption := Value;
    if FReportSource<>nil then
    begin
    FReportSource.Caption := Value;
    SetobjPosition(FReportsource);
    end;
    end;

    procedure TrrRbFooter.AfterDesignerCreate;
    begin
    inherited;
    InitMmbObjs;
    end;

    initialization
    { We register this component so that it will appear in its own toolbar in
    the
    ReportDesigner }
    ppRegisterComponent(TrrRbFooter, 'Standard Components', 11, 0,
    'rrRbFooter', HInstance);


    finalization
    ppUnRegisterComponent(TrrRbFooter);


    end.

    //END OF COMPONENT------------------------------------------------------















  • edited January 2006

    1. Child object destruction

    The child objects will be destroyed for you. In the Destroy method, remove
    all of the existing code. Typically the most appropriate way to handle child
    object references is to override the Notification method - which is part of
    the Delphi VCL Component architecture. This method will be called as each
    child object is free'd and you can write code to nil out the appropriate
    child object reference.

    2. Child object Loading

    The child objects will be read from the object stream and added to the
    Region.Objects[] array. Add an override for the Loaded method. After the
    report definition is read from the stream, the Delphi streaming system calls
    Loaded on each component. In the Loaded method you need to resolve the
    references your component's child objects. Here is some code that will
    accomplish this.



    procedure TrrRbFooter.Loaded;
    var
    lsUserName: String;
    liIndex: Integer;
    begin

    inherited;

    for liIndex := 0 to ChildCount-1 do
    begin
    lsUserName := Objects[liIndex].UserName;

    if (lsUsername = 'ReportName') then
    FReportName := TppSystemVariable(Objects[liIndex])
    else if (lsUsername = 'PageNo') then
    FPageNo := TppSystemVariable(Objects[liIndex])

    else if (lsUsername = 'Reportsource') then
    FReportsource := TppLabel(Objects[liIndex])

    else if (lsUsername = 'DateTime') then
    FDateTime := TppSystemVariable(Objects[liIndex]);

    end;

    end;









    Best regards,

    Nard Moseley
    Digital Metaphors
    www.digital-metaphors.com
  • edited January 2006
    Hi,

    I delete the destructor Destroy and add Loaded override (I copy the exact
    code as yours). The access violation still comes up. I put a break point in
    Loaded. It stops when I switch from Preview to Design. But the ChildCount is
    0. I guess that's why I got access voilation.

    Maybe somehow all the children of this region are not saved in the stream.
    So when it tries to recover the region from the stream, the children are
    gone.

    Anyway, the problem still exists. How can I fix that?

    Bin

  • edited January 2006

    Try using ObjectCount rather than ChildCount -. my mistake.

    I made a simple test case here using ObjectCount and it works properly -
    references are restored.




    Best regards,

    Nard Moseley
    Digital Metaphors
    www.digital-metaphors.com
This discussion has been closed.