Visualizza il feed RSS

MarcoGG : Articoli Blog

[VB.NET] UserControl DataGridView con Riga Totali

Valuta questo inserimento
di pubblicato il 09-07-2011 alle 12:11 (5385 Visite)
Descrizione :
Una tecnica su UserControl per aggiungere un DataGridView Totali ad un DataGridView Dati.

Tratto da :
http://forum.masterdrive.it/visual-b...li-riga-56497/

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 : "FormTest"
-- Button "cmd_test"
-- Button "cmd_testnothing"
-- Button "cmd_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 :

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

    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)
            Dim tot As Double
            For i As Integer = 0 To m_totcolindices.Count - 1
                tot = m_datasource.Compute("SUM(" & m_datasource.Columns(m_totcolindices(i)).ColumnName & ")", Nothing)
                .Rows(0).Cells(m_totcolindices(i)).Value = tot
            Next

        End With

        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(sender As System.Object, 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(e As System.EventArgs)

        ResizeDGV()
        Me.Refresh()
        MyBase.OnResize(e)

    End Sub

    Private Sub DGV_ColumnWidthChanged(sender As Object, 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_CellValueChanged(sender As Object, e As System.Windows.Forms.DataGridViewCellEventArgs) Handles DGV.CellValueChanged

        If m_totcolindices Is Nothing Then Exit Sub
        If m_totcolindices.Contains(e.ColumnIndex) Then
            Dim tot As Double = m_datasource.Compute("SUM(" & m_datasource.Columns(e.ColumnIndex).ColumnName & ")", Nothing)
            DGV_totals.Rows(0).Cells(e.ColumnIndex).Value = tot
        End If

    End Sub

    Private Sub DGV_ColumnHeaderMouseClick(sender As Object, e As System.Windows.Forms.DataGridViewCellMouseEventArgs) Handles DGV.ColumnHeaderMouseClick

        DGV.HorizontalScrollingOffset = DGV_totals.HorizontalScrollingOffset

    End Sub

    Private Sub DGV_CellMouseClick(sender As Object, 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(sender As Object, 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(sender As Object, e As System.Windows.Forms.ScrollEventArgs) Handles DGV_totals.Scroll

        DGV.HorizontalScrollingOffset = DGV_totals.HorizontalScrollingOffset

    End Sub

End Class
A questo punto basta compilare, e su Form "FormTest" 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 FormTest, per vederlo all'opera.

--> Codice per FormTest :

codice:
Public Class FormTest

    Private Sub cmd_test_Click(sender As System.Object, e As System.EventArgs) Handles cmd_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 cmd_testnothing_Click(sender As System.Object, e As System.EventArgs) Handles cmd_testnothing.Click

        TDataGridView1.SetDataSource(Nothing, Nothing)

    End Sub

    Private Sub TDataGridView1_DGVCellRightClick(column As System.Int32, row As System.Int32, value As System.Object) Handles TDataGridView1.DGVCellRightClick

        MessageBox.Show("[" & column & ", " & row & "] = " & value.ToString)

    End Sub

    Private Sub cmd_testfont_Click(sender As System.Object, e As System.EventArgs) Handles cmd_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.

--> cmd_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 ).

Tag: vb.net
Categorie
Programmazione

Commenti