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.