Appunti di Programmazione

Creative Commons License

Pagina 1 2 3 4 5 6 7 8

Stampare una selezione di celle di una DataGridView

Qualche tempo fa un utente mi chiese come poteva fare a stampare SOLO le celle selezionate di una tabella DataGridView. Considerato che avevo già sviluppato un programma per la stampa di una tabella intera, ho pensato di sfruttare quel codice e di modificarlo per fare quanto mi era stato chiesto.

In teoria, non è molto difficile: si tratta di consentire la stampa delle selezioni attivando la relativa opzione nella finestra di dialogo, PrintDialog, e di recuperare l'insieme delle celle che sono selezionate dall'utente tramite l'uso della tastiera o del mouse.
L'oggetto .Net che ci consente di "catturare" le celle scelte è :

DataGridView.SelectedCells(...)

l'unico inconveniente risiede nel fatto che l'ordine con il quale vengono memorizzate le celle è inverso a quello con il quale vengono scelte. Se un utente seleziona le celle

(2,3)
(5,1)
(2,2)
(10,3)

il metodo sopra menzionato le memorizzerà nel seguente ordine:

(10,3)
(2,2)
(5,1)
(2,3)

Per come ho impostato il lavoro, è necessario che le celle siano ordinate in modo crescente, dando priorità alle righe. Continuando con l'esempio precedente avremo:

(2,2)
(2,3)
(5,1)
(10,3)

Nel programma uso diversi array per contenere gli indici delle celle, ma nonostante il codice sia commentato, credo sia utile aggiungere alcune spiegazioni al riguardo.
Facciamo un esempio.

Sia questa una tabella:

Tabella dataGridView

Dove R0...R6 e C0...C3 rappresentano le righe e le colonne della tabella rispettivamente.
Immaginiamo che l'utente selezioni queste celle nello stesso ordine in cui sono scritte di seguito:

DataGridView con celle selezionate

(2,3)
(2,1)
(2,0)
(6,3)
(5,2)
(3,0)
(5,1)
(3,3)
(3,1)

Inizialmente deposito i valori degli indici di riga e colonna negli array Riga(...) e Colonna(...) rispettivamente, quindi opero un ordinamento delle colonne prima, e delle righe dopo, ottenendo questo risultato:

(2,0)
(2,1)
(2,3)
(3,0)
(3,1)
(3,3)
(5,1)
(5,2)
(6,3)

Lo scopo è quello di riunire i dati selezionati in una nuova tabella che contiene solo le righe e le colonne interessate e che siano stampate solo le celle evidenziate lasciando vuote le altre, in questo modo:

Tabella risultante dopo la stampa

Per ottenere questo, ho necessità di conoscere quali righe e quali colonne sono state coinvolte nell'operazione della selezione. Pertanto ciclo gli array Riga e Colonna memorizzando gli indici di quelli che sono stati scelti. Tali valori li deposito all'interno di altri due array: indiciRiga(...) e indiciColonna(...), i quali saranno:

indici Riga(0)=2    indiciColonna(0)=0
indici Riga(1)=3    indiciColonna(1)=1
indici Riga(2)=5    indiciColonna(2)=2
indici Riga(3)=6    indiciColonna(3)=3

Nel caso in cui una colonna non ha celle selezionate le verrà assegnato un valore pari a "-1".
Ottenuto questo, posso passare a stampare il tutto, stando bene attento a rappresentare il contenuto delle celle che l'utente ha scelto. In pratica, con i valori di indici Riga(...) e inidiciColonna(...), utilizzati in un ciclo, disegno una tabella e per ogni cella verifico se questa appartiene all'insieme di quelle selezionate; se ciò accade ne scrivo il contenuto, altrimenti la lascio vuota.

Utilizzo il medesimo procedimento per stampare l'intero contenuto della tabella.
Recupero il numero di righe e di colonne dell'intera griglia con poche e semplici istruzioni, e ne disegno ogni singola cella.

Questo è il programma:

Imports System.Drawing.Printing
                
Public Class Form1

    'definizione di una tabella DataGridView
    Private WithEvents Tabella As New System.Windows.Forms.DataGridView

    'definizione di un pulsante "Stampa"
    Private WithEvents btnStampa As New System.Windows.Forms.Button

    'definizione di un documento da stampare
    Private WithEvents docToPrint As New System.Drawing.Printing.PrintDocument

    'definizione di una finestra di dialogo "Stampa"
    Friend PrintDialog1 As New System.Windows.Forms.PrintDialog

    Public Sub New()

        InitializeComponent()

        'impostazione della dimensione della form
        Me.Size = New Size(430, 300)

        'impostazione dello stile delle intestazioni della tabella
        With (Tabella.ColumnHeadersDefaultCellStyle)

            .Font = New Font(Tabella.Font, FontStyle.Bold)
            .Alignment = DataGridViewContentAlignment.TopCenter

        End With

        'impostazione della tabella
        With Tabella

            .Name = "dgv"
            .Size = New Size(400, 180)
            .ColumnCount = 4
            .ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single
            .CellBorderStyle = DataGridViewCellBorderStyle.Single
            .GridColor = Color.Black
            .RowHeadersVisible = True
            .Dock = DockStyle.Top

            .Columns(0).Name = "Cognome"
            .Columns(0).Width = 80
            .Columns(1).Name = "Nome"
            .Columns(1).Width = 80
            .Columns(2).Name = "Telefono"
            .Columns(2).Width = 100
            .Columns(3).Name = "Cellulare"
            .Columns(3).Width = 100

        End With

        'impostazione del pulsante "Stampa"
        With btnStampa

            .Text = "Stampa"
            .Size = New Size(80, 30)
            .Location = New Point(175, 200)

        End With

        'impostazione della possibibilità di effettuare stampe di "selezione"
        Me.PrintDialog1.AllowSelection = True

        'aggiunta dei controlli alla form
        Me.Controls.Add(Tabella)
        Me.Controls.Add(Me.btnStampa)

        'chiamata della sub per riempire la tabella con dei dati di prova
        RiempiDataGridView()

    End Sub

    'sub per inserire alcuni dati nella tabella
    Private Sub RiempiDataGridView()

        Dim riga0 As String() = {"Rossi", "Mario", "123/456789", "0999/987654"}
        Dim riga1 As String() = {"Bianchi", "Carlo", "231/566889", "0111/998877"}
        Dim riga2 As String() = {"Neri", "Antonio", "321/445566", "0222/552469"}
        Dim riga3 As String() = {"Gialli", "Luisa", "132/696987", "0333/487962"}
        Dim riga4 As String() = {"Verdi", "Franca", "213/323539", "0444/1258974"}

        For i As Integer = 0 To 12

            With Tabella.Rows

                .Add(riga0)
                .Add(riga1)
                .Add(riga2)
                .Add(riga3)
                .Add(riga4)

            End With

        Next

    End Sub

    'gestione evento click del pulsante di stampa
    Private Sub btnStampa_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnStampa.Click

        PrintDialog1.Document = docToPrint
        If (PrintDialog1.ShowDialog) = Windows.Forms.DialogResult.OK Then docToPrint.Print()

    End Sub

    'sub per elencare in ordine crescente due array fra loro collegati
    Private Sub Riordina(ByRef ArrayKey() As Integer, ByRef Array() As Integer, ByVal numero As Integer)

        Dim chk As Integer

        For i As Integer = 0 To numero

            chk = 0

            For j As Integer = i To numero

                If ArrayKey(i) > ArrayKey(j) Then

                    chk = ArrayKey(i)
                    ArrayKey(i) = ArrayKey(j)
                    ArrayKey(j) = chk

                    chk = Array(i)
                    Array(i) = Array(j)
                    Array(j) = chk

                End If

            Next

        Next

    End Sub

    'definizione del documento da stampare
    Private Sub docToPrint_PrintPage(ByVal sender As Object, ByVal e As _
    System.Drawing.Printing.PrintPageEventArgs) Handles docToPrint.PrintPage

        'coordinata X a cui stampare la tabella
        Dim posX As Integer

        'coordinata Y a cui stampare la tabella
        Dim posY As Integer

        'definizione dell'incremento in X per disegnare due celle adiacenti
        Dim incremento As Integer

        'rettangolo che rappresenta i margini di una cella della DataGridView
        Dim Rettangolo As Rectangle

        'altezza di una cella
        Dim Altezza As Integer = Tabella.RowTemplate.Height

        'definizione delle dimensioni di una cella
        Dim dimensioneCella As System.Drawing.SizeF

        'definizione dei font usati nella tabella:
        'per le celle
        Dim NormalFont As Font = New System.Drawing.Font("Microsoft Sans Serif", 8.25!, FontStyle.Regular)

        'per le intestazioni
        Dim BoldFont As Font = New System.Drawing.Font("Microsoft Sans Serif", 8.25!, FontStyle.Bold)

        'definizione del contatore delle righe stampate, in una singola pagina
        Dim righeStampate As Integer = 0

        'definizione del numero max di righe che possono essere rappresentate in una singola pagina
        Dim righePerPagina As Integer = CType(e.MarginBounds.Height / Altezza, Integer)

        'definizione del contatore delle pagine stampate
        Static pagineStampate As Integer = 0

        'definizione del numero di righe che devono essere stampate
        Dim numeroRighe As Integer

        'definizione del numero totale di pagine che devono essere stampate
        Dim nPagine As Integer

        'definizione del numero delle celle che sono da stampare
        Dim celleTotali As Integer

        'definizione del testo delle celle da stampare
        Dim testo As String

        'variabile di controllo generale per vari usi
        Dim chk As Integer

        '******************************************************************************

        'CALCOLO DEL NUMERO DI CELLE TOTALI CHE SONO DA STAMPARE

        'se nella finestra di dialogo "Stampa" l'utente ha optato di stampare la selezione di celle da lui scelta...
        If PrintDialog1.PrinterSettings.PrintRange = Printing.PrintRange.Selection Then

            'celleTotali=numero delle celle selezionate
            celleTotali = Tabella.GetCellCount(DataGridViewElementStates.Selected)

        Else
            'altrimenti, se è stato scelto si stampare l'intera tabella:

            'impostazione del numero di righe
            numeroRighe = Tabella.RowCount - 1

            'celleTotali=(numero di righe * numero di colonne)
            celleTotali = (numeroRighe) * (Tabella.ColumnCount)

        End If

        'definizione della matrice che conterrà gli indici, per ogni cella, delle RIGHE appartenenti alle celle selezionate
        Dim riga(celleTotali - 1) As Integer

        'definizione della matrice che conterrà gli indici, per ogni cella ,delle COLONNE appartenenti alle celle selezionate
        Dim colonna(celleTotali - 1) As Integer

        'definizione della matrice che contiene gli indici, in ordine crescente, delle righe che contengono le celle 
        'che devono essere stampate
        Dim indiciRiga(0) As Integer

        'definizione della matrice che contiene gli indici, in ordine crescente, delle colonne che contengono le celle 
        'che devono essere stampate
        Dim indiciColonna(Tabella.ColumnCount - 1) As Integer

        '*******************************************************************************************
        'Le celle selezionate vengono memorizzate nell'ordine inverso a quello con il quale sono state 
        'scelte, pertanto è necessario eseguire un riordinamento per una corretta manipolazione degli 
        'indici, al fine di stampare il risultato su carta così come lo si vede sul monitor.
        '*******************************************************************************************

        'se è stato scelto di stampare una selezione...
        If PrintDialog1.PrinterSettings.PrintRange = Printing.PrintRange.Selection Then

            'recupero degli indici di riga e colonna delle celle selezionate
            For i As Integer = 0 To celleTotali - 1

                riga(i) = Tabella.SelectedCells(i).RowIndex
                colonna(i) = Tabella.SelectedCells(i).ColumnIndex

            Next

            'riordinamento delle celle usando le colonne come riferimento
            Riordina(colonna, riga, celleTotali - 1)

            'riordinamento delle celle usando le righe come riferimento
            Riordina(riga, colonna, celleTotali - 1)

            'recupero del numero di righe che sono state selezionate
            chk = -1

            For i As Integer = 0 To celleTotali - 1

                If riga(i) <> chk Then

                    chk = riga(i)
                    numeroRighe += 1

                End If

            Next

            'recupero degli indici delle righe selezionate
            chk = -1
            ReDim indiciRiga(numeroRighe - 1)
            Dim contatore As Integer = 0

            For i As Integer = 0 To celleTotali - 1

                If riga(i) <> chk Then
                    indiciRiga(contatore) = riga(i)
                    chk = riga(i)
                    contatore += 1
                End If

            Next

            'recupero degli indici delle colonne che contengono celle selezionate
            'Se una colonna non presenta celle selezionate le viene assegnato un valore =-1
            For i As Integer = 0 To Tabella.ColumnCount - 1

                For j As Integer = 0 To celleTotali - 1

                    If colonna(j) = i Then

                        indiciColonna(i) = i
                        Exit For

                    Else

                        indiciColonna(i) = -1

                    End If

                Next

            Next

        Else
            'altrimenti, se la tabella deve essere stampata completamente allora...
            'inserimento degli indici di riga e colonna nelle rispettive matrici
            chk = 0
            For i As Integer = 0 To Tabella.RowCount - 2

                For j As Integer = 0 To Tabella.ColumnCount - 1

                    riga(chk) = i
                    colonna(chk) = j

                    chk += 1

                Next

            Next

            'recupero degli indici delle colonne
            ReDim indiciRiga(numeroRighe - 1)
            For i As Integer = 0 To numeroRighe - 1

                indiciRiga(i) = i
                If i < Tabella.ColumnCount Then
                    indiciColonna(i) = i
                End If

            Next

        End If

        'impostazione del numero di pagine da stampare
        If numeroRighe Mod righePerPagina > 0 Then

            nPagine = (numeroRighe \ righePerPagina) + 1

        Else

            nPagine = (numeroRighe \ righePerPagina)

        End If

        '***************************************************************************
        'STAMPA DELLE INTESTAZIONI
        'coordinate della posizione iniziale delle intestazioni
        posX = CType((e.MarginBounds.Width - 360) / 2 + e.MarginBounds.Left, Integer)
        posY = e.MarginBounds.Top


        For i As Integer = 0 To Tabella.ColumnCount - 1

            'se l'indice della colonna è diverso da -1 allora significa che la colonna contiene celle da stampare
            'ed è necessario disegnarne l'intestazione
            If indiciColonna(i) <> -1 Then

                'recupero del testo dell'intestazione
                testo = Tabella.Columns(indiciColonna(i)).HeaderText

                'calcolo delle dimensioni della cella
                dimensioneCella = e.Graphics.MeasureString(testo, BoldFont)

                'calcolo dell'incremento da usare per disegnare celle che sono vicine
                incremento = CType((Tabella.Columns(indiciColonna(i)).Width - dimensioneCella.Width) / 2, Integer)

                'impostazione del rettangolo che rappresenta una cella della tabella
                Rettangolo = New Rectangle(posX, posY, Tabella.Columns(indiciColonna(i)).Width, Altezza)

                'disegno del rettangolo della cella
                e.Graphics.DrawRectangle(Pens.Black, Rettangolo)

                'disegno del contenuto della cella
                e.Graphics.DrawString(testo, BoldFont, Brushes.Black, posX + incremento, posY + 5)

                'aggiornamento della posizione X per rappresentare le intestazioni su una stessa riga
                posX += Tabella.Columns(indiciColonna(i)).Width

            End If
        Next

        '***************************************************************************
        'STAMPA DELLA TABELLA
        'coordinate della posizione iniziale della tabella
        posX = CType((e.MarginBounds.Width - 360) / 2 + e.MarginBounds.Left, Integer)
        posY = e.MarginBounds.Top + Altezza

        'impostazione della variabile booleana che tiene conto del fatto se una cella esiste oppure no
        Dim esiste As Boolean

        'impostazione del contatore delle righe
        Static r As Integer = 0

        'impostazione del contatore del numero di celle stampate
        Static nc As Integer = 0

        'ciclo per disegnare le celle della tabella
        Do

            For c As Integer = 0 To Tabella.ColumnCount - 1

                'se la colonna in questione contiene celle selezionate
                If indiciColonna(c) <> -1 Then

                    'impostazione a False della variabile "esiste"
                    esiste = False

                    'scorrimento di tutte le celle per controllare se quella che è in fase di valutazione appartiene o meno alla lista
                    'delle celle da stampare
                    For i As Integer = 0 To celleTotali - 1

                        'se l'utente ha scelto di stampare una selezione, potrebbe accadere che alcune celle di una stessa riga,
                        'siano da stampare e altre no, pertanto devo verificare se la cella che sto valutando, che ha indici
                        'indiciRiga(r) e indiciColonna(c) ha una corrispondenza nella lista di celle selezionate.
                        'Questo perché indiciRiga e indiciColonna contengono solo i valori degli indici delle righe e colonne
                        'rispettivamente, che hanno almeno una cella selezionata in esse contenute.
                        'ESEMPIO:
                        'se ho una tabella di 3 righe e 4 colonne e seleziono solo le prime 2 colonne della prima e terza riga avrò:
                        'indiciRiga(0)=0
                        'indiciRiga(1)=2

                        'indiciColonna(0)=0
                        'indiciColonna(1)=1
                        'indiciColonna(2)=-1
                        'indiciColonna(3)=-1

                        'mentre
                        'riga(0)=0; colonna(0)=0
                        'riga(1)=0; colonna(1)=1
                        'riga(2)=2; colonna(2)=0
                        'riga(3)=2; colonna(3)=1

                        'se ciò accade allora significa che la cella Esiste e deve essere stampata.
                        If riga(i) = indiciRiga(r) AndAlso colonna(i) = indiciColonna(c) Then

                            esiste = True
                            Exit For

                        End If

                    Next

                    'quindi la si stampa
                    If esiste = True Then

                        testo = Tabella.Item(indiciColonna(c), indiciRiga(r)).Value.ToString
                        dimensioneCella = e.Graphics.MeasureString(testo, NormalFont)
                        incremento = CType((Tabella.Columns(indiciColonna(c)).Width - dimensioneCella.Width) / 2, Integer)
                        Rettangolo = New Rectangle(posX, posY, Tabella.Columns(indiciColonna(c)).Width, Altezza)

                        e.Graphics.DrawRectangle(Pens.Black, Rettangolo)
                        e.Graphics.DrawString(testo, NormalFont, Brushes.Black, posX + incremento, posY + 5)
                        posX += Tabella.Columns(indiciColonna(c)).Width

                        'incremento del numero di celle con il loro contenuto, che sono state stampate
                        nc += 1

                    Else
                        'altrimenti si disegna solo una cella vuota
                        testo = Tabella.Item(indiciColonna(c), indiciRiga(r)).Value.ToString
                        dimensioneCella = e.Graphics.MeasureString(testo, NormalFont)
                        incremento = CType((Tabella.Columns(indiciColonna(c)).Width - dimensioneCella.Width) / 2, Integer)
                        Rettangolo = New Rectangle(posX, posY, Tabella.Columns(indiciColonna(c)).Width, Altezza)

                        e.Graphics.DrawRectangle(Pens.Black, Rettangolo)
                        posX += Tabella.Columns(indiciColonna(c)).Width

                    End If

                End If

            Next

            'aggiornamento delle coordinate della posizione della prossima riga
            posX = CType((e.MarginBounds.Width - 360) / 2 + e.MarginBounds.Left, Integer)
            posY += Altezza

            'incremento del numero di righe processate
            r += 1

            'e di quelle stampate
            righeStampate += 1

            'se le celle stampate sono tutte, si esce dal ciclo Do...Loop
            If nc >= celleTotali Then Exit Do

            'il ciclo si ripete fino a quando le righe stampate sono inferiori o uguali a quelle che possono stare
            'in una singola pagina...
        Loop While (righeStampate <= righePerPagina)

        'incremento del numero di pagine stampate
        pagineStampate += 1

        'ovviamente se sono state stampate tutte le pagine...
        If pagineStampate = nPagine Then

            'non vi sono più dati da processare
            e.HasMorePages = False

            'si azzerano le variabili coinvolte
            righePerPagina = 0
            pagineStampate = 0
            r = 0
            nc = 0
        Else
            'altrimenti si comunica al compilatore che ci sono altre pagine da stampare
            e.HasMorePages = True

            'aggiornamento della variabile che conteggia le righe stampate in una singola pagina
            righeStampate = 0
        End If

    End Sub

End Class

Download sorgente "Print_Selezione_DataGridView.zip" ( 80KB )

Pagina 1 2 3 4 5 6 7 8