Descrizione :
Una mia tecnica su UserControl per aggiungere un DataGridView Totali ad un DataGridView Dati.
+ Articolo :
L'articolo seguente non vuole essere un "how-to" su "come creare un UserControl", ma principalmente uno spunto pratico e funzionante in risposta al problema di avere una riga ( o più righe... ) destinata a contenere i totali ( ma anche altri dati riassuntivi... ) calcolati su alcune delle colonne di un DataGridView popolato tramite DataTable.
Il Progetto di Test ( una semplice Applicazione Windows Forms ) consta di questi componenti :
- Form di avvio : "FormMain"
--> Button : btn_test
--> Button : btn_testnothing
--> Button : btn_testfont
- UserControl "TDataGridView"
--> DataGridView : DGV
--> DataGridView : DGV_totals
A design basta aggiungere a TDataGridView i due DataGridView sopra indicati, senza preoccuparsi troppo di impostarne le caratteristiche in quanto vengono definite e re-impostate dinamicamente via codice.
--> Codice per TDataGridView :
Public Class TDataGridView
'DataSource e Indici delle Colonne di cui si desiderano i Totali
Private m_datasource As DataTable
Private m_totcolindices As List(Of Integer)
'Font comune
Private m_dgvfont As New Font(Drawing.FontFamily.GenericSansSerif, 12)
Private m_dgvheadersfont As New Font(m_dgvfont, FontStyle.Bold)
Public Event DGVCellRightClick(ByVal column As Integer, ByVal row As Integer, ByVal value As Object)
Public Property DgvFont As Font
Get
Return m_dgvfont
End Get
Set(ByVal value As Font)
Try
m_dgvheadersfont = New Font(value, FontStyle.Bold)
m_dgvfont = value
Catch ex As Exception
MessageBox.Show("Impossibile usare questo Font.", "Azione annullata", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
Exit Property
End Try
DGV.ColumnHeadersDefaultCellStyle.Font = m_dgvheadersfont
DGV.Font = m_dgvfont
DGV_totals.ColumnHeadersDefaultCellStyle.Font = m_dgvheadersfont
DGV_totals.Font = m_dgvfont
ResizeDGV()
End Set
End Property
Private Sub AggiornaTotali()
If DGV_totals.RowCount = 0 Or m_datasource Is Nothing Then Exit Sub
Me.Validate()
DGV.EndEdit()
Dim oTot As Object
Dim dTot As Double
For i As Integer = 0 To m_totcolindices.Count - 1
oTot = m_datasource.Compute("SUM(" & m_datasource.Columns(m_totcolindices(i)).ColumnName & ")", Nothing)
Try
dTot = Convert.ToDouble(oTot)
Catch ex As Exception
dTot = 0
End Try
DGV_totals.Rows(0).Cells(m_totcolindices(i)).Value = dTot
Next
End Sub
Public Sub SetDataSource(ByVal dataSource As DataTable, ByVal totColIndices As List(Of Integer))
m_totcolindices = totColIndices
m_datasource = dataSource
DGV.DataSource = m_datasource
With DGV_totals
.Rows.Clear()
.Columns.Clear()
If m_datasource Is Nothing Or m_totcolindices Is Nothing Then Exit Sub
For i As Integer = 0 To DGV.Columns.Count - 1
.Columns.Add(DGV.Columns(i).Name, "")
.Columns(i).Width = DGV.Columns(i).Width
If m_totcolindices.Contains(i) Then .Columns(i).HeaderText = "Totale"
Next
Dim dgvr As New DataGridViewRow
dgvr.CreateCells(DGV)
.Rows.Add(dgvr)
End With
AggiornaTotali()
ResizeDGV()
End Sub
Public Sub SetColumnFormat(ByVal columnIndex As Integer, ByVal format As String)
Try
DGV.Columns(columnIndex).DefaultCellStyle.Format = format
DGV_totals.Columns(columnIndex).DefaultCellStyle.Format = format
Catch ex As Exception
MessageBox.Show("Impossibile applicare questo Formato.", "Azione annullata", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End Try
End Sub
Private Sub TDataGridView_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.BorderStyle = Windows.Forms.BorderStyle.FixedSingle
Me.MinimumSize = New Size(200, 100)
With DGV
.AllowUserToResizeRows = False
.ScrollBars = ScrollBars.Vertical
.Top = 1
.Left = 1
.Width = Me.Width - 4
.Anchor = AnchorStyles.Left Or AnchorStyles.Top Or AnchorStyles.Right
End With
With DGV_totals
.ReadOnly = True
.AllowUserToOrderColumns = False
.AllowUserToResizeColumns = True
.AllowUserToResizeRows = False
.ScrollBars = ScrollBars.Horizontal
.ColumnHeadersVisible = True
.RowHeadersVisible = DGV.RowHeadersVisible
.Left = DGV.Left
.Width = DGV.Width
.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Bottom
End With
End Sub
Private Sub ResizeDGV()
DGV.AutoResizeRows()
With DGV_totals
.AutoResizeRows()
.Width = DGV.Width
If DGV.Controls.OfType(Of VScrollBar).SingleOrDefault.Visible = True Then
.Width -= SystemInformation.VerticalScrollBarWidth
End If
If .Rows.Count > 0 Then
.Height = .Rows(0).Height + 1
Else
.Height = .RowTemplate.Height + 1
End If
If .ColumnHeadersVisible = True Then
.Height += .ColumnHeadersHeight
End If
If .Controls.OfType(Of HScrollBar).SingleOrDefault.Visible = True Then
.Height += SystemInformation.HorizontalScrollBarHeight
End If
.Top = Me.Height - .Height - 3
DGV.Height = Me.Height - DGV.Top - .Height - 2
End With
End Sub
Protected Overrides Sub OnResize(ByVal e As System.EventArgs)
ResizeDGV()
Me.Refresh()
MyBase.OnResize(e)
End Sub
Private Sub DGV_ColumnWidthChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewColumnEventArgs) Handles DGV.ColumnWidthChanged
DGV_totals.Columns(e.Column.Index).Width = DGV.Columns(e.Column.Index).Width
ResizeDGV()
DGV_totals.HorizontalScrollingOffset = DGV.HorizontalScrollingOffset
End Sub
Private Sub DGV_ColumnHeaderMouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs) Handles DGV.ColumnHeaderMouseClick
DGV.HorizontalScrollingOffset = DGV_totals.HorizontalScrollingOffset
End Sub
Private Sub DGV_CellMouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs) Handles DGV.CellMouseClick
If e.Button = Windows.Forms.MouseButtons.Right Then RaiseEvent DGVCellRightClick(e.ColumnIndex, e.RowIndex, DGV(e.ColumnIndex, e.RowIndex).Value)
End Sub
Private Sub DGV_totals_ColumnWidthChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewColumnEventArgs) Handles DGV_totals.ColumnWidthChanged
DGV.Columns(e.Column.Index).Width = DGV_totals.Columns(e.Column.Index).Width
ResizeDGV()
DGV_totals.HorizontalScrollingOffset = DGV.HorizontalScrollingOffset
End Sub
Private Sub DGV_totals_Scroll(ByVal sender As Object, ByVal e As System.Windows.Forms.ScrollEventArgs) Handles DGV_totals.Scroll
DGV.HorizontalScrollingOffset = DGV_totals.HorizontalScrollingOffset
End Sub
Private Sub DGV_CellValueChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles DGV.CellValueChanged
AggiornaTotali()
End Sub
Private Sub DGV_RowsRemoved(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewRowsRemovedEventArgs) Handles DGV.RowsRemoved
AggiornaTotali()
End Sub
Private Sub DGV_RowsAdded(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewRowsAddedEventArgs) Handles DGV.RowsAdded
AggiornaTotali()
End Sub
End ClassA questo punto basta compilare, e su Form "FormMain" possiamo trascinare un nuovo Controllo "TDataGridView1" direttamente dalla ToolBar di Visual Studio. come si nota subito, grazie all'Overrides Sub OnResize(), e al Metodo interno ResizeDGV(), le posizioni e proporzioni dei due DataGridView nel nuovo Controllo si adattano anche in design ad ogni operazione manuale.
Nell'esempio ho scelto di gestire :
- DataSource a livello di TDataGridView : definito semplicemente assieme agli indici di colonna destinati a contenere i totali.
- Font a livello di TDataGridView : una Font unica.
- Resize : sia a Design, sia a Runtime. Posizioni e proporzioni, nonchè variazioni della larghezza Colonne, sia sul DGV principale, sia sul secondario, sono completamente automatiche.
- Scrolling : lo scrolling viene sempre propagato dal DGV secondario ( Totali ), mentre la HScrollBar sul principale non è visibile. Visivamente il Controllo sembra una sorta di "DataGridView avanzato" diviso in due sezioni...
- DGVCellRightClick() : un esempio di come implementare un Evento e i suoi parametri a livello di UserControl.
- SetColumnFormat : per impostare in modo unificato il formato di visualizzazione dei campi numerici soggetti a calcolo del totale.
- Calcolo dei Totali Colonna : NON eseguito banalmente con cicli sulle celle, MA a livello di DataSource, grazie al Metodo Compute() del DataTable.
A questo punto non resta altro che aggiungere un nuovo "TDataGridView1" a FormMain, per vederlo all'opera.
--> Codice per FormMain :
Public Class FormMain
Private Sub btn_test_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_test.Click
'DataTable
Dim DT As New DataTable
DT.Columns.Add("ID", GetType(Integer))
DT.PrimaryKey = New DataColumn() {DT.Columns("ID")}
DT.Columns.Add("Campo1", GetType(String))
DT.Columns.Add("Campo2", GetType(Integer))
DT.Columns.Add("Campo3", GetType(Single))
DT.Columns.Add("Campo4", GetType(Double))
For i As Integer = 1 To 50
DT.Rows.Add({i, "Campo1_" & i, i, 1.23 * i, 12.3456 * i})
Next
'TDataGridView1
With TDataGridView1
.SetDataSource(DT, New List(Of Integer)({2, 3, 4}))
'Formati e altre proprietà ...
'...
.SetColumnFormat(3, "N2")
.SetColumnFormat(4, "N4")
'...
End With
'Test Nomi Colonne
Dim SB As New System.Text.StringBuilder
With TDataGridView1
For i As Integer = 0 To DT.Columns.Count - 1
SB.Append(DT.Columns(i).ColumnName & " / " & .DGV.Columns(i).Name & " / " & .DGV_totals.Columns(i).Name & _
Environment.NewLine)
Next
End With
MessageBox.Show(SB.ToString)
End Sub
Private Sub btn_testnothing_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_testnothing.Click
TDataGridView1.SetDataSource(Nothing, Nothing)
End Sub
Private Sub TDataGridView1_DGVCellRightClick(ByVal column As System.Int32, ByVal row As System.Int32, ByVal value As System.Object) Handles TDataGridView1.DGVCellRightClick
MessageBox.Show("[" & column & ", " & row & "] = " & value.ToString)
End Sub
Private Sub btn_testfont_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_testfont.Click
Using FD As New FontDialog
FD.Font = TDataGridView1.DgvFont
If FD.ShowDialog = Windows.Forms.DialogResult.OK Then TDataGridView1.DgvFont = FD.Font
End Using
End Sub
End Class--> Come si nota, il blocco di codice destinato all'impostazione del TDataGridView1 da parte del codice Form è ridotto all'osso : imposto il DataSource, gli indici dei Campi da riportare in Totale, e la formattazione sui Campi con virgole. Tutto il resto, sia a design, sia a runtime, è gestito all'interno della Classe UserControl.
--> Il ciclo "Test Nomi Colonne" serve solo allo scopo di verficare la corrispondenza tra indici e nomi dei Campi, che sono gli stessi per DT, DGV e DGV_totals. Quando servirà leggere i totali da DGV_totals questo fatto tornerà utile...
--> TDataGridView1_DGVCellRightClick() è l'utilizzo del nuovo Evento personalizzato creato a livello di UserControl.
--> btn_testfont permette di impostare la Font comune per TDataGridView1. Da notare che questa impostazione funziona anche nel caso in cui il DataSource sia a Nothing ( cmd_testnothing ).
+ Fine Articolo.



11:47
MarcoGG


Posted in:
9 commenti:
Ciao MarcoGG, ho dato un occhiata al tuo usercontrol datagridview con righe totali, lo vorrei usare in uan mia applicazione, fin qui nessun problema, ma lo volevo pure usare come un controllo che eredita da datagridview, è possibile?
Ooopss! scusa per la mia domanda, ho risolto!!! complimenti per il controllo è molto utile.. :)
Ciao. Come tu stesso hai notato, si tratta di un UserControl che include due DataGridView. Pertanto non può ereditare da DataGridView stesso. Chiedi pure se hai dubbi. - Grazie. In effetti mi sono divertito su questo Controllo. Ed è stato apprezzato da diversi utenti. ;-)
Ciao MarcoGG volevo chiederti una cosa su questo controllo;
ho notato che quando elimino l'intera riga selezionandola e premendo canc da tastiera, i totali non si aggiornano, si può fare qualcosa? per risolvere? grazie sempre..... anticipatamente..
Ciao. Anzitutto ti ringrazio per il feedback sul presente Articolo. Lo scopo di questo spazio per i commenti è anche quello di segnalare dubbi e perplessità. In questo modo posso migliorare le soluzioni proposte anche sulla scorta delle segnalazioni degli utenti. In particolare questa tua è opportuna, e ne ho approfittato per rivedere l'intero codice della Classe TDataGridView. Penso che ora il funzionamento generale del controllo sia decisamente migliorato. Invito te e chiunque sia in possesso della vecchia versione, di passare a quest'ultima.
Ciao MarcoGG ho visto la nuova versione del tuo controllo perfetto, era quello che mancava, mi sono permesso di aggiungere altri metodi al controllo, dato che si tratta di un contenitore che contiene dei numeri, ho voluto inserire la gestione della virgola decimale, e delle operazioni tra celle, ti invio il codice tramite email, ho fatto tutto in base alle mie conoscenze, spero che lo guardi e che migliori il codice ove è necessario, fammi sapere ciao.
Ottimo. :) Sì, adesso sicuramente è più funzionale e stabile di prima, anche nei casi in cui l'utente arrivi a lavorare su una sola Row, o anche ad eliminare tutte le Row e a reinserirne di nuove a mano. Darò senz'altro un'occhiata alla tua Mail. Ma intanto, anche grazie ai feedback come il tuo, aspetterei di avere la certezza sulla definitiva stabilità di quanto già c'è, e inoltre ad inserire anche la corrispondente versione C#.
A differenza della quasi-totalità dei miei Articoli, questo è nato in risposta a richieste di altri utenti su Forum tecnici, e non ho avuto ancora modo di usarlo ( e quindi "stressarlo" di persona ;) ) all'interno di applicazioni mie.
Ottimo.
Sarebbe bello avere un .zip del progetto, perchè con il copia e incolla del codice che hai messo è un casino.
Ciao :)
Ciao :).
Esattamente cosa trovi di così difficoltoso nel Copia/Incolla del codice ?
C'è una Guida all'uso del Blog :
http://marcoggblog.blogspot.com/p/informazioni.html
in cui ho chiaramente spiegato che basta fare Doppio-Click in ogni Code-Box per selezionare tutto il codice. Non è necessario scrollare...
Detto questo, arriveranno anche gli allegati agli Articoli, ma si vedrà più avanti.
Posta un commento