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.
0 commenti:
Posta un commento