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:
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:
(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:
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 )