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.



21:59
MarcoGG


Posted in:
0 commenti:
Posta un commento