Differenze tra le versioni di "Creare report con il componente gb.report2"

Da Gambas-it.org - Wikipedia.
 
(19 versioni intermedie di un altro utente non mostrate)
Riga 1: Riga 1:
 +
 
== Introduzione ==
 
== Introduzione ==
'''gb.report2''' di ''Fabien Bodard'' è un utilissimo componente che ci permette con pochi passaggi di avere un'anteprima di stampa e di stampare su file o carta ogni sorta di documento da noi creato con Gambas.
+
 
 +
'''gb.report2''' di ''Fabien Bodard'' <SUP>&#091;[[#Note|nota 1]]&#093;</sup> è un utilissimo componente che ci permette con pochi passaggi di avere un'anteprima di stampa e di stampare su file o carta ogni sorta di documento da noi creato con Gambas.
 
Come si intuisce dal nome il componente è stato studiato per creare dei report dalle interrogazioni delle tabelle dei database, ma come vedremo può essere usato per quasi tutti i programmi che necessitano della stampa.
 
Come si intuisce dal nome il componente è stato studiato per creare dei report dalle interrogazioni delle tabelle dei database, ma come vedremo può essere usato per quasi tutti i programmi che necessitano della stampa.
 
Per poter comprendere a fondo come creare un buon report occorre aver ben presente come si creano le finestre dei programmi con Gambas con l'aiuto imprescindibile dei contenitori (''Container'') che qui hanno il prefisso '''Report'''.
 
Per poter comprendere a fondo come creare un buon report occorre aver ben presente come si creano le finestre dei programmi con Gambas con l'aiuto imprescindibile dei contenitori (''Container'') che qui hanno il prefisso '''Report'''.
Riga 15: Riga 17:
 
Ripetiamo l'operazione del clic sul tasto destro per aggiungere anche un '''Modulo''' che rinomineremo ''MBase''.
 
Ripetiamo l'operazione del clic sul tasto destro per aggiungere anche un '''Modulo''' che rinomineremo ''MBase''.
 
Prima di creare il report andiamo ad aggiungere un pulsante alla finestra '''FMain''' e riportiamo questo codice in '''FMain.class''':
 
Prima di creare il report andiamo ad aggiungere un pulsante alla finestra '''FMain''' e riportiamo questo codice in '''FMain.class''':
Property Read sPathDB As String
+
  Public Sub Form_Open()
Private $sPathDB As String
+
 
+
    db.Debug = True
Public Sub Form_Open()
+
    If MBase.CheckDB() Then
+
      Print ("#Unable to create database")
  $sPathDB = User.Home &/ Application.Name
+
      Me.Close
  If Not Exist($sPathDB) Then Mkdir $sPathDB
+
    Endif
  If MBase.CheckDB() Then
+
 
    Print ("#Unable to create database")
+
  End
    Me.Close
+
 
  Endif
+
   Public Sub Button1_Click()
+
 
End
+
    Report1.Preview
+
 
Private Function sPathDB_Read() As String
+
  End
 
   Return $sPathDB
 
 
End
 
 
Public Sub Button1_Click()
 
 
  Report1.Preview
 
 
End
 
 
Diamo uno sguardo a quanto abbiamo appena fatto:<br>
 
Diamo uno sguardo a quanto abbiamo appena fatto:<br>
Per prima cosa abbiamo creato una proprietà di sola lettura per fare in modo che il suo contenuto (''la path'') possa essere letto dagli altri moduli e/o classi, nel caso specifico rendere visibile la path al modulo del database.
+
Per prima cosa l’attivazione di '''db.Debug''', che stampando i passaggi nella console, ci da la possibilità di apprezzare tutto quanto viene effettivamente fatto dietro le quinte da Gambas.<br>
All'apertura della finestra e quindi del progetto stesso, viene creata una directory nella home dell'utente e attraverso un metodo del modulo del database (lo vedremo fra poco) il database stesso.
+
Quindi attraverso il metodo ''CheckDB()'' del modulo del database (lo vedremo fra poco) la creazione del database stesso. Se il metodo ritorna ''true'' vuol dire che è sopraggiunto un errore e il database non si è formato, l'utente in questo caso verrà informato attraverso un messaggio in console.<br> Come si vede la scritta è in inglese e avvolta da parentesi, queste ultime servono per poter tradurre le scritte e creare un progetto internazionale. Per renderlo tale, al momento della creazione del progetto basta spuntare la casella ''Il progetto è traducibile'', se non lo abbiamo fatto basta andare al menu '''Progetto''' > '''Proprietà''' > '''Opzioni''' e ''swithcciare'' su ''Il progetto è traducibile'', nella '''IDE''' appare il nuovo pulsante '''Traduci''' e se ci clicchiamo sopra possiamo tradurre la scritta che in caso di errore apparirà in italiano.<br>
Se il metodo ritorna ''true'' vuol dire che è sopraggiunto un errore e il database non si è formato, l'utente in questo caso verrà informato attraverso un messaggio in console.
 
Come vedete la scritta è in inglese e avvolta da parentesi, queste ultime servono per poter tradurre le scritte e creare un progetto internazionale.
 
Per renderlo tale, al momento della creazione del progetto basta spuntare la casella ''Il progetto è traducibile'', se non lo abbiamo fatto basta andare al menu '''Progetto''' > '''Proprietà''' > '''Opzioni''' e swithcciare su ''Il progetto è traducibile'', nella IDE appare il nuovo pulsante '''Traduci''' e se ci clicchiamo sopra possiamo tradurre la scritta che in caso di errore apparirà in italiano.<br>
 
 
Nell'evento ''Button1_Click()'' c'è il codice che avvia il report.<br>
 
Nell'evento ''Button1_Click()'' c'è il codice che avvia il report.<br>
 
Ora a seguire il codice del modulo del database MBase:
 
Ora a seguire il codice del modulo del database MBase:
  
Private $nRecords As Integer
+
  Private $nRecords As Integer
Private $hConn As New Connection
+
  Private $hConn As New Connection
Private $sNameDB As String = "TestReport.db"
+
  Private $sNameDB As String = "TestReport.db"
Private $sPathDB As String
+
  Private $sPathDB As String = "/tmp"
+
 
Public Sub CheckDB() As Boolean
+
  Public Sub CheckDB() As Boolean
+
 
  $sPathDB = FMain.sPathDB
+
    If Not Exist($sPathDB) Then Return
  If Not Exist($sPathDB) Then Return
+
    With $hConn
  With $hConn
+
      .Close()
    .Close()
+
      .Type = "sqlite3"
    .Type = "sqlite3"
+
      .Host = $sPathDB
    .Host = $sPathDB
+
      .Open()
    .Open()
+
      If .Databases.Exist($sNameDB) Then .Databases.Remove($sNameDB)
    If Not $hConn.Databases.Exist($sNameDB) Then
+
      ' Possiamo cambiare qui il numero dei record
      $nRecords = 100 'FRecords.ReturnRecords()
+
      $nRecords = 100
      'If $nRecords = 0 Then Return True
+
      .Databases.Add($sNameDB)
      .Databases.Add($sNameDB)
+
      .Close()
      .Close()
+
      .Name = $sNameDB
      .Name = $sNameDB
+
      .Open()
      .Open()
+
      MakeTable()
      MakeTable()
+
      FillRecords
      FillRecords
+
      Print ("#Database successfully created!")
      Print ("#Database successfully created!")
+
      .Close()
    Else
+
    End With
      Print ("#Existing database")
+
  Catch
    Endif
+
    Message.Error(("Fatal error in ") & Error.Where & ("\nError number: ") & Error.Code & ("\nCause: ") & Error.Text, "OK")
    .Close()
+
    CloseDB()
  End With
+
    Return True
Catch
+
 
  Message.Error(("Fatal error in ") & Error.Where & ("\nError number: ") & Error.Code & ("\nCause: ") & Error.Text, "OK")
+
  End
  Return True
+
 
+
  Private Sub OpenDB()
End
+
 
+
    With $hConn
Private Sub OpenDB()
+
      .Close()
+
      .Type = "sqlite3"
  With $hConn
+
      .Host = $sPathDB
    .Close()
+
      .Name = $sNameDB
    .Type = "sqlite3"
+
      .Open()
    .Host = $sPathDB
+
    End With
    .Name = $sNameDB
+
  Catch
    .Open()
+
    CloseDB()
  End With
+
    Print Error.Text, Error.Code
+
 
Catch
+
  End
  Print Error.Text, Error.Code
+
 
+
  Private Sub CloseDB()
End
+
 
+
    $hConn.Close
Public Sub CloseDB()
+
 
+
  End
  $hConn.Close
+
 
+
  Private Sub MakeTable()
End
+
 
+
    Dim hTable As Table = $hConn.Tables.Add("tuser")
Public Function ExistConnection() As Boolean
+
 
+
    hTable.Fields.Add("uskey", db.Serial)
  Return $hConn.Opened
+
    hTable.Fields.Add("usnam", db.String)
+
    hTable.Fields.Add("ussur", db.String)
End
+
    hTable.Fields.Add("usdat", db.Date)
+
    hTable.PrimaryKey = ["uskey"]
+
    hTable.Update
Private Sub MakeTable()
+
  Catch
+
    CloseDB()
  Dim sMySql As String
+
    Print Error.Text, Error.Code
+
 
  $hConn.Begin()
+
  End
+
 
  sMySql = "CREATE TABLE 'tuser' ("
+
  Private Sub FillRecords()
  sMySql &= " 'uskey' INTEGER PRIMARY KEY,"
+
 
  sMySql &= " 'usnam' TEXT,"
+
    Dim nCasual, i, y, m, d As Integer
  sMySql &= " 'ussur' TEXT,"
+
    Dim sName, sSurname As String
  sMySql &= " 'usdat' TEXT NOT NULL"
+
    Dim aFirstName As String[] = ["Binah", "Hanna", "Selassie", "Okpara", "Agustina", "Clema""Coyan", "Eusebio", "Diego",
  sMySql &= " );"
+
"Alisha", "Daphne", "Wiley", "Valentine", "Lidwina", "Roxana", "Edmund", "Hildeger", "Adrianne", "Janette", "Quentin",
+
"Sylvain", "Francesco", "Marco", "Aldina", "Ornella", "Carmencita", "Galena", "Florentino", "Rodrigo", "Benoit", "Mario"]
  $hConn.EXEC(sMySql)
+
    Dim aLastName As String[] = ["Smith", "Johnson", "Williams", "Brown", "Martin", "Bernard", "Dubois", "Thomas", "Robert",
+
"Richard", "Müller", "Schmidt", "Schneider", "Fischer", "Weber", "Meyer", "Gunnarsson", "García", "González", "Rodríguez",
  $hConn.Commit()
+
"Fernández", "López", "Martínez", "Jensen", "Nielsen", "Cohen", "Friedman", "Rossi", "Bianchi", "Minisini", "Verdone"]
Catch
+
    Dim hDate As Date
  Print Error.Text, Error.Code
+
    Dim hResult As Result
+
 
End
+
    Randomize
+
    $hConn.Begin()
Private Sub FillUser(iKey As Integer, sName As String, sSurname As String, sDate As String)
+
    For i = 0 To $nRecords
+
      nCasual = Rand(0, 30)
  Dim sMySql As String
+
      sName = aFirstName[nCasual]
+
      nCasual = Rand(0, 30)
  sName = Replace(sName, "'", "''")
+
      sSurname = aLastName[nCasual]
  sSurname = Replace(sSurname, "'", "''")
+
      y = Rand(1959, 2000)
+
      m = Rand(1, 12)
  sMySql = "INSERT INTO tuser ("
+
      d = Rand(1, 28)
  sMySql &= " uskey,"
+
      hDate = Date(y, m, d)
  sMySql &= " usnam,"
+
      hResult = $hConn.Create("tuser")
  sMySql &= " ussur,"
+
      hResult!usnam = sName
  sMySql &= " usdat"
+
      hResult!ussur = sSurname
  sMySql &= " )"
+
      hResult!usdat = hDate
  sMySql &= " VALUES ("
+
      hResult.Update
  sMySql &= " " & iKey & ","
+
    Next
  sMySql &= " '" & sName & "',"
+
    $hConn.Commit()
  sMySql &= " '" & sSurname & "',"
+
  Catch
  sMySql &= " '" & sDate & "'"
+
    CloseDB()
  sMySql &= " );"
+
    Print Error.Text, Error.Code
  $hConn.EXEC(sMySql)
+
 
+
  End
Catch
+
 
  Print Error.Text, Error.Code
+
  Public Function ReturnData() As Result
+
 
End
+
    Dim hResult As Result
+
 
Private Sub FillRecords()
+
    OpenDB()
+
    hResult = $hConn.Find("tuser")
  Dim nCasual, i, nPar, nCon As Integer
+
    If hResult.Available Then
  Dim sName, sSurname, sDate As String
+
      Return hResult
+
    Endif
  Randomize
+
    CloseDB()
  nPar = $nRecords Div 100
 
  nCon = nPar
 
  Wait
 
  OpenDB()
 
  $hConn.Begin()
 
  For i = 0 To $nRecords
 
    nCasual = Rand(1, 31)
 
    sName = firstName(nCasual)
 
    nCasual = Rand(1, 31)
 
    sSurname = lastName(nCasual)
 
    nCasual = Rand(2009, 2018)
 
    sDate = CStr(nCasual) & "-"
 
    nCasual = Rand(1, 12)
 
    sDate &= Format(nCasual, "0#") & "-"
 
    nCasual = Rand(1, 28)
 
    sDate &= Format(nCasual, "0#")
 
    FillUser(i + 1, sName, sSurname, sDate)
 
  Next
 
  $hConn.Commit()
 
  CloseDB()
 
Catch
 
  Print Error.Text, Error.Code
 
 
End
 
 
Private Function firstName(value As Integer) As String
 
 
  Select Case value
 
    Case 1
 
      Return "Binah"
 
    Case 2
 
      Return "Hanna"
 
    Case 3
 
      Return "Selassie"
 
    Case 4
 
      Return "Okpara"
 
    Case 5
 
      Return "Agustina"
 
    Case 6
 
      Return "Clema"
 
    Case 7
 
      Return "Coyan"
 
    Case 8
 
      Return "Eusebio"
 
    Case 9
 
      Return "Diego"
 
    Case 10
 
      Return "Alisha"
 
    Case 11
 
      Return "Daphne"
 
    Case 12
 
      Return "Wiley"
 
    Case 13
 
      Return "Valentine"
 
    Case 14
 
      Return "Lidwina"
 
    Case 15
 
      Return "Roxana"
 
    Case 16
 
      Return "Edmund"
 
    Case 17
 
      Return "Hildeger"
 
    Case 18
 
      Return "Adrianne"
 
    Case 19
 
      Return "Janette"
 
    Case 20
 
      Return "Quentin"
 
    Case 21
 
      Return "Sylvain"
 
    Case 22
 
      Return "Francesco"
 
    Case 23
 
      Return "Marco"
 
    Case 24
 
      Return "Aldina"
 
    Case 25
 
      Return "Ornella"
 
    Case 26
 
      Return "Carmencita"
 
    Case 27
 
      Return "Galena"
 
    Case 28
 
      Return "Florentino"
 
    Case 29
 
      Return "Rodrigo"
 
    Case 30
 
      Return "Benoit"
 
    Case Else
 
      Return "Mario"
 
  End Select
 
 
End
 
 
Private Function lastName(value As Integer) As String
 
 
  Select Case value
 
    Case 1
 
      Return "Smith"
 
    Case 2
 
      Return "Johnson"
 
    Case 3
 
      Return "Williams"
 
    Case 4
 
      Return "Brown"
 
    Case 5
 
      Return "Martin"
 
    Case 6
 
      Return "Bernard"
 
    Case 7
 
      Return "Dubois"
 
    Case 8
 
      Return "Thomas"
 
    Case 9
 
      Return "Robert"
 
    Case 10
 
      Return "Richard"
 
    Case 11
 
      Return "Müller"
 
    Case 12
 
      Return "Schmidt"
 
    Case 13
 
      Return "Schneider"
 
    Case 14
 
      Return "Fischer"
 
    Case 15
 
      Return "Weber"
 
    Case 16
 
      Return "Meyer"
 
    Case 17
 
      Return "Gunnarsson"
 
    Case 18
 
      Return "García"
 
    Case 19
 
      Return "González"
 
    Case 20
 
      Return "Rodríguez"
 
    Case 21
 
      Return "Fernández"
 
    Case 22
 
      Return "López"
 
    Case 23
 
      Return "Martínez"
 
    Case 24
 
      Return "Jensen"
 
    Case 25
 
      Return "Nielsen"
 
    Case 26
 
      Return "Cohen"
 
    Case 27
 
      Return "Friedman"
 
    Case 28
 
      Return "Rossi"
 
    Case 29
 
      Return "Bianchi"
 
    Case 30
 
      Return "Minisini"
 
    Case Else
 
      Return "Verdone"
 
  End Select
 
 
End
 
 
Public Function ReturnData() As Result
 
 
  Dim sMySql As String
 
  Dim hResult As Result
 
 
  If KeyCount() > 1 Then
 
    sMySql = "SELECT *"
 
    sMySql &= " FROM tuser"
 
    sMySql &= ";"
 
    OpenDB()
 
    hResult = $hConn.Exec(sMySql)
 
    If hResult.Available Then
 
      Return hResult
 
    Endif
 
  Endif
 
 
   Catch
 
   Catch
  CloseDB()
+
    CloseDB()
  Print Error.Text, Error.Code
+
    Print Error.Text, Error.Code
+
 
End
+
  End
 
Public Function KeyCount() As Integer
 
 
  Dim sMySql As String
 
  Dim hResult As Result
 
  Dim i As Integer
 
 
  sMySql = "SELECT MAX( uskey )"
 
  sMySql &= " FROM tuser"
 
  sMySql &= ";"
 
 
  OpenDB()
 
  hResult = $hConn.Exec(sMySql)
 
  If hResult.Available Then
 
    If Not IsNull(hResult["0"]) Then
 
      i = CInt(hResult["0"]) + 1
 
    Else
 
      i = 1
 
    Endif
 
  Else
 
    i = -1
 
  Endif
 
  CloseDB()
 
  Return i
 
Catch
 
  CloseDB()
 
  Print Error.Text, Error.Code
 
  Return -1
 
 
End
 
 
Anche qui diamo uno sguardo a quanto abbiamo appena fatto:<br>
 
Anche qui diamo uno sguardo a quanto abbiamo appena fatto:<br>
Prima però una doverosa precisazione: '''SQLite''' è un database particolare non serve installarlo, se avete un Gambas completo di tutti i componenti è già installato nel vostro sistema.<br>
+
Prima però una doverosa precisazione: ''SQLite'' è un database particolare non serve installarlo, se avete un Gambas completo di tutti i componenti è già installato nel vostro sistema.<br>
Le variabili di modulo riguardano in ordine: I record per il riempimento della tabella user, la connessione, il nome del database e la stringa che contiene il percorso (path).<br>
+
Le variabili di modulo riguardano in ordine: i record per il riempimento della tabella user, la connessione, il nome del database e la stringa che contiene il percorso (''path'').<br>
Il primo metodo apre la connessione, crea il database, crea la tabella e la riempe di dati (101 semplici record completi di; numero identificativo univoco (''uskey'') di tipo integer, nome dell'user (''usnam'') testo, cognome (''ussur'') testo, data (''usdat'') testo anch'essa perchè in SQLite non ci sono i concetti di ''Char'' (varchar, character ecc.) e ''Date'' come nei database server/client tipo ''PostgreSQL'', ''MySQL'' ecc.<br>
+
Il primo metodo apre la connessione, crea il database <u>temporaneo</u>, crea la tabella e la riempe di dati (101 semplici record completi di; numero identificativo univoco (''uskey'') di tipo contatore, nome dell'user (''usnam'') testo, cognome (''ussur'') testo, data (''usdat'') data.<br>
Tutti i restanti metodi sono prodromi a questo metodo eccetto la funzione ''ReturnData'' che serve per la creazione del report e che utilizza una semplicissima interrogazione ''SQL'', fa eccezzione anche la funzione ''KeyCount'' utilizzata dalla appena citata funzione.
+
In SQLite non ci sono i concetti di ''Char'' (varchar, character ecc.) e ''Date'' come nei database server/client tipo ''PostgreSQL'', ''MySQL'' ecc. però il componente '''gb.db''' li supporta e pensa lui al corretto trattamento.
 +
Se il database è già presente nella cartella ''tmp'' lo elimina prima di ricrearlo.<br>
 +
Tutti i restanti metodi sono prodromi a questo metodo eccetto la funzione ''ReturnData()'' che come si intuisce dal nome interroga la tabella e ne restituisce tutti i dati.
  
Ricapitoliamo: Abbiamo una finestra principale che quando si apre la prima volta crea un database, essa ha un solo pulsante che richiama il report il quale a sua volta usa un metodo che interroga il database per avere il risultato dei dati da visualizzare in forma comprensibile per l'utente.
+
'''Ricapitoliamo''': Abbiamo una finestra principale che quando si apre la prima volta crea un database, essa ha un solo pulsante che richiama il report il quale a sua volta usa un metodo che interroga il database per avere il risultato dei dati da visualizzare in forma comprensibile per l'utente.
  
Ora finalmente possiamo dedicarci al report.<br>
+
=== Il disegno del report ===
 +
 
 +
Finalmente possiamo dedicarci al report.<br>
 
In questo progetto ne creeremo uno di tipo ''tabellare'' simile a un ''GridView'':<br>
 
In questo progetto ne creeremo uno di tipo ''tabellare'' simile a un ''GridView'':<br>
 
Nel browser sulla sinistra della IDE diamo un doppio clic su Report1 per visualizzarne la finestra (scheda ''Report1.report'') e alla destra della IDE in '''Proprietà''' > '''Padding''' clicchiamo sui tre puntini ('''...'''), nella finestra che appare lasciamo spuntato sincronizza e scriviamo 20 mm oppure in alternativa 2 cm e diamo OK, otterremo così un foglio A4 verticale con due centimetri di bordo tutt'intorno.
 
Nel browser sulla sinistra della IDE diamo un doppio clic su Report1 per visualizzarne la finestra (scheda ''Report1.report'') e alla destra della IDE in '''Proprietà''' > '''Padding''' clicchiamo sui tre puntini ('''...'''), nella finestra che appare lasciamo spuntato sincronizza e scriviamo 20 mm oppure in alternativa 2 cm e diamo OK, otterremo così un foglio A4 verticale con due centimetri di bordo tutt'intorno.
Riga 401: Riga 186:
 
In '''Proprietà''' > '''BoxShadow''' diamo l'effetto ombra agendo sempre sui tre puntini e nella finestra scriviamo rispettivamente dall'alto in basso: 1 mm, 1mm, 2 px 0.<br>
 
In '''Proprietà''' > '''BoxShadow''' diamo l'effetto ombra agendo sempre sui tre puntini e nella finestra scriviamo rispettivamente dall'alto in basso: 1 mm, 1mm, 2 px 0.<br>
 
Clicchiamo sul pulsante colore per scegliere un grigio chiaro.
 
Clicchiamo sul pulsante colore per scegliere un grigio chiaro.
Se clicchiamo sul più possiamo ottenere coloriparticolari.<br>
+
Se clicchiamo sul più possiamo ottenere colori particolari.<br>
 
Diamo OK
 
Diamo OK
  
Riga 433: Riga 218:
 
:Border = 2 px per Left e Right
 
:Border = 2 px per Left e Right
 
Per la terza ReportLabel:
 
Per la terza ReportLabel:
:Text = DATE
+
:Text = DATE OF BIRTH
 
:Expand = True
 
:Expand = True
 
:Alignment = Center
 
:Alignment = Center
Riga 496: Riga 281:
 
  End
 
  End
 
   
 
   
Creiamo una funzione per mostrare la data in modo corretto:
+
Creiamo una funzione per mostrare la data in modo corretto (''internazionalizzata''):
Private Function SdateToString(sDate As String) As String
+
  Private Function SdateToString(hDate As Date) As String
+
 
  Dim y, m, d As String
+
    Return Left(Str(hDate), 10)
  Dim hDate As Date
+
 
+
  End
  y = Left(sDate, 4)
 
  m = Mid(sDate, 6, 2)
 
  d = Right(sDate, 2)
 
  hDate = CDate(m &/ d &/ y)
 
  Return Left(Str(hDate), 10)
 
 
End
 
  
 
Fatto, ora possiamo avviare il progetto per constatarne il corretto funzionamento.
 
Fatto, ora possiamo avviare il progetto per constatarne il corretto funzionamento.
 +
  
 
== Progetto LoremReport ==
 
== Progetto LoremReport ==
Riga 534: Riga 313:
 
Sotto ancora una '''ReportImage''' con:
 
Sotto ancora una '''ReportImage''' con:
 
:Alignment = Center
 
:Alignment = Center
:Height 210 mm, misura che permette all'immagine di stare più o meno al centro della pagina tenuto conto del margine duperiore, dell'intestazione e del separatore.
+
:Height 210 mm, misura che permette all'immagine di stare più o meno al centro della pagina tenuto conto del margine superiore, dell'intestazione e del separatore.
 
Clicchiamo adesso su ''Book'' per spostarci nella seconda scheda:<br>
 
Clicchiamo adesso su ''Book'' per spostarci nella seconda scheda:<br>
 
Inseriamo un '''ReportVBox''' di 23 cm di altezza.<br>
 
Inseriamo un '''ReportVBox''' di 23 cm di altezza.<br>
Riga 752: Riga 531:
 
   
 
   
 
  End
 
  End
 +
 +
=== Disegnare il report con il codice ===
  
 
Prima di addentrarci nella spiegazione del codice occorre notare che il testo del libro (''tomo'') è stato arricchito di alcuni ''segnaposto'':<br>
 
Prima di addentrarci nella spiegazione del codice occorre notare che il testo del libro (''tomo'') è stato arricchito di alcuni ''segnaposto'':<br>
Riga 764: Riga 545:
 
Imposta il font generale, ne ottiene la massima larghezza per riga in pixel sfruttando la funzione interna a report ''UnitToInc'' e ne ottiene l'altezza.<br>
 
Imposta il font generale, ne ottiene la massima larghezza per riga in pixel sfruttando la funzione interna a report ''UnitToInc'' e ne ottiene l'altezza.<br>
 
Il testo del ''tomo'' viene diviso in paragrafi attraverso il carattere di accapo e passati ad un vettore.
 
Il testo del ''tomo'' viene diviso in paragrafi attraverso il carattere di accapo e passati ad un vettore.
  NOTA: Qui occorre fare una puntualizzazione se i periodi fra un carattere di accapo e un altro fossero più lunghi di una pagina questa suddivisione del testo non funzionerebbe in quanto ''gb.report2'' non è in grado di gestire paragrafi più lunghi di una pagina.
+
  '''NOTA''': Qui occorre fare una puntualizzazione se i periodi fra un carattere di accapo e un altro fossero
 +
più lunghi di una pagina questa suddivisione del testo non funzionerebbe in quanto ''gb.report2'' non
 +
è in grado di gestire paragrafi più lunghi di una pagina.
 
Ogni paragrafo viene ciclato e viene controllato l'inizio, se inizia con:<br>
 
Ogni paragrafo viene ciclato e viene controllato l'inizio, se inizia con:<br>
 
''Caput'' incrementa un contatore e se il contatore è superiore a uno significa che è appena finito un capitolo allora viene inserita un immagine di fine capitolo in base al numero del contatore.<br>
 
''Caput'' incrementa un contatore e se il contatore è superiore a uno significa che è appena finito un capitolo allora viene inserita un immagine di fine capitolo in base al numero del contatore.<br>
Questo viene fatto come se stessimo disegnado il report manualmente, vale a dire che viene inserito in ReportVBox1 una '''ReportImage'''.<br>
+
Questo viene fatto come se stessimo disegnando il report manualmente, vale a dire che viene inserito in ReportVBox1 una '''ReportImage'''.<br>
 
A seguire una '''ReportPageBreak'''.<br>
 
A seguire una '''ReportPageBreak'''.<br>
 
A seguire una '''ReportLabel''' formattata per il layout del titolo del capitolo posto al centro della riga.<br>
 
A seguire una '''ReportLabel''' formattata per il layout del titolo del capitolo posto al centro della riga.<br>
Riga 775: Riga 558:
 
Dopo aver sottratto al paragrafo la frase scritta nella ReportLabel (comprensiva del capolettera) il resto del paragrafo verrà scritto in una '''ReportTextLabel''' della quale viene stabilita l'altezza attraverso il numero delle righe dato dalla funzione ''Rows''.<br>
 
Dopo aver sottratto al paragrafo la frase scritta nella ReportLabel (comprensiva del capolettera) il resto del paragrafo verrà scritto in una '''ReportTextLabel''' della quale viene stabilita l'altezza attraverso il numero delle righe dato dalla funzione ''Rows''.<br>
 
Se invece il paragrafo inizia con $pic inserisce un'immagine centrale di 5 cm e a seguire una label per la didascalia sempre sulla base del contatore.<br>
 
Se invece il paragrafo inizia con $pic inserisce un'immagine centrale di 5 cm e a seguire una label per la didascalia sempre sulla base del contatore.<br>
Se invece il paragrafo non inizia con una delle frasi viste prima, inserisce un normale paragrafo in una '''ReportTextLabel''' la cui altezza viene calcolata come visto in precedenza.<br>--[[Utente:Gianluigi|Gianluigi]] ([[Discussioni utente:Gianluigi|discussioni]]) 10:17, 16 mar 2019 (MDT)--[[Utente:Gianluigi|Gianluigi]] ([[Discussioni utente:Gianluigi|discussioni]]) 10:17, 16 mar 2019 (MDT)
+
Se invece il paragrafo non inizia con una delle frasi viste prima, inserisce un normale paragrafo in una '''ReportTextLabel''' la cui altezza viene calcolata come visto in precedenza.<br>
 
Alla fine del ciclo viene inserita l'ultima immagine di fine paragrafo.
 
Alla fine del ciclo viene inserita l'ultima immagine di fine paragrafo.
  
 
Fatto, ora possiamo avviare il progetto per constatarne il corretto funzionamento.
 
Fatto, ora possiamo avviare il progetto per constatarne il corretto funzionamento.
 +
 +
 +
 +
=Note=
 +
[1] [http://gambaswiki.org/wiki/howto/makereport?l=it Vedi altro]
 +
 +
[2] Vedere anche i seguenti video-tutorial:
 +
* https://www.youtube.com/watch?v=EgM6IeuyVWU
 +
* https://www.youtube.com/watch?v=6OXb9OJALrc
 +
* https://www.youtube.com/watch?v=6OXb9OJALrc

Versione attuale delle 10:07, 13 giu 2023

Introduzione

gb.report2 di Fabien Bodard [nota 1] è un utilissimo componente che ci permette con pochi passaggi di avere un'anteprima di stampa e di stampare su file o carta ogni sorta di documento da noi creato con Gambas. Come si intuisce dal nome il componente è stato studiato per creare dei report dalle interrogazioni delle tabelle dei database, ma come vedremo può essere usato per quasi tutti i programmi che necessitano della stampa. Per poter comprendere a fondo come creare un buon report occorre aver ben presente come si creano le finestre dei programmi con Gambas con l'aiuto imprescindibile dei contenitori (Container) che qui hanno il prefisso Report.

Di seguito creeremo due tipi di report uno che mostra il risultato di un interrogazione, l'altro invece la stampa di un documento di testo in stile libro. Questi esempi, pur essendo abbastanza completi, non possono mostrare tutte le potenzialità del componente, vi invito pertanto a dare un'occhiata al codice sorgente del componente e ai test scritti dallo stesso Bodard dove possiamo ottenere nuovi spunti.

Nota: Nella Software farm sono presenti i progetti completi dei report qui descritti.

Progetto ReportTest

Avviamo Gambas e creiamo un progetto grafico nominandolo ReportTest (questo è il nome nella farm) o in qualunque altro modo vi aggrada. In Progetto > Proprietà > Componenti mettiamo la spunta a gb.db e a gb.report2 che si porterà dietro anche il componente gb.eval. Ora nel browser di sinistra dell'editor della IDE, se clicchiamo con il tasto desto del mouse sopra Sorgenti appare in aggiunta ai soliti, il nuovo menu Report... clicchiamoci sopra e diamo l'ok così da aggiungere il Report1 al progetto. Ripetiamo l'operazione del clic sul tasto destro per aggiungere anche un Modulo che rinomineremo MBase. Prima di creare il report andiamo ad aggiungere un pulsante alla finestra FMain e riportiamo questo codice in FMain.class:

 Public Sub Form_Open()
 
   db.Debug = True
   If MBase.CheckDB() Then
     Print ("#Unable to create database")
     Me.Close
   Endif
 
 End
 
 Public Sub Button1_Click()
 
   Report1.Preview
 
 End

Diamo uno sguardo a quanto abbiamo appena fatto:
Per prima cosa l’attivazione di db.Debug, che stampando i passaggi nella console, ci da la possibilità di apprezzare tutto quanto viene effettivamente fatto dietro le quinte da Gambas.
Quindi attraverso il metodo CheckDB() del modulo del database (lo vedremo fra poco) la creazione del database stesso. Se il metodo ritorna true vuol dire che è sopraggiunto un errore e il database non si è formato, l'utente in questo caso verrà informato attraverso un messaggio in console.
Come si vede la scritta è in inglese e avvolta da parentesi, queste ultime servono per poter tradurre le scritte e creare un progetto internazionale. Per renderlo tale, al momento della creazione del progetto basta spuntare la casella Il progetto è traducibile, se non lo abbiamo fatto basta andare al menu Progetto > Proprietà > Opzioni e swithcciare su Il progetto è traducibile, nella IDE appare il nuovo pulsante Traduci e se ci clicchiamo sopra possiamo tradurre la scritta che in caso di errore apparirà in italiano.
Nell'evento Button1_Click() c'è il codice che avvia il report.
Ora a seguire il codice del modulo del database MBase:

 Private $nRecords As Integer
 Private $hConn As New Connection
 Private $sNameDB As String = "TestReport.db"
 Private $sPathDB As String = "/tmp"
 
 Public Sub CheckDB() As Boolean
 
   If Not Exist($sPathDB) Then Return
   With $hConn
     .Close()
     .Type = "sqlite3"
     .Host = $sPathDB
     .Open()
     If .Databases.Exist($sNameDB) Then .Databases.Remove($sNameDB)
     ' Possiamo cambiare qui il numero dei record
     $nRecords = 100
     .Databases.Add($sNameDB)
     .Close()
     .Name = $sNameDB
     .Open()
     MakeTable()
     FillRecords
     Print ("#Database successfully created!")
     .Close()
   End With
 Catch
   Message.Error(("Fatal error in ") & Error.Where & ("\nError number: ") & Error.Code & ("\nCause: ") & Error.Text, "OK")
   CloseDB()
   Return True
 
 End
 
 Private Sub OpenDB()
 
   With $hConn
     .Close()
     .Type = "sqlite3"
     .Host = $sPathDB
     .Name = $sNameDB
     .Open()
   End With
 Catch
   CloseDB()
   Print Error.Text, Error.Code
 
 End
 
 Private Sub CloseDB()
 
   $hConn.Close
 
 End
 
 Private Sub MakeTable()
 
   Dim hTable As Table = $hConn.Tables.Add("tuser")
 
   hTable.Fields.Add("uskey", db.Serial)
   hTable.Fields.Add("usnam", db.String)
   hTable.Fields.Add("ussur", db.String)
   hTable.Fields.Add("usdat", db.Date)
   hTable.PrimaryKey = ["uskey"]
   hTable.Update
 Catch
   CloseDB()
   Print Error.Text, Error.Code
 
 End
 
 Private Sub FillRecords()
 
   Dim nCasual, i, y, m, d As Integer
   Dim sName, sSurname As String
   Dim aFirstName As String[] = ["Binah", "Hanna", "Selassie", "Okpara", "Agustina", "Clema",  "Coyan", "Eusebio", "Diego",
"Alisha", "Daphne", "Wiley", "Valentine", "Lidwina", "Roxana", "Edmund", "Hildeger", "Adrianne", "Janette", "Quentin",
"Sylvain", "Francesco", "Marco", "Aldina", "Ornella", "Carmencita", "Galena", "Florentino", "Rodrigo", "Benoit", "Mario"]
   Dim aLastName As String[] = ["Smith", "Johnson", "Williams", "Brown", "Martin", "Bernard", "Dubois", "Thomas", "Robert",
"Richard", "Müller", "Schmidt", "Schneider", "Fischer", "Weber", "Meyer", "Gunnarsson", "García", "González", "Rodríguez",
"Fernández", "López", "Martínez", "Jensen", "Nielsen", "Cohen", "Friedman", "Rossi", "Bianchi", "Minisini", "Verdone"]
   Dim hDate As Date
   Dim hResult As Result
 
   Randomize
   $hConn.Begin()
   For i = 0 To $nRecords
     nCasual = Rand(0, 30)
     sName = aFirstName[nCasual]
     nCasual = Rand(0, 30)
     sSurname = aLastName[nCasual]
     y = Rand(1959, 2000)
     m = Rand(1, 12)
     d = Rand(1, 28)
     hDate = Date(y, m, d)
     hResult = $hConn.Create("tuser")
     hResult!usnam = sName
     hResult!ussur = sSurname
     hResult!usdat = hDate
     hResult.Update
   Next
   $hConn.Commit()
 Catch
   CloseDB()
   Print Error.Text, Error.Code
 
 End
 
 Public Function ReturnData() As Result
 
   Dim hResult As Result
 
   OpenDB()
   hResult = $hConn.Find("tuser")
   If hResult.Available Then
     Return hResult
   Endif
   CloseDB()
 Catch
   CloseDB()
   Print Error.Text, Error.Code
 
 End

Anche qui diamo uno sguardo a quanto abbiamo appena fatto:
Prima però una doverosa precisazione: SQLite è un database particolare non serve installarlo, se avete un Gambas completo di tutti i componenti è già installato nel vostro sistema.
Le variabili di modulo riguardano in ordine: i record per il riempimento della tabella user, la connessione, il nome del database e la stringa che contiene il percorso (path).
Il primo metodo apre la connessione, crea il database temporaneo, crea la tabella e la riempe di dati (101 semplici record completi di; numero identificativo univoco (uskey) di tipo contatore, nome dell'user (usnam) testo, cognome (ussur) testo, data (usdat) data.
In SQLite non ci sono i concetti di Char (varchar, character ecc.) e Date come nei database server/client tipo PostgreSQL, MySQL ecc. però il componente gb.db li supporta e pensa lui al corretto trattamento. Se il database è già presente nella cartella tmp lo elimina prima di ricrearlo.
Tutti i restanti metodi sono prodromi a questo metodo eccetto la funzione ReturnData() che come si intuisce dal nome interroga la tabella e ne restituisce tutti i dati.

Ricapitoliamo: Abbiamo una finestra principale che quando si apre la prima volta crea un database, essa ha un solo pulsante che richiama il report il quale a sua volta usa un metodo che interroga il database per avere il risultato dei dati da visualizzare in forma comprensibile per l'utente.

Il disegno del report

Finalmente possiamo dedicarci al report.
In questo progetto ne creeremo uno di tipo tabellare simile a un GridView:
Nel browser sulla sinistra della IDE diamo un doppio clic su Report1 per visualizzarne la finestra (scheda Report1.report) e alla destra della IDE in Proprietà > Padding clicchiamo sui tre puntini (...), nella finestra che appare lasciamo spuntato sincronizza e scriviamo 20 mm oppure in alternativa 2 cm e diamo OK, otterremo così un foglio A4 verticale con due centimetri di bordo tutt'intorno. Volendo possiamo togliere la spunta a Sincronizza e dare margini differenti per ogni lato.
Diamo OK.
Nella Proprietà di Report1 possiamo anche nominare la scheda (un report può avere più schede, come vedremo in seguito) scrivendone il nome nella proprietà Text, scriviamo Section 1.

Appena sotto alle proprietà, nella ToolBox clicchiamo sulla scheda Container e su ReportHBox per poi disegnarlo sul report con un'altezza di 15 mm.

Nella scheda Proprietà > Border facciamo clic sui tre puntini per visualizzare la finestra in cui sceglieremo il layout dell'intestazione, nella scheda Bordo lasciamo spuntato sincronizza e scriviamo 1 mm qui si può anche cambiare il colore agendo sull'apposito pulsante scegliamo un grigio medio, nella scheda Angolo 5 mm (sempre sincronizzati).
Anche qui volendo possiamo togliere la spunta a Sincronizza in entrambe le schede per provare nuovi layout.
Diamo OK.

In Proprietà > BoxShadow diamo l'effetto ombra agendo sempre sui tre puntini e nella finestra scriviamo rispettivamente dall'alto in basso: 1 mm, 1mm, 2 px 0.
Clicchiamo sul pulsante colore per scegliere un grigio chiaro. Se clicchiamo sul più possiamo ottenere colori particolari.
Diamo OK

Ora all'interno del ReportHBox andiamo ad inserire una ReportLabel con le seguenti proprietà:

Alignment = Center
Autoresize = True
Brush: Clicchiamo sui puntini e impostiamo un azzurro RGB(0, 104, 208) o se preferite lo stesso grigio scuro del bordo.
Font = Sans Serif,Bold,+2
Text = REPORT TEST

Tutto questo è servito per creare l'intestazione del Report.

Ora creiamo le intestazioni di colonna ma prima per una ragione estetica inseriamo un ReportHBox come distanziatore, facciamolo dandogli un'altezza (Height) di 5mm.

Inseriamo un altro ReportHBox che farà da contenitore alle nostre intestazioni di colonna, gli diamo un bordo arrotondato per accompagnare il motivo dell'intestazione ma lo facciamo solo per gli angoli della parte superiore.
Clicchiamo sui puntini della proprietà Border e nella scheda Bordo scriviamo 2 px lasciando il colore nero di default.
Nella scheda Angolo 2 mm solo per TopLeft e TopRight.
OK.

Ora facciamo attenzione: Se desideriamo che le intestazioni vengano riportate in tutte le pagine dobbiamo impostare la proprietà Fixed a True, facciamolo.
Impostiamo anche 1 cm. di altezza.
Adesso inseriamo al suo interno le tre ReportLabel che riporteranno i nomi delle colonne, di seguito le loro rispettive proprietà:
Per tutte e tre:

Background = RGB(224, 224, 224)
Font = Bold
Padding = 2 mm. con Singronizza spuntato
Width = 65 mm.

Per la prima ReportLabel:

Text = NAME

Per la seconda ReportLabel:

Text = SURNAME
Border = 2 px per Left e Right

Per la terza ReportLabel:

Text = DATE OF BIRTH
Expand = True
Alignment = Center

Spiegazione: La seconda label mostra i margini in modo che le tre intestazioni risultino separate.
La terza label per poter mostrare il testo al centro, ha bisogno che la proprietà Expand sia posta su True.

Passiamo adesso alla costruzione delle righe di colonna.
Inseriamo l'ennesimo ReportHBox ma questa volta rinominiamolo TableLine per meglio comprenderne la funzione, diamogli un bordo di 2 px per tutti i lati escluso Top e 1 cm di altezza.
All'interno inseriremo le tre label con queste proprietà:
Prima ReportLabel:

Padding = 2 mm. sincronizzati.
Width = 65 mm. come le intestazioni di colonna.

Seconda ReportLabel:

Border = 2 px. come abbiamo già visto per le intestazioni
Padding = 2 mm. sincronizzati.
Width = 65 mm. come le intestazioni di colonna.

Terza ReportLabel:

Expand = True
Alignment = Center, anche qui come abbiamo già visto per le intestazioni.

Subito dopo inseriamo un ReportVBox con proprietà Expand su True che serve come distanziatore perché il piè di pagina appaia in modo corretto.
Piè di pagina che costruiamo inserendo subito sotto al ReportVBox una ReportLabel con queste proprietà:

Alignment = Center
Fixed = True
Padding = 3mm solo su Top
Text = ="Report Test page " & page & " on " & pages

Come abbiamo già visto in precedenza la proprietà Fixed riporta la label su tutte le pagine del report e con "=", "page" e "pages" otteniamo il numero della e delle pagine.

Agiamo sul tasto funzione F12 per passare alla scheda Report1.class e scrivere il codice: Per prima cosa scriviamo una variabile a livello di classe per contenere il risultato dell'interrogazione SQL.

Private hResult As Result

Nell'evento Open del report interroghiamo il database, passiamo il risultato alla variabile di classe e indichiamo al contenitore il numero di record totali.

Nota: Decommentiamo report debug per vedere come ci può aiutare a capire i contenitori
Public Sub Report_Open()

  'Report.Debug = True
  hResult = MBase.ReturnData()
  TableLine.DataCount = hResult.Count

End

In ogni label del contenitore andiamo a indicare il corretto risultato con:

Public Sub ReportLabel4_Data(Index As Integer)

  hResult.MoveTo(Index)
  Last.data = hResult["usnam"]

End

Public Sub ReportLabel5_Data(Index As Integer)

  hResult.MoveTo(Index)
  Last.data = hResult["ussur"]

End

Public Sub ReportLabel6_Data(Index As Integer)

  hResult.MoveTo(Index)
  Last.data = SdateToString(hResult["usdat"])

End

Creiamo una funzione per mostrare la data in modo corretto (internazionalizzata):

 Private Function SdateToString(hDate As Date) As String
 
   Return Left(Str(hDate), 10)
 
 End

Fatto, ora possiamo avviare il progetto per constatarne il corretto funzionamento.


Progetto LoremReport

Passiamo ora alla realizzazione di un report per la stampa di un file di testo in stile libro.
Va da se che gb.report2 non nasce per questo compito, questa è senz'altro una forzatura che però ci consente di mostrare altre caratteristiche di questo componente.
Qui si usa un testo già creato e delle immagini che sono presenti nel progetto LoremReport della Software farm.
Potete scaricare il progetto per poi importarne testo e immagini in un nuovo progetto grafico con la spunta sul componente gb.report2.

Clicchiamo col tasto destro del mouse su Sorgenti > Nuovo > Report... e diamo OK a Report1.
Nella scheda della IDE Report1.report in Proprietà:

Count = 2
Padding = Top:20mm;Left:30mm;Right:30mm;Bottom:20mm avendo tolto la spunta a sincronizza.
Text = Cover e cioè la copertina del libro.

Clicchiamo sulla seconda scheda e in proprietà Text scriviamo Book. Ritorniamo alla scheda Cover e dalla ToolBox > Container scegliamo un ReportHBox con altezza di 20 mm per disegnarlo sul report. Al suo interno disegnamo una ReportLabel (ToolBox > Report) con:

Alignment = Center
Expand = True
Font = Sans Serif,Bold,+10
Text = Lorem ipsum

Appena sotto un altro ReportHBox da 10 mm di altezza come distanziatore.
Sotto ancora una ReportImage con:

Alignment = Center
Height 210 mm, misura che permette all'immagine di stare più o meno al centro della pagina tenuto conto del margine superiore, dell'intestazione e del separatore.

Clicchiamo adesso su Book per spostarci nella seconda scheda:
Inseriamo un ReportVBox di 23 cm di altezza.
Appena sotto inseriamo un ReportPanel con proprietà Expand su True che serve come distanziatore perché il piè di pagina appaia in modo corretto, se ricordate nel precedente report, con la stessa funzione, avevamo usato un ReportVBox funzionano entrambi, l'importante è porre la proprietà Expand a True.
Per il piè di pagina usiamo anche qui una ReportLabel che disegneremo appena al di sotto con proprietà:

Alignment = Center
Height = 10 mm
Text = =Page

Abbiamo terminato la parte disegno.
Agiamo sul tasto funzione F12 per visualizzare la scheda Report1.class nella IDE e poterci scrivere questo codice:

Public Sub Report_Open()

  Dim s, sRow, sChar, sName As String
  Dim sBook As String = File.Load("./tomo")
  Dim ss As String[]
  Dim hRel As ReportTextLabel
  Dim hCap As ReportLabel
  Dim hRepb As ReportPageBreak
  Dim hReHo As ReportHBox
  Dim hRepIm As ReportImage
  Dim iRes, iWidth, H, i As Integer

  'Report.Debug = True
  iRes = Desktop.Resolution
  Report1.Font = Font["Serif,12"]
  iWidth = ReturnWidth()
  H = Report1.Font.H
  ss = Split(sBook, "\n", Null, True)
  For Each s In ss
    If s Like "Caput*" Then
      Inc i
      If i > 1 Then
        hRepIm = New ReportImage(ReportVBox1)
        hRepIm.Alignment = Align.Center
        hRepIm.Height = "2cm"
        hRepIm.Stretch = Report.Proportional
        Select i
          Case 2
            hRepIm.Image = Image.Load("./deceris.jpg")
          Case 3
            hRepIm.Image = Image.Load("./coliseum.jpg")
          Case 4
            hRepIm.Image = Image.Load("./cicero.jpg")
          Case 5
            hRepIm.Image = Image.Load("./janus.png")
          Case 6
            hRepIm.Image = Image.Load("./legionario.jpg")
        End Select
      Endif
      hRepb = New ReportPageBreak(ReportVBox1)
      hCap = New ReportLabel(ReportVBox1)
      hCap.Font = Font["Sans Serif,Bold,+3"]
      hCap.Expand = True
      hCap.Alignment = Align.Center
      hCap.Text = s
      hReHo = New ReportHBox(ReportVBox1)
      hReHo.Height = "10mm"
    Else If s Like "$$*" Then
      hReHo = New ReportHBox(ReportVBox1)
      hReHo.Height = "96px"
      hRepIm = New ReportImage(hReHo)
      hRepIm.Alignment = Align.Center
      hRepIm.Width = "96px"
      hRepIm.Stretch = Report.Proportional
      ' sChar = Mid(s, 3, 1)
      sChar = s[2]
      Select sChar
        Case "L"
          hRepIm.Image = Image.Load("./l.png")
        Case "U"
          hRepIm.Image = Image.Load("./u.png")
        Case "N"
          hRepIm.Image = Image.Load("./n.png")
        Case "C"
          hRepIm.Image = Image.Load("./c.png")
        Case "M"
          hRepIm.Image = Image.Load("./m.png")
        Case "E"
          hRepIm.Image = Image.Load("./e.png")
      End Select
      hCap = New ReportLabel(hReHo)
      hCap.Font = Font["Serif,12"]
      hCap.Expand = True
      hCap.Alignment = Align.BottomRight
      sRow = ReturnSentence(s, iWidth - 96)
      hCap.Text = " " & sRow
      s = Replace(s, "$$" & sChar & sRow, "")
      hRel = New ReportTextLabel(ReportVBox1)
      hRel.Font = Font["Serif,12"]
      hRel.Alignment = Align.Justify
      hRel.Text = s
      hRel.Height = CStr(Rows(s, iWidth) * H) & "px"
    Else If s Like "$pic*" Then
      hRepIm = New ReportImage(ReportVBox1)
      hRepIm.Alignment = Align.Center
      hRepIm.Height = "5cm"
      hRepIm.Stretch = Report.Proportional
      Select i
        Case 1
          hRepIm.Image = Image.Load("./1-pompei.jpg")
          sName = "Castor et Pollux"
        Case 2
          hRepIm.Image = Image.Load("./2-pompei.jpg")
          sName = "Ver allegoria"
        Case 3
          hRepIm.Image = Image.Load("./3-pompei.jpg")
          sName = "Fayyum"
        Case 4
          hRepIm.Image = Image.Load("./4-pompei.jpeg")
          sName = "Aarcana domus, picture I"
        Case 5
          hRepIm.Image = Image.Load("./5-pompei.jpg")
          sName = "Hortus allegoria"
        Case 6
          hRepIm.Image = Image.Load("./6-pompei.jpg")
          sName = "Aarcana domus, picture II"
      End Select
      hCap = New ReportLabel(ReportVBox1)
      hCap.Font = Font["Sans Serif, 10"]
      hCap.Expand = True
      hCap.Alignment = Align.Center
      hCap.Text = sName
    Else
      hRel = New ReportTextLabel(ReportVBox1)
      hRel.Font = Font["Serif,12"]
      hRel.Alignment = Align.Justify
      hRel.Text = s
      hRel.Height = CStr(Rows(s, iWidth) * H) & "px"
    Endif
  Next
  hRepIm = New ReportImage(ReportVBox1)
  hRepIm.Alignment = Align.Center
  hRepIm.Height = "2cm"
  hRepIm.Stretch = Report.Proportional
  hRepIm.Image = Image.Load("./caduceus.jpg")

End

Private Function ReturnSentence(value As String, iWidth As Integer) As String

  Dim ss As String[]
  Dim sRow As String

  value = Right(value, -3)
  If Report1.Font.TextWidth(value) < iWidth Then Return value
  ss = Split(value, " ")
  For Each s As String In ss
    sRow &= " " & s
    If Report1.Font.TextWidth(sRow) > iWidth Then
      sRow = Trim(Replace(sRow, s, ""))
      Break
    Endif
  Next
  Return sRow

End


Private Function Rows(value As String, iWidth As Integer) As Integer

  Dim i As Integer
  Dim ss As String[]
  Dim sRow As String
  ' Dim bVoid As Boolean

  If Report1.Font.TextWidth(value) < iWidth Then Return 1
  ss = Split(value, " ")
  For Each s As String In ss
    sRow &= " " & s
    If Report1.Font.TextWidth(sRow) > iWidth Then
      sRow = s
      Inc i
      '   bVoid = True
      ' Else
      '   bVoid = False
    Endif
  Next
  ' If bVoid And i > 1 Then Return i
  Return i + 1

End

Private Function ReturnUnit(value As String) As String

  Dim s As String

  Try s = Right(value, 2)
  If Error Then Error.Raise(("The value of the unit can't be extracted"))
  If s = "ft" Then Error.Raise(("Margins can't be set up in feet"))
  Return s

End

Fast Private Function ReturnMeasure(value As String) As Float

  Return CFloat(Left(value, Len(value) - 2))
Catch
  Error.Raise(("The value of measure can't be extracted"))

End

Private Function ReturnWidth() As Integer
  ' Se usiamo solo il riempimento del report, è corretto sottrarre solo il padding
  ' sinistro e destro del libro come qui.
  ' Ma se usiamo anche i margini e/o il padding di altri contenitori e/o dei
  ' bordi, anche questi devono essere sottratti.

  Dim nReport, nLeft, nRight As Float

  nReport = Report1.UnitToInch(ReturnMeasure(Report1.Width), ReturnUnit(Report1.Width))
  nLeft = Report1.UnitToInch(ReturnMeasure(Report1.Padding.Left), ReturnUnit(Report1.Padding.Left))
  nRight = Report1.UnitToInch(ReturnMeasure(Report1.Padding.Right), ReturnUnit(Report1.Padding.Left))
  Return CInt(Round((nReport - (nLeft + nRight)) * Desktop.Resolution))
Catch
  Return 0

End

Disegnare il report con il codice

Prima di addentrarci nella spiegazione del codice occorre notare che il testo del libro (tomo) è stato arricchito di alcuni segnaposto:
Dove desideriamo inserire dei capilettera la prima lettera del paragrafo è preceduta da due caratteri dollaro ($$).
Dove desideriamo inserire un immagine esplicativa del testo abbiamo inserito $pic in una riga a se stante.
Nulla invece è stato aggiunto per le piccole immagini di fine paragrafo.
Naturalmente questa è una scelta arbitraria e spetta a noi programmatori decidere quali segnaposto preferire.

Vediamo adesso cosa fa in generale il codice che abbiamo appena scritto: Al contrario di ReportTest, LoremReport disegna i contenitori dinamicamente e lo fa nell'evento di apertura il resto del codice sono funzioni di supporto.
Entriamo nei particolari del codice:
Imposta il font generale, ne ottiene la massima larghezza per riga in pixel sfruttando la funzione interna a report UnitToInc e ne ottiene l'altezza.
Il testo del tomo viene diviso in paragrafi attraverso il carattere di accapo e passati ad un vettore.

NOTA: Qui occorre fare una puntualizzazione se i periodi fra un carattere di accapo e un altro fossero
più lunghi di una pagina questa suddivisione del testo non funzionerebbe in quanto gb.report2 non
è in grado di gestire paragrafi più lunghi di una pagina.

Ogni paragrafo viene ciclato e viene controllato l'inizio, se inizia con:
Caput incrementa un contatore e se il contatore è superiore a uno significa che è appena finito un capitolo allora viene inserita un immagine di fine capitolo in base al numero del contatore.
Questo viene fatto come se stessimo disegnando il report manualmente, vale a dire che viene inserito in ReportVBox1 una ReportImage.
A seguire una ReportPageBreak.
A seguire una ReportLabel formattata per il layout del titolo del capitolo posto al centro della riga.
A seguire come distanziatore fra titolo e testo un ReportHBox alto 10 mm.
Se invece il paragrafo inizia con $$ allora viene inserito un ReportHBox che deve contenere sia l'immagine del capolettera (data dalla prima lettera che segue $$) che la prima frase del paragrafo.
Per fare questo inserisce nel ReportHBox una ReportImage e una ReportLabel.
Dopo aver sottratto al paragrafo la frase scritta nella ReportLabel (comprensiva del capolettera) il resto del paragrafo verrà scritto in una ReportTextLabel della quale viene stabilita l'altezza attraverso il numero delle righe dato dalla funzione Rows.
Se invece il paragrafo inizia con $pic inserisce un'immagine centrale di 5 cm e a seguire una label per la didascalia sempre sulla base del contatore.
Se invece il paragrafo non inizia con una delle frasi viste prima, inserisce un normale paragrafo in una ReportTextLabel la cui altezza viene calcolata come visto in precedenza.
Alla fine del ciclo viene inserita l'ultima immagine di fine paragrafo.

Fatto, ora possiamo avviare il progetto per constatarne il corretto funzionamento.


Note

[1] Vedi altro

[2] Vedere anche i seguenti video-tutorial: