Table of Contents

22.11.3 Report project - Draft 2

22.11.3.1 Class ReportVBox

This class presents a report container that arranges its inserted controls vertically.

To create a new ReportVBox object in the source code:

Dim hReportVBox As ReportVBox
hReportVBox = New ReportVBox ([ Parent As ReportContainer ])

You can then define the main style properties of the container.

Note: For many projects, it makes sense to specify the basic layout of the report in the IDE in the Report Designer. ReportVBoxes are part of the basic layout because in many cases they contain additional report control elements, which can then be inserted very easily via source code. This approach has proven itself in practice:

B1

Figure 22.11.3.1.1: Report structure

The report structure shown in Figure 22.11.3.1.1 consists of a header as ReportPanel with two ReportLabels, the content area highlighted in yellow as ReportVBox and a footer as ReportPanel in section 1.

22.11.3.2 Class ReportHBox

This class presents a report container that arranges its inserted controls horizontally.

To create a new ReportHBox object:

Dim hReportHBox As ReportHBox
hReportHBox = New ReportHBox ( [ Parent As ReportContainer ] )

You can then define the main style properties of the ReportHBox container.

Example - the data basis is a database table

  If hDBResult.Available Then
 
    For i = 0 To hDBResult.Max
       rhboxDBRow = New ReportHBox(rvboxContent)
       rhboxDBRow.Spacing = "3mm"
       rhboxDBRow.Height = "6mm"
       rhboxDBRow.Padding = ReportPadding["1mm"]
 
       If iMod = 0 Then
          If i Mod 2 = 0 Then rhboxDBRow.BackGround = ReportBrush["#E0E0E0"]
       Else
          If i Mod 2 <> 0 Then rhboxDBRow.BackGround = ReportBrush["#E0E0E0"]
       Endif
   '-- SurName
       rlblDBField = New ReportLabel(rhboxDBRow)
       rlblDBField.Font = hDBFieldFont
       rlblDBField.Width = cMaxField["vorname"]
       rlblDBField.Text = hDBResult["vorname"]
       ...
   '-- EMail
       rlblDBField = New ReportLabel(rhboxDBRow)
       rlblDBField.Font = hDBFieldFont
       rlblDBField.Width = cMaxField["email"]
       rlblDBField.Text = hDBResult["email"]
       ...
       hDBResult.MoveNext()
     Next
  Endif

This is what a report section with alternating background for the data record looks like in the preview:

B2

Figure 22.11.3.2.1: Section with three report HBoxes, each with nine ReportLabels

22.11.3.3 ReportPanel

This class is a report container that arranges its inserted controls depending on the assigned value of the ‘Arrangement’ property. The default is ReportPanel.Arrangement = Arrange.None. However, the ‘Arrangement’ property can also have the values Arrange.Horizontal or Arrange.Vertical.

How to create a new ReportPanel object:

Dim hReportPanel As ReportPanel
hReportPanel = New ReportPanel ( [ Parent As ReportContainer ] )

You can then define the main style properties of the container as in the following example:

Public Sub Report_Open()
 
    Report1.Padding = ReportPadding["10mm"]
    Report1.Spacing = "20mm"
'-- Arrangement of 4 report labels one below the other
    ReportPanel1.Arrangement = Arrange.Vertical
    ReportPanel1.Spacing = "3mm"
    ReportPanel1.Padding = ReportPadding["Top:1mm;Bottom:1mm"]
    ReportPanel1.Height = "35mm" ' = (1+6+3+6+3+6+3+6+1)mm
'-- Arrangement of 4 report labels next to each other
    ReportPanel2.Arrangement = Arrange.Horizontal
    ReportPanel2.Spacing = "3mm"
    ReportPanel2.Padding = ReportPadding["Top:1mm;Bottom:1mm"]
    ReportPanel2.Height = "8mm"
    ReportPanel2.Width = "45mm" ' =((210-2*10)-4*45)/3
'-- Utilisation of the effective width of 190 mm on the ReportPanel2 for a flush finish
    ReportLabel8.Expand = True
...
End

Example:

B3

Figure 22.11.3.3.1: Two report panels with 4 ReportLabels each in the Report Designer(!) in the IDE

Attention: Only after rendering do you see the real arrangement of the 8 report labels - either vertically (1-4) or horizontally (5-8) - in a preview:

B4

Figure 22.11.3.3.2: Two report panels with 4 report labels each

22.11.3.4 ReportLine

This class reimplements the ‘Line’ class in the gb.qt5 component.

You define the line style using one of the following 6 line constants, whereby no line is drawn for the Line.None constant:

Example:

Public Sub Report_Open()
 
    Dim rLine As ReportLine
 
    Report1.Padding = ReportPadding["15mm"]
    rLineTop = New ReportLine (Report1)
 
'-- TopLeft;Top;TopRight;Left;Right;BottomLeft;Bottom;BottomRight
    rLineTop.Direction = Align.Right
'-- None;Solid;Dash;Dot;DashDot;DashDotDot
    rLineTop.LineStyle = Line.Dot
'-- "m";"cm";"mm";"in";"pt";"px"
    rLineTop.LineWidth = "0.3mm"
    rLineTop.Brush = ReportBrush["#0000FF"]
'-- Die Linie soll auf allen Seiten gezeichnet werden
    rLineTop.Fixed = True
 
End

Attention: If you insert another line into the report, this line will adopt the line style of the first line defined! This also happens if you define its style separately. Obviously, this separate definition is ignored (as of 30 April 2023)!

It is worth considering setting a horizontal boundary with an upper and/or lower border of a report container instead of inserting a line into the report. The following is an example of using the container properties Border and Padding instead of a separate line.

'-- Inserting page numbers with format on each side
    rlblPageFooter.Fixed = True
'-- Definition of selected properties
    rlblPageFooter.Border = ReportBorder["Top:0.1mm"]
    rlblPageFooter.Padding = ReportPadding["Top:1.3mm"]
    rlblPageFooter.Font = Font["Noto Sans,8"]
    rlblPageFooter.Height = GetTextHight(rlblPageFooter.Font)
    rlblPageFooter.Text = ("=\"Seite \" & (page) & \" von \" & (pages)")
    rlblPageFooter.Alignment = Align.Right

22.11.3.5 Inserting new pages into a report

If you set the Report.ForceNewPage property to True, you force the creation of a new report page - but only if the content of a container does not fit completely on the page. In contrast, you can use the ReportPageBreak class, in which a new object forces a page break after a report control, which must be specified as a parameter.

Example

If you always want to start individual sections of a chapter on a new page for a text report, then you must insert a self-defined markup string such as <NewPage> in the original text and parse the text after it in order to create a new ReportPageBreak object.

Here is an excerpt from a text for a text report:

...
13
Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.

<NewPage>25.2.4 Mirum est notare quam littera gothica

14
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem.
...

With regard to the markup string, you could, for example, insert <NewPage> or 2 dollar signs or other characters of your choice into the raw text and then parse the text when generating the structure of the report. Another idea would be equal signs as in the syntax of DokuWiki or # characters for MarkDown. You could also use the number of characters to control the text levels.

    Dim rtlblText As ReportTextLabel
    Dim iHeight, iUsableWidth As Integer
    Dim sRawText As String
    Dim aLines As String[]
    Dim sLine As String
    Dim hTextFont As Font
    Dim rPageBreak As ReportPageBreak
 
'-- Loading Text
    sRawText = File.Load("./data/lorem.txt")
    aLines = Split(sRawText, "\n")
    hTextFont = Font["Noto Sans,11"]
    iHeight = hTextFont.H
    iUsableWidth = GetUsableWidth()
 
'-- Inserting text with many paragraphs
    For Each sLine In aLines
 
  '-- Each new chapter starts on a new page
  '-- Individual NewPage-Markup is <NewPage> in sRawText
      If Left(sLine, 9) = "<NewPage>" Then
         rPageBreak = New ReportPageBreak(rvboxText)
         sLine = Replace(sLine, "<NewPage>", "")
      Endif
 
      rtlblText = New ReportTextLabel(rvboxText)
      rtlblText.Font = hTextFont
      rtlblText.Alignment = Align.Justify
      rtlblText.Text = sLine
      rtlblText.Height = CStr(GetNumberOfRows(sLine, iUsableWidth, hTextFont) * iHeight) & "px"
 
    Next

22.11.3.6 Text in a report

Text in a report places special demands on the correct formatting of the text to be displayed in a ReportLabel or in a ReportTextLabel. Important to know: ReportTextLabels are the only labels that implement an (automatic) text wrap, which normal ReportLabels do not have.

The following scenarios occur:

With a headline consisting of a few words, you only need to make sure that the font is chosen so that the text fits on one line and matches the design.

If, on the other hand, you want to display data from a database table in a table in the report, you must ensure that a cell in your table - represented by a report label - is at least as long as the maximum length of the selected field. Of course, you must also take into account the font that you use for the report for the ReportLabel. You can use the following functions, which differ for SQLite, MySQL and PostgreSQL.

22.11.3.6.1 SQLite

Use the following SQL statement to determine the maximum field length in a column (field_name) of an SQLite table (table_name):

SELECT max(length(field_name)) FROM table_name;

A special feature of SQLite is the determination of the maximum length for a date. Internally, a date is always saved as DateTime such as 08.12.1981 00: 00: 00 with an empty value for the time. In the procedure SetMaxTextWidth(argFont As Font) specified below, this special feature is taken into account.

22.11.3.6.2 MySQL

The following SQL statement shows how to determine the maximum field length in a column of a MySQL table:

SELECT max( length( `field_name` ) ) AS `max_len` FROM `table_name`

This extended SQL statement for MySQL outputs the field content with the longest content:

SELECT field_name
FROM table_name
WHERE length(`field_name`) = SELECT max(length(`field_name`)) FROM table_name

22.11.3.6.3 PostgreSQL

For PostgreSQL, use this SQL statement to determine the maximum field length:

SELECT max(char_length(field_name)) FROM table_name

With this extended SQL statement for PostgreSQL, the field content with the longest content is output:

SELECT field_name
FROM table_name
WHERE char_length(field_name) = SELECT max(char_length(field_name)) FROM table_name

Notes

You must then permanently check whether the table header and all fields of the (DB) table are displayed for the font used. If, for example, only 7 ReportLabels fit into the container for a font used - in most cases this is a ReportVBox - then an existing 8th ReportLabel is simply cut off; it is simply no longer displayed in the container! Therefore, start with a very small font size, which you can gradually increase after a successful check using the Report.Preview() method.

Example: Calculation of the optimum width for each DB field (SQLite) in a report

Use the procedure SetMaxTextWidth(argFont As Font) to calculate the optimum width for each field ReportLabel for SQLite once, as a collection is created with the field names as the key and the maximum field length+3 as the value.

Private cMaxField As Collection
Private sTablename As String = "contacts"
...
Public Sub SetMaxTextWidth(argFont As Font)
 
    Dim hDBResultMax As Result
    Dim vValue As Variant
 
    For Each vValue In cMaxField
'-- Special editing of the date in SQLite3
'-- Example of the stored date in the DB table: 08.12.1981 00: 00: 00
      If cMaxField.Key = "gebdatum" Then
         sSQLStatement = Subst("Select date(&1), Max(Length(&1)) From &2", cMaxField.Key, DB.Quote(sTablename, True))
         hDBResultMax = DBCS.DBConnection.Exec(sSQLStatement)
         cMaxField[cMaxField.Key] = CStr(argFont.TextWidth(hDBResultMax[0]) + 3) & "px"
      Else
         sSQLStatement = Subst("Select &1, Max(Length(&1)) From &2", cMaxField.Key, DB.Quote(sTablename, True))
         hDBResultMax = DBCS.DBConnection.Exec(sSQLStatement)
         cMaxField[cMaxField.Key] = CStr(argFont.TextWidth(hDBResultMax[0]) + 3) & "px"
      Endif
    Next
 
'-- To check:
  ' For Each vValue In cMaxField
  '   Print cMaxField.Key; " -> "; vValue
  ' Next
 
End

Whether you increase the maximum size by 3 pixels - you can freely define the value within certain limits - or work with suitable values for padding (left/right) for the ReportLabel is up to you.

22.11.3.7 Inserting page numbers

You can also insert page numbers in a footer that is to be displayed on certain or all pages of a report. It is informative to specify the current page number with a reference to the number of all pages of the report, such as ‘Page 3 of 6’. Please note that it is usual not to add a footer to an (optional) cover page.

When inserting page numbers, it depends on whether you insert the number of pages in the IDE in the properties in the corresponding report label in the footer or declare it in the source text, as the syntax is different.

Specifying page numbers in the Report Designer (IDE) for the text property of a report label in the footer:

="Page " & page & " von " & pages

Input in the source text in a ReportLabel ‘rlblPageFooter’:

'-- Inserting page numbers with format.
    rlblPageFooter.Fixed = True
    rlblPageFooter.Border = ReportBorder["Top:1px"]
    rlblPageFooter.Padding = ReportPadding["Top:3mm"]
    rlblPageFooter.Font = Font["Noto Sans,8"]
    rlblPageFooter.Height = GetTextHight(rlblPageFooter.Font)
    rlblPageFooter.Text = ("=\"Page \" & (page) & \" von \" & (pages)")
    rlblPageFooter.Alignment = Align.Left

If you are using a cover page, change the definition as follows

'-- Inserting page numbers with format. The cover sheet is NOT counted.
    rlblPageFooter.Text = ("=\"Page \" & (page -1) & \" von \" & (pages -1)")

22.11.3.8 Debugging

You can check the selected layout and design of the report at any time via a report preview or a printout in order to make corrections. Temporarily setting the Report.Debug property to True can be a quick help here, because in this mode the borders and padding of all objects contained in the report are drawn in colour in the preview.

'-- Description of the structure of the report
Public Sub Report_Open()
    ...
'-- Only for controls in the testing of the report project
    Report.Debug = True
    ...

22.11.3.9 Notes on the report methods Refresh() and Layout()

The Report.Refresh() method deletes all internal calculations and the current layout. You start the calculation of the layout with the Report.Layout() method. It can be called manually before printing, for example. But sometimes it is useful to calculate the layout in advance - for example for the preview. Both methods can be followed by rendering the report, which can be done on any medium (preview or printer) and in any resolution.

In many cases, it is necessary to print the generated report. Various report methods are available for this purpose:

22.11.3.10.1 Report.Print()

The Report.Print() method immediately starts a dialogue without a report preview, in which you can set up the selected printer before printing and then print it:

BILD

Figure 22.11.3.10.1: Print dialogue

22.11.3.10.2 Report.Prieview()

The Report.Preview() method is efficient. On the one hand, it allows you to check the report before printing and, on the other hand, offers output to the (standard) printer (Printer tab) with the option of specifying certain printer properties and, among other things, the print format (landscape or landscape format).

Alternatively, you can also print the report to a PDF file. Select the ‘File’ tab and start a dialogue to define the path and file name of the PDF file using the button box on the right-hand side of the line. Click on the Print button to start printing the report to the specified PDF file. This is great because you can then distribute this report file electronically, for example by email.

BILD

Figure 22.11.3.10.2: Printout in a PDF file (A4 landscape format)

To start a preview directly in the IDE. To do this, right-click on the ‘Report’ in the project directory and then select ‘Execute class’ in the context menu.

22.11.3.10.3 Report.Print(hPrinter)

The Report.Print(hPrinter) method starts the printout immediately, as you have already created a printer object in the source code and provided it with suitable properties. This method is ideal if you always print with the same (standard) printer - without a preview and without a printer dialogue.

    Dim hPrinter As Printer
 
    hPrinter = New Printer
 
'-- Definition of selected printer properties
    hPrinter.Paper = Printer.A4
    hPrinter.Orientation = Printer.Landscape
    hPrinter.GrayScale = True
    hPrinter.Duplex = Printer.Vertical
 
    Inc Application.Busy     		'-- The programme no longer accepts any input ...
      Report1.Print(hPrinter)   	'-- Printing is started
    Dec Application.Busy     		'-- The programme is accepting entries again...