venerdì 24 febbraio 2012

[C#] UserControl DataGridView con Riga Totali

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 :

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Windows.Forms;

namespace UserControlDGVTotali
{
    public partial class TDataGridView : UserControl
    {
        public TDataGridView()
        {
            InitializeComponent();
        }

        //DataSource e Indici delle Colonne di cui si desiderano i Totali
        private DataTable m_datasource;
        private List<int> m_totcolindices;
        //Font comune 
        private Font m_dgvfont = new Font(System.Drawing.FontFamily.GenericSansSerif, 12);
        private Font m_dgvheadersfont = new Font(System.Drawing.FontFamily.GenericSansSerif, 12, FontStyle.Bold);

        public delegate void DGVCellRightClickEventHandler(int column, int row, object value);
        public event DGVCellRightClickEventHandler DGVCellRightClick;

        public Font DgvFont
        {
            get
            {
                return m_dgvfont;
            }
            set
            {
                try
                {
                    m_dgvheadersfont = new Font(value, FontStyle.Bold);
                    m_dgvfont = value;
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Impossibile usare questo Font.", "Azione annullata", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    return;
                }
                DGV.ColumnHeadersDefaultCellStyle.Font = m_dgvheadersfont;
                DGV.Font = m_dgvfont;
                DGV_totals.ColumnHeadersDefaultCellStyle.Font = m_dgvheadersfont;
                DGV_totals.Font = m_dgvfont;
                ResizeDGV();
            }
        }

        private void AggiornaTotali()
        {
            if (DGV_totals.RowCount == 0 || m_datasource == null)
            {
                return;
            }
            this.Validate();
            DGV.EndEdit();
            object oTot = null;
            double dTot = 0;
            for (int i = 0; i < m_totcolindices.Count; i++)
            {
                oTot = m_datasource.Compute("SUM(" + m_datasource.Columns[m_totcolindices[i]].ColumnName + ")", null);
                try
                {
                    dTot = Convert.ToDouble(oTot);
                }
                catch (Exception ex)
                {
                    dTot = 0;
                }
                DGV_totals.Rows[0].Cells[m_totcolindices[i]].Value = dTot;
            }
        }

        public void SetDataSource(DataTable dataSource, List<int> totColIndices)
        {
            m_totcolindices = totColIndices;
            m_datasource = dataSource;
            DGV.DataSource = m_datasource;

            DGV_totals.Rows.Clear();
            DGV_totals.Columns.Clear();
            if (m_datasource == null | m_totcolindices == null)
            {
                return;
            }
            for (int i = 0; i < DGV.Columns.Count; i++)
            {
                DGV_totals.Columns.Add(DGV.Columns[i].Name, "");
                DGV_totals.Columns[i].Width = DGV.Columns[i].Width;
                if (m_totcolindices.Contains(i))
                {
                    DGV_totals.Columns[i].HeaderText = "Totale";
                }
            }
            DataGridViewRow dgvr = new DataGridViewRow();
            dgvr.CreateCells(DGV);
            DGV_totals.Rows.Add(dgvr);
            
            AggiornaTotali();
            ResizeDGV();
        }

        public void SetColumnFormat(int columnIndex, string format)
        {
            try
            {
                DGV.Columns[columnIndex].DefaultCellStyle.Format = format;
                DGV_totals.Columns[columnIndex].DefaultCellStyle.Format = format;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Impossibile applicare questo Formato.", "Azione annullata", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        private void TDataGridView_Load(object sender, EventArgs e)
        {
            this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            this.MinimumSize = new Size(200, 100);

            DGV.AllowUserToResizeRows = false;
            DGV.ScrollBars = ScrollBars.Vertical;
            DGV.Top = 1;
            DGV.Left = 1;
            DGV.Width = this.Width - 4;
            DGV.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;

            DGV_totals.ReadOnly = true;
            DGV_totals.AllowUserToOrderColumns = false;
            DGV_totals.AllowUserToResizeColumns = true;
            DGV_totals.AllowUserToResizeRows = false;
            DGV_totals.ScrollBars = ScrollBars.Horizontal;
            DGV_totals.ColumnHeadersVisible = true;
            DGV_totals.RowHeadersVisible = DGV.RowHeadersVisible;
            DGV_totals.Left = DGV.Left;
            DGV_totals.Width = DGV.Width;
            DGV_totals.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom;
        }

        private void ResizeDGV()
        {
            DGV.AutoResizeRows();
            DGV_totals.AutoResizeRows();
            DGV_totals.Width = DGV.Width;
            if (DGV.Controls.OfType<VScrollBar>().SingleOrDefault<VScrollBar>().Visible == true )
            {
                DGV_totals.Width -= SystemInformation.VerticalScrollBarWidth;
            }
            if (DGV_totals.Rows.Count > 0)
            {
                DGV_totals.Height = DGV_totals.Rows[0].Height + 1;
            }
            else
            {
                DGV_totals.Height = DGV_totals.RowTemplate.Height + 1;
            }
            if (DGV_totals.ColumnHeadersVisible == true)
            {
                DGV_totals.Height += DGV_totals.ColumnHeadersHeight;
            }
            if (DGV_totals.Controls.OfType<HScrollBar>().SingleOrDefault<HScrollBar>().Visible == true)
            {
                DGV_totals.Height += SystemInformation.HorizontalScrollBarHeight;
            }
            DGV_totals.Top = this.Height - DGV_totals.Height - 3;
            DGV.Height = this.Height - DGV.Top - DGV_totals.Height - 2;
        }

        protected override void OnResize(System.EventArgs e)
        {
            ResizeDGV();
            this.Refresh();
            base.OnResize(e);
        }

        private void DGV_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
        {
            DGV_totals.Columns[e.Column.Index].Width = DGV.Columns[e.Column.Index].Width;
            ResizeDGV();
            DGV_totals.HorizontalScrollingOffset = DGV.HorizontalScrollingOffset;
        }

        private void DGV_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            DGV.HorizontalScrollingOffset = DGV_totals.HorizontalScrollingOffset;
        }

        private void DGV_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                if (DGVCellRightClick != null)
                    DGVCellRightClick(e.ColumnIndex, e.RowIndex, DGV[e.ColumnIndex, e.RowIndex].Value);
            }
        }

        private void DGV_totals_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
        {
            DGV.Columns[e.Column.Index].Width = DGV_totals.Columns[e.Column.Index].Width;
            ResizeDGV();
            DGV_totals.HorizontalScrollingOffset = DGV.HorizontalScrollingOffset;
        }

        private void DGV_totals_Scroll(object sender, ScrollEventArgs e)
        {
            DGV.HorizontalScrollingOffset = DGV_totals.HorizontalScrollingOffset;
        }

        private void DGV_CellValueChanged(object sender, DataGridViewCellEventArgs e)
        {
            AggiornaTotali();
        }

        private void DGV_RowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
        {
            AggiornaTotali();
        }

        private void DGV_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
        {
            AggiornaTotali();
        }

    }
}

A 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 :

using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

namespace UserControlDGVTotali
{
    public partial class FormMain : Form
    {
        public FormMain()
        {
            InitializeComponent();
        }

        private void btn_test_Click(object sender, EventArgs e)
        {
            //DataTable
            DataTable DT = new DataTable();
            DT.Columns.Add("ID", typeof(int));
            DT.PrimaryKey = new DataColumn[] { DT.Columns["ID"] };
            DT.Columns.Add("Campo1", typeof(string));
            DT.Columns.Add("Campo2", typeof(int));
            DT.Columns.Add("Campo3", typeof(float));
            DT.Columns.Add("Campo4", typeof(double));
            for (int i = 1; i <= 50; i++)
            {
                DT.Rows.Add(new object[] { i, "Campo1_" + i, i, 1.23 * i, 12.3456 * i });
            }

            //TDataGridView1
            tDataGridView1.SetDataSource(DT, new List<int>(new[] { 2, 3, 4 }));

            //Formati e altre proprietà ...
            //...
            tDataGridView1.SetColumnFormat(3, "N2");
            tDataGridView1.SetColumnFormat(4, "N4");
            //...

            //Test Nomi Colonne
            System.Text.StringBuilder SB = new System.Text.StringBuilder();
            for (int i = 0; i < DT.Columns.Count; i++)
            {
                SB.Append(DT.Columns[i].ColumnName + " / " + tDataGridView1.DGV.Columns[i].Name + " / " + tDataGridView1.DGV_totals.Columns[i].Name + Environment.NewLine);
            }
            MessageBox.Show(SB.ToString());
        }

        private void btn_testnothing_Click(object sender, EventArgs e)
        {
            tDataGridView1.SetDataSource(null, null);
        }

        private void btn_testfont_Click(object sender, EventArgs e)
        {
            using (FontDialog FD = new FontDialog())
            {
                FD.Font = tDataGridView1.DgvFont;
                if (FD.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    tDataGridView1.DgvFont = FD.Font;
                }
            }
        }

        private void tDataGridView1_DGVCellRightClick(int column, int row, object value)
        {
            MessageBox.Show("[" + column + ", " + row + "] = " + value.ToString());
        }

    }
}


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

Un Click su "Mi Piace" è il modo migliore per ringraziare l'autore di questo articolo.



0 commenti:

Posta un commento

 
Design by Free WordPress Themes Modificato da MarcoGG