Skip Navigation LinksHome : Articoli : Report complicato in Access

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.