By | May 15, 2020

Printing the invoice total

Quite a while ago I was working with Digital Metaphors Report Builder with Delphi. In this instance I had to generate invoices. The report needed to be able to generate invoices for a single invoice or a range of invoices. To achieve this the report had a group on the invoice number, putting a page break after each group footer. But the invoice total had to appear on the bottom of the page. This means the group footer had to print on the bottom of the page. So how to get Report Builder to print the group footer at the bottom of the page in RAP

The solution to get Report Builder To Print Group Footer At the Bottom of the Page in RAP

I had found some instructions in the rbWiki to do that using a Delphi code type report. But he report was embedded into the delphi form and the logic done in the delphi code. But the way I was working with 100 or so reports was with separate report files. 90% of the logic is using the Digital Metaphors RAP language. My own report designer application allows me to build and edit the reports. I needed to do the same thing as in the instructions I had found but in RAP only. I couldn’t find anything but except some pointers which led me to the solution. Extend the RAP.

Extend the RAP

The answer was to add extra functions to the RAP language. D.M. provide the means to extend their language. Information on how to do that is provided in the report builder RAP help file and the D.M. rbWiki. Essentially you follow their instructions and put the unit(s) where put your extensions in the uses clause of the unit where you have your have and load your TppReport component either on a form/data module or you create it via code. I posted an overview of my solution in a digital metaphors post which you can find at http://www.digital-metaphors.com/forums/discussion/2390/previous-incomplete-putting-group-footer-on-page-bottom-through-rap

Let’s elaborate on this.

Firstly we need a to create a ppFromMMThousandths function that RAP can use. This will enable us to convert the Report.Engine.PageBottom mmThousandths to the units of the report. So in a new unit we create a class inheriting from TRptUtilityFunction that inherits from TraSystemFunction. I will give the full code to everything in the article later but the declaration of this is:

  TppFromMMThousandthsFunction = class(TRptUtilityFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
    class function IsFunction : boolean; override;
  end;

Secondly we need to expose the PageBottom property to the RAP Report Engine class. We do that by extending the report engine RAP class with the following declaration:

  TppCustomEngineRTTI = class(TraTppCacheableRTTI)
  public
    class procedure GetPropList(aClass: TClass; aPropList: TraPropList); override;
    class function  GetPropRec(aClass: TClass; const aPropName: String; var aPropRec: TraPropRec): Boolean; override;
    class function  GetParams(const aMethodName: String): TraParamList; override;
    class function  CallMethod(aObject: TObject; const aMethodName: String; aParams: TraParamList; aGet: Boolean): Boolean; override;
    class function  RefClass: TClass; override;
    class function GetPropValue(aObject: TObject; const aPropName: String; var aValue): Boolean; override;
    class function SetPropValue(aObject: TObject; const aPropName: String; var aValue): Boolean; override;
  end;

Thirdly we add in our report an event handler on the GroupFooter.OnBeforePrint event to set the group footer position. This is the final step to get Report Builder To Print Group Footer At the Bottom of the Page in RAP.

 var  
   lPageBottom: Single;  
 begin  
   Footer.Visible := False;  
  {Get the bottom of the page from the report engine. This value is adjusted 
   for the footer and bottom margin. Convert the units from mmThousandths to 
   Report.Units}  
  lPageBottom := ppFromMMThousandths(Report.Engine.PageBottom, Report.Units, False, nil); 
   {Set the group footer band print position to align it to the bottom of the page}  
  GroupFooterBand1.PrintPosition := lPageBottom - GroupFooterBand1.Height - 0.01; 
end;

So let’s put steps 1 and 2 together and the implementations into one unit. I called it RapSupport. I have also added one or two other useful utilities functions. You may find these useful as well. As I mentioned earlier you must add RapSupport to the uses clause of the unit you are using the TppReport.

unit RapSupport;

interface
uses
  ppRTTI, ppChrt, Series,
  TeEngine, Chart, raFunc, ppCache, ppRelatv, ppProd, ppUtils, graphics,
  ppTypes, teCanvas, ppDB, ppDBPipe;
type
  TMyStringFunction = class(TraStringFunction)
  published
    class function Category: String; override;
  end;

  TMyMathFunction = class(TraSystemFunction)
  published
    class function Category: string; override;
  end;

  TRptUtilityFunction = class(TraSystemFunction)
  published
    class function Category: string; override;
  end;

  TppFromMMThousandthsFunction = class(TRptUtilityFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
    class function IsFunction : boolean; override;
  end;

  TMakeAddressFunction = class(TMYStringFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
  end;

  TLongMonthNamesFunction = class(TMyStringFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
  end;

  TShortMonthNamesFunction = class(TMyStringFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
  end;

  TLPadStringFunction = class(TMyStringFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
  end;

  TAbsFunction = class(TMyMathFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
    class function IsFunction : boolean; override;
  end;


  TppCustomEngineRTTI = class(TraTppCacheableRTTI)
  public
    class procedure GetPropList(aClass: TClass; aPropList: TraPropList); override;
    class function  GetPropRec(aClass: TClass; const aPropName: String; var aPropRec: TraPropRec): Boolean; override;
    class function  GetParams(const aMethodName: String): TraParamList; override;
    class function  CallMethod(aObject: TObject; const aMethodName: String; aParams: TraParamList; aGet: Boolean): Boolean; override;
    class function  RefClass: TClass; override;
    class function GetPropValue(aObject: TObject; const aPropName: String; var aValue): Boolean; override;
    class function SetPropValue(aObject: TObject; const aPropName: String; var aValue): Boolean; override;
  end;

implementation

uses
  SysUtils, Variants, Classes, ppClass, JvAppCommand, Windows;


{ TMakeAddressFunction }

class function TMYStringFunction.Category: String;
begin
  Result := 'MY Stuff';
end;

procedure TMakeAddressFunction.ExecuteFunction(aParams: TraParamList);
var
  Line1,
  Line2,
  Line3,
  Line4,
  Line5,
  City,
  State,
  PostCode : string;
  ReturnStrings : TStrings;
  Line6 : string;
  ReturnResult : string;
begin
  ReturnStrings := TStringList.Create;
  try
    GetParamValue(0,Line1);
    GetParamValue(1,Line2);
    GetParamValue(2,Line3);
    GetParamValue(3,Line4);
    GetParamValue(4,Line5);
    GetParamValue(5,City);
    GetParamValue(6,State);
    GetParamValue(7,PostCode);
    ReturnStrings.Clear;
    if Line1 <> '' then
      ReturnStrings.Add(Line1);
    if Line2 <> '' then
      ReturnStrings.Add(Line2);
    if Line3 <> '' then
      ReturnStrings.Add(Line3);
    if Line4 <> '' then
      ReturnStrings.Add(Line4);
    if Line5 <> '' then
      ReturnStrings.add(Line5);
    if City <> '' then
      ReturnStrings.Add(City);

    {now lets determine what the last line is as long as there is more than
      1 line}
    if ReturnStrings.Count <= 1 then
      Line6 := ''
    else
    begin
      Line6 := ReturnStrings.Strings[ReturnStrings.Count-1];
      ReturnStrings.Delete(ReturnStrings.Count - 1);
    end;

    if State <> '' then
    begin
      if Line6 <> '' then
        Line6 := Line6 + ' ';
      Line6 := Line6 + State;
    end;
    if PostCode <> ''  then
    begin
      if Line6 <> '' then
        Line6 := Line6 + ' ';
      Line6 := Line6 + PostCode;
    end;

    if Line6 <> '' then
      ReturnStrings.Add(Line6);

    ReturnResult := ReturnStrings.Text;
    SetParamValue(8,ReturnResult);
  finally
    ReturnStrings.Free;
  end;
end;

class function TMakeAddressFunction.GetSignature: String;
begin
  Result := 'function MakeSqueezedAddress(Line1 : string;'+
                                          'Line2 : string;'+
                                          'Line3 : string;'+
                                          'Line4 : string;'+
                                          'Line5 : string;'+
                                          'City : string;'+
                                          'State : string;'+
                                          'PostCode : string) : string;';
end;

class function TMakeAddressFunction.HasParams: Boolean;
begin
  Result := True;
end;

{ TLongMonthNamesFunction }

procedure TLongMonthNamesFunction.ExecuteFunction(aParams: TraParamList);
var
  s : string;
  m : integer;
  fs : TFormatSettings;
begin
  GetParamValue(0,m);
  GetLocaleFormatSettings(GetThreadLocale,fs);
  s := fs.LongMonthNames[m];
  SetParamValue(1,s);
end;

class function TLongMonthNamesFunction.GetSignature: String;
begin
  Result := 'function GetLongMonthName(MonthNo : integer) : string;';
end;

class function TLongMonthNamesFunction.HasParams: Boolean;
begin
  Result := True;
end;

{ TShortMonthNamesFunction }

procedure TShortMonthNamesFunction.ExecuteFunction(aParams: TraParamList);
var
  s : string;
  m : integer;
  fs : TFormatSettings;
begin
  GetParamValue(0,m);
  GetLocaleFormatSettings(GetThreadLocale,fs);
  s := fs.ShortMonthNames[m];
  SetParamValue(1,s);
end;

class function TShortMonthNamesFunction.GetSignature: String;
begin
  Result := 'function GetShortMonthName(MonthNo : integer) : string;';
end;

class function TShortMonthNamesFunction.HasParams: Boolean;
begin
  Result := True;
end;

{ TLPadStringFunction }

procedure TLPadStringFunction.ExecuteFunction(aParams: TraParamList);
var
  s : string;
  p : char;
  MaxLen : integer;
  Res : string;
  l : integer;
begin
  GetParamValue(0,s);
  GetParamValue(1,p);
  GetParamValue(2,MaxLen);

  l := Length(s);
  if L < MaxLen then
  begin
    Res := StringOfChar(p,MaxLen-l) +
           s;
  end
  else
  begin
    Res := s;
  end;
  SetParamValue(3,Res);
end;

class function TLPadStringFunction.GetSignature: String;
begin
  Result := 'function LPadString(s : string; PadChar : char; MaxLength : integer) : string;';
end;

class function TLPadStringFunction.HasParams: Boolean;
begin
  Result := True;
end;

{ TMYMathFunction }

class function TMYMathFunction.Category: string;
begin
  Result := 'MYMathStuff';
end;

{ TAbsFunction }

procedure TAbsFunction.ExecuteFunction(aParams: TraParamList);
var
  n : double;
  Res : double;
begin
  GetParamValue(0,n);
  Res := abs(n);

  {abs doesn't work in the report RAP compiler}
//  if n < 0 then
//    Res := -n
//  else
//    Res := n;
  SetParamValue(1,Res);
end;

class function TAbsFunction.GetSignature: String;
begin
  Result := 'function Abs(n : double) : double;';
end;

class function TAbsFunction.HasParams: Boolean;
begin
  Result := True;
end;

class function TAbsFunction.IsFunction: boolean;
begin
  Result := True;
end;

{ TTppCustomEngineRTTI }

class function TppCustomEngineRTTI.CallMethod(aObject: TObject;
  const aMethodName: String; aParams: TraParamList; aGet: Boolean): Boolean;
begin
  Result := inherited CallMethod(aObject, aMethodName, aParams, aGet);
end;

class function TppCustomEngineRTTI.GetParams(
  const aMethodName: String): TraParamList;
begin
  Result := inherited GetParams(aMethodName);
end;

class procedure TppCustomEngineRTTI.GetPropList(aClass: TClass;
  aPropList: TraPropList);
begin
  inherited GetPropList(aClass, aPropList);
  aPropList.AddProp('PageBottom');
end;

class function TppCustomEngineRTTI.GetPropRec(aClass: TClass;
  const aPropName: String; var aPropRec: TraPropRec): Boolean;
begin
  Result := True;
  if ppEqual(aPropName, 'PageBottom') then
    PropToRec(aPropName, daInteger, True, aPropRec)
  else
    Result := inherited GetPropRec(aClass, aPropName, aPropRec);
end;

class function TppCustomEngineRTTI.GetPropValue(aObject: TObject;
  const aPropName: String; var aValue): Boolean;
begin
  Result := True;
  if ppEqual(aPropName, 'PageBottom') then
    Integer(aValue) := TppCustomEngine(aObject).PageBottom
  else
    Result := inherited GetPropValue(aObject, aPropName, aValue);
end;

class function TppCustomEngineRTTI.RefClass: TClass;
begin
  Result := TppCustomEngine;
end;

class function TppCustomEngineRTTI.SetPropValue(aObject: TObject;
  const aPropName: String; var aValue): Boolean;
begin
  Result := inherited SetPropValue(aObject,aPropName, aValue);
end;

{ TRptUtilityFunction }

class function TRptUtilityFunction.Category: string;
begin
  Result := 'Report Utilities';
end;

{ TppFromMMThousandthsFunction }

procedure TppFromMMThousandthsFunction.ExecuteFunction(aParams: TraParamList);
var
  Value : LongInt;
  aUnits : TppUnitType;
  aResolution : TppResolutionType;
  aResolutionHorizontal : boolean;
  aPrinter : TObject;
  Res : Single;
begin
  GetParamValue(0,Value);
  GetParamValue(1,aUnits);
  GetParamValue(2,aResolutionHorizontal);
  GetParamValue(3,aPrinter);

  //now we have all the values we need call the actual function
  if aResolutionHorizontal then
    aResolution := pprtHorizontal
  else
    aResolution := pprtVertical;

  Res := ppFromMMThousandths(Value, aUnits, aResolution, aPrinter);

  SetParamValue(4,Res);
end;

class function TppFromMMThousandthsFunction.GetSignature: String;
begin
  Result := 'function ppFromMMThousandths(Value: Integer; aUnits: TppUnitType;'+
    ' aResolutionHorizontal: boolean; aPrinter: TObject) : single;';
end;

class function TppFromMMThousandthsFunction.HasParams: Boolean;
begin
  Result := True;
end;

class function TppFromMMThousandthsFunction.IsFunction: boolean;
begin
  Result := True;
end;

initialization
  raRegisterRTTI(TppCustomEngineRTTI);

  raRegisterFunction('MakeSqueezedAddress', TMakeAddressFunction);
  raRegisterFunction('GetLongMonthName',TLongMonthNamesFunction);
  raRegisterFunction('GetShortMonthName',TShortMonthNamesFunction);
  raRegisterFunction('LPadString', TLPadStringFunction);
  raRegisterFunction('ppFromMMThousandths' , TppFromMMThousandthsFunction);
  raRegisterFunction('Abs', TAbsFunction);


finalization
  raUnRegisterRTTI(TppCustomEngineRTTI);
  
  raUnRegisterFunction('MakeSqueezedAddress');
  raUnRegisterFunction('GetLongMonthName');
  raUnRegisterFunction('GetShortMonthName');
  raUnRegisterFunction('LPadString');
  raUnRegisterFunction('ppFromMMThousandths' );
  raUnRegisterFunction('Abs');
end.

So there it is. I hope this is helpful to some folks. It is also a good example of extending RAP.