Creare report con il componente gb.report2
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. 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:
Property Read sPathDB As String Private $sPathDB As String Public Sub Form_Open() $sPathDB = User.Home &/ Application.Name If Not Exist($sPathDB) Then Mkdir $sPathDB If MBase.CheckDB() Then Print ("#Unable to create database") Me.Close Endif End Private Function sPathDB_Read() As String Return $sPathDB End Public Sub Button1_Click() Report1.Preview End
Diamo uno sguardo a quanto abbiamo appena fatto:
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.
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.
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.
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 Public Sub CheckDB() As Boolean $sPathDB = FMain.sPathDB If Not Exist($sPathDB) Then Return With $hConn .Close() .Type = "sqlite3" .Host = $sPathDB .Open() If Not $hConn.Databases.Exist($sNameDB) Then $nRecords = 100 'FRecords.ReturnRecords() 'If $nRecords = 0 Then Return True .Databases.Add($sNameDB) .Close() .Name = $sNameDB .Open() MakeTable() FillRecords Print ("#Database successfully created!") Else Print ("#Existing database") Endif .Close() End With Catch Message.Error(("Fatal error in ") & Error.Where & ("\nError number: ") & Error.Code & ("\nCause: ") & Error.Text, "OK") Return True End Private Sub OpenDB() With $hConn .Close() .Type = "sqlite3" .Host = $sPathDB .Name = $sNameDB .Open() End With Catch Print Error.Text, Error.Code End Public Sub CloseDB() $hConn.Close End Public Function ExistConnection() As Boolean Return $hConn.Opened End Private Sub MakeTable() Dim sMySql As String $hConn.Begin() sMySql = "CREATE TABLE 'tuser' (" sMySql &= " 'uskey' INTEGER PRIMARY KEY," sMySql &= " 'usnam' TEXT," sMySql &= " 'ussur' TEXT," sMySql &= " 'usdat' TEXT NOT NULL" sMySql &= " );" $hConn.EXEC(sMySql) $hConn.Commit() Catch Print Error.Text, Error.Code End Private Sub FillUser(iKey As Integer, sName As String, sSurname As String, sDate As String) Dim sMySql As String sName = Replace(sName, "'", "") sSurname = Replace(sSurname, "'", "") sMySql = "INSERT INTO tuser (" sMySql &= " uskey," sMySql &= " usnam," sMySql &= " ussur," sMySql &= " usdat" sMySql &= " )" sMySql &= " VALUES (" sMySql &= " " & iKey & "," sMySql &= " '" & sName & "'," sMySql &= " '" & sSurname & "'," sMySql &= " '" & sDate & "'" sMySql &= " );" $hConn.EXEC(sMySql) Catch Print Error.Text, Error.Code End Private Sub FillRecords() Dim nCasual, i, nPar, nCon As Integer Dim sName, sSurname, sDate As String Randomize 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 CloseDB() Print Error.Text, Error.Code 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:
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, 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.
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.
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.
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 coloriparticolari.
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
- 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: Private Function SdateToString(sDate As String) As String Dim y, m, d As String Dim hDate As Date 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 potete avviare il progetto per constatarne il corretto funzionamento.