tiOPF |
Free, Open Source Object Persistence Framework for Free Pascal & Delphi |
This chapter was provided by Andrew Denton adenton@q-range.com after a discussion on the mailing list about reporting techniques and the tiOPF.
The following code is taken from a base form in one of my applications that utilises printing a tiListViewPlus (a normal tiListView should work fine also) using Report Builder. Create a form with the following components: -
A tiListViewPlus (named lvTrans)
A TppReport (named rptBase)
A TppJITPipeline (named plData)
Set the ReportUnits property of the report to be utMillimeters – this is important for the field placement later on. You will also need to attach your tiListView to a TPerObjList descendant. I'm assuming that everyone knows how to do this! Next, create two Private methods in your form declaration as follows: -
Function plCheckEOF : Boolean; Function plGetFieldValue(aFieldName : String) : Variant;
These are the required event handlers for the pipeline that we need to tell it how to provide the data. Hit CTRL+SHIFT+C for code completion - we will come to these in detail shortly, but we'll leave them blank for now. We also need some kind of UI component to enable the user to request a report, I generally use an action list, but to keep things simple we’ll settle for dropping a button on the form. Name the button btnPrint and double-click it to bring up its OnClick event handler. Now comes the coding part! Make your event handler look like this:
Procedure TfrmTransBase.btnPrintClick(Sender : TObject); Const FieldSpacing = 10; Var I, OldPos : Integer; MyType : TppDataType; CurXPos : Integer; Function GetReportFieldSize(FieldType : TppDataType) : Integer; Begin Result := 5; Case FieldType Of dtDateTime : Result := 100; dtInteger : Result := 50; dtString : Result := 120; dtCurrency : Result := 60; End; { Case } End; Procedure AddReportField(Const pFieldName : String; FieldNo : Integer; FieldType : TppDataType); Var lblField1 : TppDBText; Begin lblField1 := TppDBText.Create(Self); lblField1.Band := rptBase.DetailBand; lblField1.spLeft := CurXPos; lblField1.spTop := 2; lblField1.DataPipeline := plData; lblField1.AutoSize := False; lblField1.DataField := pFieldName; lblField1.spWidth := GetReportFieldSize(FieldType); If FieldType In [dtCurrency, dtInteger] Then lblField1.Alignment := taRightJustify; CurXPos := CurXPos + lblField1.spWidth + FieldSpacing; End; Procedure AddColumnHeading(Const pHeading : String; FieldNo : Integer; FieldType : TppDataType); Var lblHeading1 : TppLabel; Begin lblHeading1 := TppLabel.Create(Self); lblHeading1.Band := rptBase.HeaderBand; lblHeading1.spLeft := CurXPos; lblHeading1.spTop := 5; lblHeading1.AutoSize := False; lblHeading1.Caption := pHeading; lblHeading1.Font.Style := [fsBold]; lblHeading1.spWidth := GetReportFieldSize(FieldType); If FieldType In [dtCurrency, dtInteger] Then lblHeading1.Alignment := taRightJustify; End; Begin // Let's create a report. // First we need to interrogate the list view for it's columns so we can // create the fields and add the fields to the report CurXPos := 2; OldPos := lvTrans.SelectedIndex; // Remember where we were in the list. For I := 0 To lvTrans.ListColumns.Count - 1 Do Begin With lvTrans.ListColumns.Items[I] Do Begin MyType := dtString; Case DataType Of lvtkString : MyType := dtString; lvtkFloat : MyType := dtCurrency; lvtkDateTime : MyType := dtDateTime; lvtkInt : MyType := dtInteger; End; { Case } AddColumnHeading(DisplayLabel, I, MyType); // 25 is a temporary hack value for now. plData.DefineField(FieldName, MyType, 25); AddReportField(FieldName, I, MyType); End; End; { Loop } // Now we need to hookup the event handlers to the JIT Pipeline to provide // access to the underlying data. plData.OnCheckEOF := plCheckEOF; plData.OnGetFieldValue := plGetFieldValue; // Set the report's title. lblReportTitle.Caption := 'Q-Bar Quick Report - ' + Caption; // Prevent annoying listview scrolling when preparing report. LockWindowUpdate(Self.Handle); // Now all that's done we print the report. Try rptBase.PrintReport; Finally LockWindowUpdate(0); // Now we're good to go. End; If (OldPos <> -1) Then lvTrans.PositionCursor(OldPos) Else lvTrans.PositionCursor(0); // Reset the list view's highlight bar. End;
Now we’ve coded the main printing routine we need to write the code for the event handlers, which looks something like this:
Function TfrmTransBase.plCheckEOF : Boolean; Begin Result := (plData.RecordIndex >= lvTrans.Items.Count); End; Function TfrmTransBase.plGetFieldValue(aFieldName : String) : Variant; Var lListColumn : TtiListColumn; lColID : Integer; Begin // Need to obtain the value of the property aFieldName from the current // list item. Using PositionCursor isn't ideal but AFAIK it's the only way of // accessing the underlying Object. lvTrans.PositionCursor(plData.RecordIndex); If (lvTrans.SelectedData <> Nil) Then Begin lListColumn := lvTrans.ListColumns.FindByFieldName(aFieldName); lColID := lListColumn.ID; If lListColumn.Derived Then Result := lvTrans.Selected.SubItems.Strings[lColID] Else // The magic of RTTI !!! :) Result := GetPropValue(lvTrans.SelectedData, aFieldName) End Else Result := 'Error'; End;
Compile and run your test project and you should now be able to print a report based on the view presented by a tiListView or tiListViewPlus. Couple this with Form Inheritance and you have all your basic reporting functionality already written. Of course, the routines described here could be improved on - most noticeable is the lack of any kind of totalling on the final report, but it should give you an insight into what can be achieved with these two packages.