Creare un report in Microsoft Access con due sottoreport a righe fisse
di Gionata Aladino Canova (5 Marzo 2010)
Superare l'impossibile. Creare un report in Microsoft Access, con due sottoreport
di dettaglio entrambi a righe fisse. Come serve nei moduli da strappare a metà.
Le richieste sono il motore dell'ingegno
Ci è stato richiesto di stampare su un foglio A4 due moduli simili, da dividere
successivamente strappando il foglio, come si vede in
Figura 1.
Figura 1
Bene, questa cosa, con Access, sembra impossibile.
Infatti, se si inseriscono due sottoreport espandibili, il primo si espande fino
ad aver stampato tutte le sue righe. Se si imposta la proprietà Espandibile
a Falso, il sottoreport stampa solo le righe che può contenere.
Anche impostare l'altezza del corpo del report principale non da l'effetto voluto,
in quanto l'altezza si riferisce alla parte del corpo che si ripete, non all'altezza
totale di tutte le righe.
Soluzione adottata, campi fissi e codice
Per superare i problemi, abbiamo creato un report con campi fissi. Ossia, per stampare
tre righe, ripeto tre volte la fila dei campi necessari. I problemi da risolvere
sono:
- Valorizzare i campi semplicemente
- Generare il numero di pagine necessarie nel report
Il primo problema è stato risolto creando una funzione DammiIlValore(Pagina, Riga,
Colonna). Il campo "sa" in quale pagina si trova, quindi passa alla funzione
il numero della pagina. Il numero di riga passato lo decidiamo noi, sarà 1 per la
prima riga, 2 per la seconda e così via. E stessa cosa per la colonna, come si vede
in Figura 2.
Figura 2
Questa soluzione consente di modificare i campi in maniera intuitiva (e senza dover
dare un nome sensato ad ognuno di essi) e di non avere codice che punta a campi
con nomi errati, in ogni caso.
Il secondo problema è far generare ad Access il numero di pagine che serve nel report.
Per quanto ne so, non esiste modo di aggiungere pagine ad un report via codice,
in quanto è il motore di rendering che calcola quante pagine sono necessarie.
Un sistema che funziona, è aggiungere nel corpo del report il controllo interruzione
di pagina, tante volte per quante pagine si vogliono generare. Le soluzioni
possibili per farlo sono due. Aprire via codice il report in modalità struttura
e poi aggiungere i controlli necessari. Questo approccio può creare problemi utilizzando
il runtime per far girare la soluzione o problemi relativi all'apertura in modalità
struttura. Un approccio molto più leggero, se si sa a priori il numero massimo di
pagine richiesto, è quello che abbiamo adottato. Ossia, abbiamo inserito manualmente
10 interruzioni di pagina nel corpo. E via codice provvediamo a rendere visibili
solo quelle necessarie. Il motore di rendering utilizza solo quelle visibili. Unica
accortezza: non "appiccicare" la parte inferiore del corpo all'ultima interruzione,
che verrebbe così ignorata.
Figura 3.
Figura 3
Recuperare le informazioni
Analizziamo il codice necessario.
Option Compare Database
Option Explicit
Const NumRighePerPagina = 2
Dim intRigheTotali
Dim Dati()
Dichiariamo un array Dati() che verrà valorizzato con i nostri dati. La costante
NumRighePerPagina sarà valorizzata con le righe desiderate. La routine seguente
viene eseguita all'apertura del report:
Private Sub Report_Open(Cancel As Integer)
' Apro il recordset dei dettagli
Dim rst As New ADODB.Recordset
rst.Open "SELECT * FROM XMLRigaDocumento", _
CurrentProject.Connection, _
adOpenForwardOnly, adLockOptimistic
If rst.BOF And rst.EOF Then
MsgBox "Il report rptModuloConsegna" & _
"non contiene dati, stampa annullata."
Cancel = True
Else
' Memorizzo i risultati in un array
Dati = rst.GetRows
' Calcolo il numero di righe e di pagine
Dim intNumPagine As Integer
intRigheTotali = UBound(Dati, 2) + 1
intNumPagine = -(Int((intRigheTotali) / _
-NumRighePerPagina))
Dim i As Integer
For i = 1 To intNumPagine
Me.Controls("InterruzionePagina" & i).Visible = True
Next
For i = intNumPagine + 1 To 10
Me.Controls("InterruzionePagina" & i).Visible = False
Next
' Caso particolare. Se è una sola pagina,
' le interruzioni non servono
If intNumPagine = 1 Then
Me.InterruzionePagina1.Visible = False
End If
End If
rst.Close
Set rst = Nothing
End Sub
Riempiamo l'array Dati() con i dati provenienti dalla nostra query, con la
comodissima istruzione GetRows. A questo punto possiamo calcolare quante
righe dovremo stampare e, conseguentemente, quante pagine sarà il nostro report.
A questo punto è sufficiente visualizzare le interruzioni di pagina volute e nascondere
le altre. Con un caso particolare da tenere presente. Se la pagina è una sola, NON
servono interruzioni di pagina, esiste già.
Piccolo trucco. Dovendo arrotondare il numero di pagine all'intero superiore, si
fa il negativo del numero , si arrotonda con la funzione Int (che ci restituirà
il numero inferiore) e poi si ribalta il risultato con un nuovo segno -.
La routine seguente è quella che restituisce il valore ai campi
Private Function DammiIlValore(Pagina, Riga, Colonna)
Dim intPrimaRigaDaLeggere As Integer
intPrimaRigaDaLeggere = (Pagina - 1) * NumRighePerPagina
Dim intRigaDaLeggere As Integer
intRigaDaLeggere = intPrimaRigaDaLeggere + (Riga - 1)
' leggo i valori da un array
If intRigaDaLeggere < intRigheTotali Then
DammiIlValore = Dati(Colonna, intRigaDaLeggere)
Else
DammiIlValore = ""
End If
End Function
Come già detto, la chiamata viene effettuata da ogni singolo campo e viene passata,
in ordine, la pagina, la riga e la colonna voluta.
L'unico calcolo da fare, data la riga voluta, è considerare quale riga dell'array
leggere, moltiplicando il numero di righe per pagina per la pagina alla quale siamo
posizionati.
Lo specificare la colonna ci consente anche, ovviamente, di avere dati diversi nella
parte superiore ed inferiore del modulo. Ossia, ad esempio, nella 4' colonna del
modulo superiore potrei voler visualizzare il prezzo, mentre nella 4' colonna della
parte inferiore, il peso. Basterà leggere colonne diverse dalla base dati.
Listato completo
Option Compare Database
Option Explicit
Const NumRighePerPagina = 2
Dim intRigheTotali
Dim Dati()
Private Sub Report_Open(Cancel As Integer)
' Apro il recordset dei dettagli
Dim rst As New ADODB.Recordset
rst.Open "SELECT * FROM XMLRigaDocumento", _
CurrentProject.Connection, _
adOpenForwardOnly, adLockOptimistic
If rst.BOF And rst.EOF Then
MsgBox "Il report rptModuloConsegna" & _
"non contiene dati, stampa annullata."
Cancel = True
Else
' Memorizzo i risultati in un array
Dati = rst.GetRows
' Calcolo il numero di righe e di pagine
Dim intNumPagine As Integer
intRigheTotali = UBound(Dati, 2) + 1
intNumPagine = -(Int((intRigheTotali) / _
-NumRighePerPagina))
Dim i As Integer
For i = 1 To intNumPagine
Me.Controls("InterruzionePagina" & i).Visible = True
Next
For i = intNumPagine + 1 To 10
Me.Controls("InterruzionePagina" & i).Visible = False
Next
' Caso particolare. Se è una sola pagina,
' le interruzioni non servono
If intNumPagine = 1 Then
Me.InterruzionePagina1.Visible = False
End If
End If
rst.Close
Set rst = Nothing
End Sub
Private Function DammiIlValore(Pagina, Riga, Colonna)
Dim intPrimaRigaDaLeggere As Integer
intPrimaRigaDaLeggere = (Pagina - 1) * NumRighePerPagina
Dim intRigaDaLeggere As Integer
intRigaDaLeggere = intPrimaRigaDaLeggere + (Riga - 1)
' leggo i valori da un array
If intRigaDaLeggere < intRigheTotali Then
DammiIlValore = Dati(Colonna, intRigaDaLeggere)
Else
DammiIlValore = ""
End If
End Function
Impossibile, mai dirlo
Il problema che abbiamo affrontato era uno di quelli catalogati come senza soluzione.
Ma noi informatici non ci arrendiamo mai. E siamo estremamente pigri, il computer
deve lavorare per noi... :-)
Un ringraziamento ad Andrea Venturi, mio collaboratore, con il
quale ho realizzato a 4 mani questa soluzione!
Download
Il codice descritto insieme ad un report di esempio sono disponibili per il
download e liberamente utilizzabili.