Visualizzazione post con etichetta C#. Mostra tutti i post
Visualizzazione post con etichetta C#. Mostra tutti i post

domenica 26 febbraio 2012

[C#] DataGridView e MultiSelezioni

Descrizione :
Una mia tecnica per gestire la Multi-Selezione utente su DataGridView.

+ Articolo :

Spesso nei Forum Tecnici capita di leggere richieste sull'utilizzo avanzato del Controllo DataGridView, sempre molto utile e adatto alla rappresentazione di dati in svariate situazioni.
In questo Articolo penso di dare una risposta pratica e abbastanza completa sul problema di riuscire a selezionare con il Mouse, ma non solo ( la tecnica seguente tiene conto anche della selezione via tastiera e via codice... ) i dati presenti in un DataGridView connesso ad un DataTable, mantenendo tale selezione attiva.
Non sono quindi necessarie colonne CheckBox aggiuntive nè combinazioni di tasti ( niente CTRL ) per eseguire le selezioni e inoltre le Righe o Celle selezionate vengono automaticamente raccolte in opportune List() di riferimento che rimangono sempre aggiornate e pronte al prelievo dei dati.

Per replicare l'esempio seguente basta predisporre la solita Application Windows Forms con una Form "FormMain".
Su FormMain servono 4 controlli. Nell'ordine da sinistra a destra sono :
--> DataGridView : DGV1.
--> ListBox : lst_dgv1.
--> DataGridView : DGV2.
--> ListBox : lst_dgv2.

- DGV1 rappresenta i dati del proprio DataSource ( DT1 ), e mostra la tecnica sulla selezione delle Rows.
Le Rows selezionate e raccolte nella propria List() "selectedRows", sono mostrate nella lst_dgv1.
- Analogamente, DGV2 rappresenta i dati del proprio DataSource ( DT2 ), e mostra la tecnica sulla selezione delle Cells.
Le Cells selezionate e raccolte nella propria List() "selectedCells", sono mostrate nella lst_dgv2.
Perciò le due ListBox qui hanno scopo di verifica costante e visibile di quanto avviene dietro le quinte.

Non occorre impostare alcuna proprietà dei controlli a design, in quanto tutto viene definito al Load().

La figura seguente mostra un esempio di utilizzo a runtime :


--> Codice FormMain :

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

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

        //DataTable per DGV1
        private DataTable DT1 = new DataTable();
        //DataTable per DGV1
        private DataTable DT2 = new DataTable();
        //SelectedIndices per Rows su DGV1
        private List<DataGridViewRow> selectedRows = new List<DataGridViewRow>();
        //SelectedCells per Cells su DGV2
        private List<DataGridViewCell> selectedCells = new List<DataGridViewCell>();

        private void FormMain_Load(object sender, EventArgs e)
        {
            //Impostazioni DGV1 - Dimostrazione con Rows
            DGV1.MultiSelect = true;
            DGV1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            DGV1.DefaultCellStyle.SelectionBackColor = Color.Red;
            DGV1.DefaultCellStyle.Font = new Font(System.Drawing.FontFamily.GenericSansSerif, 12);
            DGV1.ReadOnly = false;
            DGV1.AllowUserToAddRows = false;
            DGV1.AllowUserToDeleteRows = true;
            DGV1.DataSource = DT1;

            //Impostazioni DGV2 - Dimostrazione con Celle
            DGV2.MultiSelect = true;
            DGV2.SelectionMode = DataGridViewSelectionMode.CellSelect;
            DGV2.DefaultCellStyle.SelectionBackColor = Color.Green;
            DGV2.DefaultCellStyle.Font = new Font(System.Drawing.FontFamily.GenericSansSerif, 12);
            DGV2.ReadOnly = false;
            DGV2.AllowUserToAddRows = false;
            DGV2.AllowUserToDeleteRows = true;
            DGV2.DataSource = DT2;

            //DataTable per DGV1
            //Colonna PK
            DT1.Columns.Add("ID");
            DT1.PrimaryKey = new DataColumn[] {DT1.Columns["ID"]};
            //Altre Colonne
            for (int i = 1; i <= 9; i++)
            {
                DT1.Columns.Add("Colonna_" + i.ToString().PadLeft(2, '0'));
            }
            //...
            //Inserimento Rows
            DataRow R = null;
            for (int i = 0; i <= 9; i++)
            {
                R = DT1.NewRow();
                R[0] = i;
                for (int j = 1; j < DT1.Columns.Count; j++)
                {
                    R[j] = "Cella_" + j;
                }
                DT1.Rows.Add(R);
            }

            //DataTable per DGV2
            //Colonna PK
            DT2.Columns.Add("ID");
            DT2.PrimaryKey = new DataColumn[] {DT2.Columns["ID"]};
              //Altre Colonne
            for (int i = 1; i <= 9; i++)
            {
                DT2.Columns.Add("Colonna_" + i.ToString().PadLeft(2, '0'));
            }
            //...
            //Inserimento Rows
            for (int i = 0; i <= 9; i++)
            {
                R = DT2.NewRow();
                R[0] = i;
                for (int j = 1; j < DT2.Columns.Count; j++)
                {
                    R[j] = "Cella_" + j;
                }
                DT2.Rows.Add(R);
            }
        }

        private void CheckCurrentDgv1()
        {
            if (DGV1.CurrentRow == null)
            {
                return;
            }
            if (selectedRows.Contains(DGV1.CurrentRow))
            {
                selectedRows.Remove(DGV1.CurrentRow);
            }
            else
            {
                selectedRows.Add(DGV1.CurrentRow);
            }
            SelezioneDgv1();
        }

        private void SelezioneDgv1()
        {
            foreach (DataGridViewRow dgvr in DGV1.Rows)
            {
                if (selectedRows.Contains(dgvr))
                {
                    dgvr.Selected = true;
                }
                else
                {
                    dgvr.Selected = false;
                }
            }
            lst_dgv1.Items.Clear();
            lst_dgv1.Items.AddRange(selectedRows.ToArray());
        }

        private void DGV1_CurrentCellChanged(object sender, EventArgs e)
        {
            CheckCurrentDgv1();
        }

        private void DGV1_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
        {
            SelezioneDgv1();
        }

        private void DGV1_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e)
        {
            if (selectedRows.Contains(e.Row))
            {
                selectedRows.Remove(e.Row);
            }
            SelezioneDgv1();
        }

        private void DGV1_DoubleClick(object sender, EventArgs e)
        {
            CheckCurrentDgv1();
        }

        private void CheckCurrentDgv2()
        {
            if (DGV2.CurrentCell == null)
            {
                return;
            }
            if (selectedCells.Contains(DGV2.CurrentCell))
            {
                selectedCells.Remove(DGV2.CurrentCell);
            }
            else
            {
                selectedCells.Add(DGV2.CurrentCell);
            }
            SelezioneDgv2();
        }

        private void SelezioneDgv2()
        {
            foreach (DataGridViewRow dgvr in DGV2.Rows)
            {
                foreach (DataGridViewCell dgvc in dgvr.Cells)
                {
                    if (selectedCells.Contains(dgvc))
                    {
                        dgvc.Selected = true;
                    }
                    else
                    {
                        dgvc.Selected = false;
                    }
                }
            }
            lst_dgv2.Items.Clear();
            lst_dgv2.Items.AddRange(selectedCells.ToArray());
        }

        private void DGV2_CurrentCellChanged(object sender, EventArgs e)
        {
            CheckCurrentDgv2();
        }

        private void DGV2_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
        {
            SelezioneDgv2();
        }

        private void DGV2_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e)
        {
            foreach (DataGridViewCell dgvc in e.Row.Cells)
            {
                if (selectedCells.Contains(dgvc))
                {
                    selectedCells.Remove(dgvc);
                }
            }
            SelezioneDgv2();
        }

        private void DGV2_DoubleClick(object sender, EventArgs e)
        {
            CheckCurrentDgv2();
        }


    }
}

Per quanto riguarda la parte Dati il codice è già ben commentato.
Con un paio di semplici Metodi scritti ad hoc e 4 Eventi ( comunque molto compatti ), il gioco è fatto.
Come si può notare, la tecnica tiene conto anche dell'eventuale eliminazione di Righe da parte dell'utente.
Inoltre L'Edit di Cella funziona in ogni caso, con Riga o Cella selezionata e non, e come è possibile notare ...


... non reca disturbi sulle pregresse selezioni.

Ovviamente il passo successivo ( che lascio ai lettori ) sarà quello di crearsi una propria Classe che eredita da DataGridView, e che conterrà questi ed altri Metodi ed Eventi, a seconda delle esigenze.

+ Fine Articolo.

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



sabato 25 febbraio 2012

[C#] Operazioni Base Access

Descrizione :
Raccolta di suggerimenti e tecniche base con System.Data.OleDb e DB Access.

+ Articolo :

Nonostante Microsoft stessa abbia dichiarato che non ci sarà ( mai ) un supporto Entity Framework per i database Access, e le non poche scomodità ( MDAC, Jet, limite file a 2 GB, ecc... ), molti utenti .NET, nel momento in cui si rende necessario disporre di un DB file based locale, puntano ancora istintivamente su Access.

Ho deciso di scrivere questo Articolo Blog, sperando di dare anche il mio contributo, con il solito spirito "propositivo", che in questo caso vale un po' come un : "Se lo vuoi/devi fare, almeno fallo nel modo giusto...".

"Modo giusto" in questo caso significa principalmente :

1. NON usare ADODB in .NET : non ha senso usare COM in .NET, a meno che non si sia costretti a farlo, e questo non è il caso, perchè per Access c'è un signor Provider Dati : System.Data.OleDb.

2. NON costruire le Query concatenando dati e direttive Sql : OleDb supporta tutti i tipi Access, e anche di più attraverso l'uso dei Named Parameters. Nessun bisogno di perdere tempo a combattere con le date Access e i suoi "famosi" formati ( MM/dd/yyyy, yyyy/MM/dd, "cancelletti" #, ecc... ), o dover gestire i numerosi casi ambigui con le stringhe racchiuse tra apici, separatori decimali, di migliaia, ecc.

3. NON costruire le Stringhe di Connessione al file DB concatenando percorsi e proprietà : forse pochi sanno che, allo stesso modo di quanto accade per System.Data.SqlClient, esiste una comoda Classe OleDbConnectionStringBuilder.

4. NON usare Controlli tipo-Lista per rappresentare tabelle Dati : mi riferisco soprattutto all'abuso delle ListView. 9 utenti su 10 che ho visto perdersi nella gestione "avanzata" di una ListView-Dati... Non hanno fatto più ritorno...

In questo Articolo i controlli sono ReadOnly. Uso una TextBox Multiline e un DataGridView.
Ho scelto di non gestire per nulla l'interazione dell'utente con i Controlli destinati a visualizzare i risultati delle varie Query e azioni, perchè ho intenzione di parlare dell'uso del DataGridView in un altro Articolo a sè stante.

--> DataBase :

Il DB di test è un semplice file Access 2003 : "testDB.mdb".
Ho scelto questa versione perchè Office 2003 è sempre molto diffuso e l'esempio seguente può facilmente essere replicato sulle versioni successive ( .accdb ) senza problemi, aggiornando la Stringa di Connessione.

In Standard Security ciò significa in pratica questo :

//
//Standard Security

//Access 2003
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\myAccess2003file.mdb;User Id=admin;Password=;
//Oppure anche :
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\myAccess2003file.mdb;Persist Security Info=False;

//Access 2007
Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\myAccess2007file.accdb;Persist Security Info=False;

//Access 2010
Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\myAccess2010file.accdb;Persist Security Info=False;
//

Non è ovviamente scopo di questo Articolo coprire i vari casi con le differenti installazioni di Windows ( vedi ad esempio i possibili problemi con il Provider ACE.OLEDB.12 in S.O. 64 bit, ecc... ), i driver di Access da installare, ecc...
Come già accennato, questa Stringa di Connessione verrà invece qui costruita "pezzo per pezzo" grazie a :

//OleDb.OleDbConnectionStringBuilder
Publics.oleDbCnStr.Provider = "Microsoft.Jet.OLEDB.4.0";
Publics.oleDbCnStr.DataSource = Publics.percorsoLocale + Publics.nomeFileDB;
Publics.oleDbCnStr.PersistSecurityInfo = false;
Publics.oleDbCn.ConnectionString = Publics.oleDbCnStr.ToString();

La Struttura della Tabella "T_Persone" è semplice, ma al tempo stesso contiene vari tipi di dato in modo da poter dimostrare come l'uso dei Parameters semplifica enormemente la costruzione delle Query :

id ( PK ) - Numerico
nome - Testo
cognome - Testo
datanascita - Data/ora
contatti - Numerico

Faccio notare che "id" NON è di tipo Contatore.
Nell'esempio ho inserito anche un semplice sistema per ridurre al minimo il rischio di errore di duplicazione della PK.

--> Applicazione :

L'Applicazione è costituita da 2 Classi : una Classe Statica "Publics" e una Form "FormMain", che è l'oggetto di avvio.

- Codice Publics :
using System;
using System.Data.OleDb;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Drawing;

namespace OperazioniAccess
{
    internal static class Publics
    {
        //Percorso Locale in cui è situato il DB Access
        public static string percorsoLocale = Application.StartupPath + "\\";
        //Percorso LAN in cui è situato il DB Access
        public static string nomeMacchina = "nomeMacchinaLAN";
        public static string nomeDirCondivisa = "nomeDirCondivisaLAN";
        public static string percorsoLan = "\\\\" + nomeMacchina + "\\" + nomeDirCondivisa + "\\";
        //Nome file Access compresa estensione
        public static string nomeFileDB = "testDB.mdb";

        public static OleDbConnectionStringBuilder oleDbCnStr = new OleDbConnectionStringBuilder();
        public static OleDbConnection oleDbCn = new OleDbConnection();

        public static Font F = new Font(FontFamily.GenericMonospace, 12, FontStyle.Bold);
    }
}

- FormMain :


I Controlli utilizzati sono, dall'alto a sinistra :

--> Button : btn_selectparampclan
--> Button : btn_selectparam
--> Button : btn_selectparamdgv

--> Button : btn_selectmaxid
--> Button : btn_selectstar
--> Button : btn_selectstardgv

--> Button : btn_insertparam
--> Button : btn_updateparam
--> Button : btn_deleteparam

--> TextBox : txt_risultati
--> DataGridView : DGV

- Codice FormMain :

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

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

        private int maxId;

        private void setMaxId()
        {
            string strSql = "SELECT MAX(id) FROM T_Persone";
            using (OleDbCommand CMD = new OleDbCommand(strSql, Publics.oleDbCn))
            {
                Publics.oleDbCn.Open();
                maxId = (int)CMD.ExecuteScalar();
                Publics.oleDbCn.Close();
            }
        }

        private void FormMain_Load(object sender, EventArgs e)
        {
            //OleDb.OleDbConnectionStringBuilder
            Publics.oleDbCnStr.Provider = "Microsoft.Jet.OLEDB.4.0";
            Publics.oleDbCnStr.DataSource = Publics.percorsoLocale + Publics.nomeFileDB;
            Publics.oleDbCnStr.PersistSecurityInfo = false;
            Publics.oleDbCn.ConnectionString = Publics.oleDbCnStr.ToString();

            txt_risultati.Font = Publics.F;
            txt_risultati.Multiline = true;

            DGV.AllowUserToAddRows = false;
            DGV.ReadOnly = true;
            DGV.Font = Publics.F;

            setMaxId();
        }

        private void btn_selectparampclan_Click(object sender, EventArgs e)
        {
            //Modifica SOLO al .DataSource di oleDbCnStr
            Publics.oleDbCnStr.DataSource = Publics.percorsoLan + Publics.nomeFileDB;

            System.Text.StringBuilder risultati = new System.Text.StringBuilder();
            string strSql = "SELECT * FROM T_Persone WHERE datanascita>=@datanascita";

            using (OleDbCommand CMD = new OleDbCommand(strSql, Publics.oleDbCn))
            {
                //Definizione / Assegnazione
                CMD.Parameters.Add("@datanascita", OleDbType.Date).Value = new DateTime(1967, 12, 25);
                //... altri Parametri

                Publics.oleDbCn.Open();
                using (OleDbDataReader RDR = CMD.ExecuteReader())
                {

                    //Lettura Campi DataReader
                    for (int i = 0; i < RDR.FieldCount; i++)
                    {
                        risultati.Append(RDR.GetName(i).PadRight(19, ' '));
                    }
                    risultati.AppendLine().AppendLine();
                    //Lettura Campi Query
                    while (RDR.Read())
                    {
                        for (int i = 0; i < RDR.FieldCount; i++)
                        {
                            risultati.Append(RDR[i].ToString().PadRight(19, ' '));
                        }
                        risultati.AppendLine();
                    }
                }
                Publics.oleDbCn.Close();
            }
            txt_risultati.Text = risultati.ToString();
        }

        private void btn_selectparam_Click(object sender, EventArgs e)
        {
            System.Text.StringBuilder risultati = new System.Text.StringBuilder();
            string strSql = "SELECT * FROM T_Persone WHERE datanascita>=@datanascita AND cognome LIKE @cognome";

            using (OleDbCommand CMD = new OleDbCommand(strSql, Publics.oleDbCn))
            {
                //Definizione / Assegnazione
                CMD.Parameters.Add("@datanascita", OleDbType.Date).Value = new DateTime(1967, 12, 25);
                CMD.Parameters.Add("@cognome", OleDbType.VarChar).Value = "Ro" + "%";
                //... altri Parametri

                Publics.oleDbCn.Open();
                using (OleDbDataReader RDR = CMD.ExecuteReader())
                {

                    //Lettura Campi DataReader
                    for (int i = 0; i < RDR.FieldCount; i++)
                    {
                        risultati.Append(RDR.GetName(i).PadRight(19, ' '));
                    }
                    risultati.AppendLine().AppendLine();
                    //Lettura Campi Query
                    while (RDR.Read())
                    {
                        for (int i = 0; i < RDR.FieldCount; i++)
                        {
                            risultati.Append(RDR[i].ToString().PadRight(19, ' '));
                        }
                        risultati.AppendLine();
                    }
                }
                Publics.oleDbCn.Close();
            }

            txt_risultati.Text = risultati.ToString();
        }

        private void btn_selectparamdgv_Click(object sender, EventArgs e)
        {
            //Reset DGV
            DGV.Rows.Clear();
            DGV.Columns.Clear();

            DataGridViewRow dgvr = null;
            string strSql = "SELECT * FROM T_Persone WHERE datanascita>=@datanascita AND cognome LIKE @cognome";

            using (OleDbCommand CMD = new OleDbCommand(strSql, Publics.oleDbCn))
            {
                //Definizione / Assegnazione
                CMD.Parameters.Add("@datanascita", OleDbType.Date).Value = new DateTime(1967, 12, 25);
                CMD.Parameters.Add("@cognome", OleDbType.VarChar).Value = "Ro" + "%";
                //... altri Parametri

                Publics.oleDbCn.Open();
                using (OleDbDataReader RDR = CMD.ExecuteReader())
                {
                    //Lettura Campi DataReader
                    for (int i = 0; i < RDR.FieldCount; i++)
                    {
                        DGV.Columns.Add(RDR.GetName(i).ToString(), RDR.GetName(i).ToString());
                    }
                    //Lettura Campi Query
                    while (RDR.Read())
                    {
                        dgvr = new DataGridViewRow();
                        dgvr.CreateCells(DGV);
                        for (int i = 0; i < RDR.FieldCount; i++)
                        {
                            dgvr.Cells[i].Value = RDR[i].ToString();
                        }
                        DGV.Rows.Add(dgvr);
                    }
                }
                Publics.oleDbCn.Close();
            }
        }

        private void btn_selectmaxid_Click(object sender, EventArgs e)
        {
            setMaxId();
            MessageBox.Show(maxId.ToString());
        }

        private void btn_selectstar_Click(object sender, EventArgs e)
        {
            System.Text.StringBuilder risultati = new System.Text.StringBuilder();
            string strSql = "SELECT * FROM T_Persone";

            using (OleDbCommand CMD = new OleDbCommand(strSql, Publics.oleDbCn))
            {
                Publics.oleDbCn.Open();
                using (OleDbDataReader RDR = CMD.ExecuteReader())
                {
                    //Lettura Campi DataReader
                    for (int i = 0; i < RDR.FieldCount; i++)
                    {
                        risultati.Append(RDR.GetName(i).PadRight(19, ' '));
                    }
                    risultati.AppendLine().AppendLine();
                    //Lettura Campi Query
                    while (RDR.Read())
                    {
                        for (int i = 0; i < RDR.FieldCount; i++)
                        {
                            risultati.Append(RDR[i].ToString().PadRight(19, ' '));
                        }
                        risultati.AppendLine();
                    }
                }
                Publics.oleDbCn.Close();
            }
            txt_risultati.Text = risultati.ToString();
        }

        private void btn_selectstardgv_Click(object sender, EventArgs e)
        {
            //Reset DGV
            DGV.Rows.Clear();
            DGV.Columns.Clear();

            DataGridViewRow dgvr = null;
            string strSql = "SELECT * FROM T_Persone";

            using (OleDbCommand CMD = new OleDbCommand(strSql, Publics.oleDbCn))
            {
                Publics.oleDbCn.Open();
                using (OleDbDataReader RDR = CMD.ExecuteReader())
                {
                    //Lettura Campi DataReader
                    for (int i = 0; i < RDR.FieldCount; i++)
                    {
                        DGV.Columns.Add(RDR.GetName(i).ToString(), RDR.GetName(i).ToString());
                    }
                    //Lettura Campi Query
                    while (RDR.Read())
                    {
                        dgvr = new DataGridViewRow();
                        dgvr.CreateCells(DGV);
                        for (int i = 0; i < RDR.FieldCount; i++)
                        {
                            dgvr.Cells[i].Value = RDR[i].ToString();
                        }
                        DGV.Rows.Add(dgvr);
                    }
                }
                Publics.oleDbCn.Close();
            }
        }

        private void btn_insertparam_Click(object sender, EventArgs e)
        {
            string strSql = "INSERT INTO T_Persone (id, nome, cognome, datanascita, contatti) " + "VALUES (@id, @nome, @cognome, @datanascita, @contatti)";

            using (OleDbCommand CMD = new OleDbCommand(strSql, Publics.oleDbCn))
            {
                //Definizione / Assegnazione
                CMD.Parameters.Add("@id", OleDbType.Integer).Value = maxId + 1;
                CMD.Parameters.Add("@nome", OleDbType.VarChar).Value = "insertNome";
                CMD.Parameters.Add("@cognome", OleDbType.VarChar).Value = "insertCognome";
                CMD.Parameters.Add("@datanascita", OleDbType.Date).Value = new DateTime(1970, 1, 31);
                CMD.Parameters.Add("@contatti", OleDbType.Integer).Value = 12;

                Publics.oleDbCn.Open();
                CMD.ExecuteNonQuery();
                Publics.oleDbCn.Close();
            }
            setMaxId();
            MessageBox.Show("INSERT OK");
        }

        private void btn_updateparam_Click(object sender, EventArgs e)
        {
            string strSql = "UPDATE T_Persone SET nome=@nome, cognome=@cognome, datanascita=@datanascita, contatti=@contatti " + "WHERE id=@id";

            using (OleDbCommand CMD = new OleDbCommand(strSql, Publics.oleDbCn))
            {
                //Definizione / Assegnazione
                CMD.Parameters.Add("@nome", OleDbType.VarChar).Value = "updateNome";
                CMD.Parameters.Add("@cognome", OleDbType.VarChar).Value = "updateCognome";
                CMD.Parameters.Add("@datanascita", OleDbType.Date).Value = new DateTime(1968, 8, 8);
                CMD.Parameters.Add("@contatti", OleDbType.Integer).Value = 8;
                CMD.Parameters.Add("@id", OleDbType.Integer).Value = maxId;

                Publics.oleDbCn.Open();
                CMD.ExecuteNonQuery();
                Publics.oleDbCn.Close();
            }
            MessageBox.Show("UPDATE OK");
        }

        private void btn_deleteparam_Click(object sender, EventArgs e)
        {
            string strSql = "DELETE FROM T_Persone WHERE id=@id";

            using (OleDbCommand CMD = new OleDbCommand(strSql, Publics.oleDbCn))
            {
                //Definizione / Assegnazione
                CMD.Parameters.Add("@id", OleDbType.Integer).Value = maxId;

                Publics.oleDbCn.Open();
                CMD.ExecuteNonQuery();
                Publics.oleDbCn.Close();
            }
            setMaxId();
            MessageBox.Show("DELETE OK");
        }

    }
}

NOTE :
In conclusione, dopo aver replicato correttamente e provato il codice, ci si può fare un'idea su molti aspetti della gestione di un DB su file Access :

- Uso di OleDbConnectionStringBuilder.
- UNA Connessione sola, che viene riutilizzata a livello di Progetto.
- Utilizzo di un DataReader per le operazioni di lettura dati.
- Utilizzo di Named Parameters per TUTTE le operazioni.
- Uso del corretto Metodo Command a seconda del caso, e in particolare :
-- .ExecuteReader() per letture ( SELECT ... ) che prevedono la restituzione di più records.
-- .ExecuteScalar() per letture ( SELECT MAX, SELECT COUNT, ... ) che prevedono con certezza la restituzione di UN solo valore.
-- .ExecuteNonQuery() per le 3 Query di Azione ( INSERT, UPDATE, DELETE ).

Faccio Infine notare che una certa prolissità del codice Form è dovuta al fatto che, come faccio spesso per evitare lunghe liste di controlli-proprietà da impostare a Design, le Proprietà importanti sono definite nel codice stesso. Se oltre a ciò si rende davvero elementare la visualizzazione dei risultati ( ad esempio eliminando la "txt_risultati" ) il codice veramente essenziale si riduce ulteriormente.

+ Fine Articolo.


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




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.



martedì 7 febbraio 2012

[C#] Spostamento Arbitrario Rows in DataTable DataGridView

Descrizione :
Una tecnica valida per modificare l'indice di una o più righe in un DataTable / DataGridView, mantenendo la possibilità di usare l'ordinamento manuale sui ColumnHeaders.

+ Articolo :

Caso singolo : per completezza ho lasciato anche il caso di spostamento di una singola Row, dal suo indice di partenza x all'indice arbitrario desiderato y.

Caso generico : spostare N Rows in DataTable / DGV dagli indici di partenza ( x1, x2, ..., xn ) ai nuovi indici arbitrari ( y1, y2, ..., yn ).

La corrispondenza per ciascuna Row in gioco è chiaramente :
x1, y1
x2, y2
..., ...
xn, yn

Per fare ciò definisco un insieme ordinabile di coppie di valori Integer, in cui il primo valore ( Key ) è xi ( indice iniziale della Row ), e il secondo valore ( Value ) è yi ( indice arbitrario di destinazione della Row ). Ho scelto un Dictionary.

Per replicare l'esempio seguente basta una Form "FormMain" con i seguenti controlli :
--> DataGridView : DGV
--> Button : btn_movesinglerow
--> Button : btn_movemultirows

--> Codice FormMain :

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows.Forms;
 
namespace SpostamentoArbitrarioRows
{
    public partial class FormMain : Form
    {
        public FormMain()
        {
            InitializeComponent();
        }
 
        private DataTable DT = new DataTable();
 
        private void FormMain_Load(object sender, EventArgs e)
        {
            //Colonna PK
            DT.Columns.Add("ID");
            DT.PrimaryKey = new DataColumn[] { DT.Columns["ID"] };
  
            //Altre Colonne
            DT.Columns.Add("Cognome");
            DT.Columns.Add("Nome");
            //...
 
            //Inserimento Rows
            for (int i = 0; i <= 9; i++)
            {
                DT.Rows.Add(i, "Cognome_" + i, "Nome_" + i);
            }
 
            DGV.DataSource = DT;
 
            //Disabilita l'ordinamento manuale e lo rende attivo solo via codice
            for (int i = 0; i < DGV.Columns.Count; i++)
            {
                DGV.Columns[i].SortMode = DataGridViewColumnSortMode.Programmatic;
            }
        }
 
        private void DGV_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            if (DGV.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection == SortOrder.Ascending)
            {
                DGV.Sort(DGV.Columns[e.ColumnIndex], System.ComponentModel.ListSortDirection.Descending);
                DT.DefaultView.Sort = DGV.Columns[e.ColumnIndex].Name + " DESC";
            }
            else
            {
                DGV.Sort(DGV.Columns[e.ColumnIndex], System.ComponentModel.ListSortDirection.Ascending);
                DT.DefaultView.Sort = DGV.Columns[e.ColumnIndex].Name + " ASC";
            }
 
            DT = DT.DefaultView.ToTable();
        }
 
        private void btn_movesinglerow_Click(object sender, EventArgs e)
        {
            int prevIndex = 0;
            int newIndex = 2;
 
            if (prevIndex < 0 | prevIndex >= DT.Rows.Count)
            {
                MessageBox.Show("Indice di origine non corretto.", "Azione annullata.", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
            if (newIndex < 0 | newIndex >= DT.Rows.Count)
            {
                MessageBox.Show("Indice di destinazione non corretto.", "Azione annullata.", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
 
            DataRow drTemp = DT.NewRow();
            drTemp.ItemArray = DT.Rows[prevIndex].ItemArray;
            DT.Rows.RemoveAt(prevIndex);
            DT.Rows.InsertAt(drTemp, newIndex);
 
            DGV.DataSource = DT;
        }
 
        private void btn_movemultirows_Click(object sender, EventArgs e)
        {
            List<DataRow> dRowsTemp = new List<DataRow>();
            DataRow dRowTemp = null;
            Dictionary<int, int> indices = new Dictionary<int, int>();
 
            indices.Add(1, 2);
            indices.Add(6, 1);
            indices.Add(4, 3);
            indices.Add(9, 5);
 
            if (indices.Keys.Min() < 0 | indices.Keys.Max() >= DT.Rows.Count)
            {
                MessageBox.Show("Indice di origine non corretto.", "Azione annullata.", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
            if (indices.Values.Min() < 0 | indices.Values.Max() >= DT.Rows.Count)
            {
                MessageBox.Show("Indice di destinazione non corretto.", "Azione annullata.", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
 
            //Copia
            indices = indices.OrderBy((I) => I.Value).ToDictionary((i) => i.Key, (I) => I.Value);
            for (int i = 0; i < indices.Count; i++)
            {
                dRowTemp = DT.NewRow();
                dRowTemp.ItemArray = DT.Rows[indices.ElementAt(i).Key].ItemArray;
                dRowsTemp.Add(dRowTemp);
            }
            //Eliminazione
            indices = indices.OrderByDescending((I) => I.Key).ToDictionary((i) => i.Key, (I) => I.Value);
            for (int i = 0; i < indices.Count; i++)
            {
                DT.Rows.RemoveAt(indices.ElementAt(i).Key);
            }
            //Re-Inserimento
            indices = indices.OrderBy((I) => I.Value).ToDictionary((i) => i.Key, (I) => I.Value);
            for (int i = 0; i < indices.Count; i++)
            {
                DT.Rows.InsertAt(dRowsTemp[i], indices.ElementAt(i).Value);
            }
 
            DGV.DataSource = DT;
        }
  
    }
}

Nell'esempio generico ( cmd_movemultirows ) l'operazione viene eseguita su tutte le coppie aggiunte in indices :
- La Row ad indice 1 passa all'indice 2.
- La Row ad indice 6 passa all'indice 1.
- La Row ad indice 4 passa all'indice 3.
- La Row ad indice 9 passa all'indice 5.

Trattandosi di un Dictionary viene già assicurato che i valori degli indici di partenza ( Keys ) possano essere inseriti una volta sola ( due o più coppie con stessa Key non avrebbero senso ).

Nel caso ci siano due o più coppie con stesso Value, quello "che vince" ( e che si troverà all'indice desiderato ) è l'ultimo inserito. Gli altri ovviamente non potranno rispettare la regola. Basta comunque un semplice controllo aggiuntivo su indices.Values.Contains() in fase di inserimento per scongiurare il problema.

+ Fine Articolo.


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



[C#] Utilizzare Resources

Descrizione :
Un mio esempio pratico per utilizzare files di Risorse.

+ Articolo :

Argomento sicuramente già molto documentato sul Web.
I Files di Risorsa permettono di incorporare suoni, immagini, testi e quant'altro nell'eseguibile stesso.
Di documentazione in giro ce n'è tanta. Quello che forse manca, a mio avviso, sono esempi chiari e definitivi.
Molti "tutorial" poi, all'atto pratico, non funzionano, oppure utilizzano codice eccessivo, o ancora eseguono percorsi che secondo me si possono evitare tranquillamente.
Perciò inserisco questo semplice Articolo, con la dimostrazione pratica di alcune mie convinzioni sull'argomento :

1. Nessun bisogno di "ricavare" il file dalla Risorsa in cui è stato incluso, andando a ricostruire il nome originario, concatenando il percorso ( Assembly ), il nome, e addirittura l'estensione originaria.

2. Tutto può essere gestito seguendo la semplice logica dei pochi Oggetti e Metodi davvero necessari :
ResourceManager --> ResourceSet --> .GetObject().

3. Un UnmanagedMemoryStream, che è quanto restituito in uscita dalla Risorsa, può essere semplicemente assegnato all'Oggetto che lo deve utilizzare :
--> in VB, direttamente.
--> in C#, con un semplice Cast sul tipo di destinazione richiesto.

4. In particolare, per quanto riguarda l'esecuzione di Stream audio da file, tipicamente .WAV inclusi nelle Risorse, nessun bisogno di dichiarazioni/chiamate API, o peggio  ancora, controlli ActiveX "nascosti" o altro.
Basta un semplice System.Media.SoundPlayer per questo genere di cose, tanto in VB quanto in C#.

Per replicare l'esempio seguente basta una semplice Form "FormMain" con i seguenti controlli :
--> ComboBox : cmb_suoni
--> PictureBox : pbx_img
--> RichTextBox : rtb_testo

A questo punto, se ci si vuole semplificare ulteriormente le cose, si crea un File .resx per ogni gruppo omogeneo di Risorse.
Nel caso dell'esempio in questione :
--> SUONI.resx : SOUND_1.WAV, SOUND_2.WAV, SOUND_3.WAV.
--> IMMAGINI.resx : SOUND_1.jpg, SOUND_2.jpg, SOUND_3.jpg.
--> TESTI.resx : SOUND_1.txt, SOUND_2.txt, SOUND_3.txt.

La figura seguente per definire meglio il concetto :


--> Codice FormMain :

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using System.Reflection;
using System.Resources;
using System.Globalization;

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

        private string nomeProgetto = Assembly.GetExecutingAssembly().GetName().Name;

        private ResourceSet GetResxFiles(string nomeFileResx)
        {
            //Nome Risorsa senza estensione .resx
            ResourceManager RM = new ResourceManager(nomeProgetto + "." + nomeFileResx, Assembly.GetExecutingAssembly());
            ResourceSet RS = RM.GetResourceSet(CultureInfo.InvariantCulture, true, false);
            return RS;
        }

        private object GetResxFile(string nomeFileResx, string nomeRisorsa)
        {
            return GetResxFiles(nomeFileResx).GetObject(nomeRisorsa, true);
        }

        private void FormMain_Load(object sender, EventArgs e)
        {
            List<string> listaSuoni = new List<string>();
            foreach (DictionaryEntry DE in GetResxFiles("SUONI"))
            {
                listaSuoni.Add(DE.Key.ToString());
            }
            listaSuoni.Sort();
            cmb_suoni.DropDownStyle = ComboBoxStyle.DropDownList;
            cmb_suoni.Items.AddRange(listaSuoni.ToArray());
            cmb_suoni.SelectedIndex = 0;
        }

        private void cmb_suoni_SelectedIndexChanged(object sender, EventArgs e)
        {
            //Play Risorsa Audio
            using (System.Media.SoundPlayer SP = new System.Media.SoundPlayer())
            {
                SP.Stream = (System.IO.Stream)GetResxFile("SUONI", cmb_suoni.SelectedItem.ToString());
                SP.Play();
            }

            //Assegna Risorsa Immagine
            pbx_img.Image = (Image)GetResxFile("IMMAGINI", cmb_suoni.SelectedItem.ToString());

            //Assegna Risorsa Testo
            rtb_testo.Text = (String)GetResxFile("TESTI", cmb_suoni.SelectedItem.ToString());
        }

    }
}

La soluzione è pulita e funzionante : il ResourceManager genera un ResourceSet, che espone parecchi Metodi utili, tra cui .GetObject(), perciò direi che altro non serve.

+ Fine Articolo.


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



domenica 29 gennaio 2012

[C#] UserControl per Grafici

Descrizione :
Un mio UserControl che permette di rappresentare il grafico di funzioni Y = f(x).

+ Articolo :


Questo Controllo è in grado di ricevere in input un insieme di punti X e Y, in cui tipicamente Y è funzione di X, ma anche senza che le due variabili siano necessariamente correlate, e rappresentarli su un grafico con un classico piano cartesiano XY.
Tutto ciò di cui il Controllo ha bisogno dall'esterno è perciò un insieme di punti.

Il Controllo si occupa di :

--> disegnare gli assi X e Y e le scale dei valori.

--> gestire separatamente l'insieme dei punti del grafico contenuti da quello dei punti rappresentati sul grafico : l'insieme dei punti X e Y passati in input non viene banalmente modificato e riportato sul grafico, ma il Controllo provvede a creare un secondo insieme di punti, dedotto dal primo e adatto alla rappresentazione grafica, e ad unire i punti stessi con una curva in AntiAliasing, ad alta qualità.
Ovviamente la qualità della curva sarà sempre inversamente proporzionale alla distanza tra due punti successivi, nell'insieme dei punti passati in input.

--> permettere di definire due fattori di scala separati per X e Y :
agendo sulle ComboBox dedicate a questo scopo, è possibile scalare il grafico a piacere nelle direzioni X ed Y. La curva eventualmente già contenuta nel Controllo verrà automaticamente ridisegnata, e "deformata" adattandosi al nuovo rapporto delle misure X/Y.

--> permettere di definire due fattori di traslazione dell'origine separati per X e Y :
agendo sulle ComboBox dedicate a questo scopo, è possibile personalizzare la posizione dell'origine nel grafico. Si può traslare lungo X ed Y, per visualizzare la parte di curva che interessa, sempre al centro del Controllo...

--> Autosize : basta impostare l'Anchor della PictureBox interna "pbfx" su tutti i 4 valori ( Top, Bottom, Left, Right ) e fare lo stesso sull'istanza del ControlGrafico aggiunto alla Form di test, e il grafico si adatterà a qualsiasi dimensione e risoluzione desiderata.

ControlGrafico.cs :

- Struttura :


- Codice :

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace UserControlGrafici
{
    public partial class ControlGrafico : UserControl
    {
        public ControlGrafico()
        {
            InitializeComponent();
        }

        private Pen m_penassi = new Pen(Color.Black, 2); //Pen per disegno Assi XY
        private Pen m_pengrafico = new Pen(Color.Red, 2); //Pen per disegno Grafico
        private Font m_fontassi = new Font(FontFamily.GenericSansSerif, 11, FontStyle.Regular);
        private float m_tensionecurva; //valore consigliato : 0

        private int m_scalax; //Scala X : il valore esprime in pixels l'unità su Asse X
        private int m_scalay; //Scala Y : il valore esprime in pixels l'unità su Asse Y

        private int m_traslazionex;
        private int m_traslazioney;
        private int m_originex;
        private int m_originey;

        private List<PtFx> m_puntifx = new List<PtFx>();

        public void SetPuntiFx(List<PtFx> fx, float tensioneCurva)
        {
            m_tensionecurva = tensioneCurva;
            m_puntifx = fx;
            pbfx.Refresh();
        }

        private void ControlGrafico_Load(object sender, EventArgs e)
        {
            for (int i = 30; i <= 200; i += 10)
            {
                cmb_scalax.Items.Add(i);
                cmb_scalay.Items.Add(i);
            }
            cmb_scalax.Text = cmb_scalax.Items[cmb_scalax.Items.Count - 1].ToString();
            cmb_scalay.Text = cmb_scalay.Items[cmb_scalay.Items.Count - 1].ToString();

            for (int i = -10; i <= 10; i++)
            {
                cmb_traslax.Items.Add(i);
                cmb_traslay.Items.Add(i);
            }
            cmb_traslax.Text = "0";
            cmb_traslay.Text = "0";
        }

        private void pbfx_Paint(object sender, PaintEventArgs e)
        {
            if (this.DesignMode == true) return;
           
            //Traslazione Origine Graphics pbfx
            m_originex =  Convert.ToInt32(pbfx.Width / 2.0 + m_traslazionex * m_scalax);
            m_originey = Convert.ToInt32(pbfx.Height / 2.0 + m_traslazioney * m_scalay);
            e.Graphics.ResetTransform();
            e.Graphics.TranslateTransform(m_originex, m_originey);

            //Disegno Assi
            Point p1x = new Point(-pbfx.Width - m_originex, 0);
            Point p2x = new Point(pbfx.Width - m_originex, 0);
            Point p1y = new Point(0, pbfx.Height - m_originey);
            Point p2y = new Point(0, -pbfx.Height - m_originey);
            string sxy;
            float x;
            float y;
            e.Graphics.DrawLine(m_penassi, p1x, p2x);
            e.Graphics.DrawLine(m_penassi, p1y, p2y);
            for (int i = m_scalax; i <= (pbfx.Width - m_originex); i += m_scalax)
            {
                e.Graphics.DrawEllipse(m_penassi, i, -2, 2, 2);
                sxy = (i / m_scalax).ToString();
                x = i - e.Graphics.MeasureString(i.ToString(),m_fontassi).Width / 2.0f;
                y = -e.Graphics.MeasureString(i.ToString(), m_fontassi).Height;
                e.Graphics.DrawString(sxy, m_fontassi, Brushes.Black, x, y);
            }
            for (int i = -m_scalax; i >= (-pbfx.Width - m_originex); i -= m_scalax)
            {
                e.Graphics.DrawEllipse(m_penassi, i, -2, 2, 2);
                sxy = (i / m_scalax).ToString();
                x = i;
                y = -e.Graphics.MeasureString(i.ToString(), m_fontassi).Height;
                e.Graphics.DrawString(sxy, m_fontassi, Brushes.Black, x, y);
            }
            for (int i = m_scalay; i <= (pbfx.Height + m_originey); i += m_scalay)
            {
                e.Graphics.DrawEllipse(m_penassi, -2, -i, 2, 2);
                sxy = (i / m_scalay).ToString();
                x = 0;
                y = -i;
                e.Graphics.DrawString(sxy, m_fontassi, Brushes.Black, x, y);
            }
            for (int i = -m_scalay; i >= (-pbfx.Height + m_originey); i -= m_scalay)
            {
                e.Graphics.DrawEllipse(m_penassi, -2, -i, 2, 2);
                sxy = (i / m_scalay).ToString();
                x = 0;
                y = -i - e.Graphics.MeasureString(i.ToString(), m_fontassi).Height / 2.0f;
                e.Graphics.DrawString(sxy, m_fontassi, Brushes.Black, x, y);
            }

            //Disegno Curva F(x)
            if (m_puntifx == null) return;

            Point[] puntiFxGraf = new Point[m_puntifx.Count];
            for (int i = 0; i < puntiFxGraf.Length; i++)
            {
                try
                {
                    puntiFxGraf[i].X = Convert.ToInt32(m_puntifx[i].X * m_scalax);
                    puntiFxGraf[i].Y = Convert.ToInt32(m_puntifx[i].Y * m_scalay) * -1;
                }
                catch (Exception ex)
                {
                }
            }
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            if (puntiFxGraf.Length > 0)
            {
                e.Graphics.DrawCurve(m_pengrafico, puntiFxGraf, m_tensionecurva);
            }
        }

        private void pbfx_Resize(object sender, EventArgs e)
        {
            pbfx.Refresh();
        }

        private void cmb_scalax_SelectedIndexChanged(object sender, EventArgs e)
        {
            m_scalax = Convert.ToInt32(cmb_scalax.SelectedItem);
            pbfx.Refresh();
        }

        private void cmb_scalay_SelectedIndexChanged(object sender, EventArgs e)
        {
            m_scalay = Convert.ToInt32(cmb_scalay.SelectedItem);
            pbfx.Refresh();
        }

        private void cmb_traslax_SelectedIndexChanged(object sender, EventArgs e)
        {
            m_traslazionex = Convert.ToInt32(cmb_traslax.SelectedItem) * -1;
            pbfx.Refresh();
        }

        private void cmb_traslay_SelectedIndexChanged(object sender, EventArgs e)
        {
            m_traslazioney = Convert.ToInt32(cmb_traslay.SelectedItem);
            pbfx.Refresh();
        }

    }
}

Ovviamente è possibile esporre molte delle caratteristiche interne, come colori delle Pen di disegno, e svariati altri aspetti grafici e non, come ad esempio i valori da caricare di default nelle ComboBox ecc... Tutte cose di facile implementazione che non mi sono soffermato a realizzare, e che lascio a chi vorrà personalizzare questo Control come meglio crede.
Per ora il succo del discorso c'è tutto.

Il Metodo essenziale è SetPuntiFx().
Ho preferito esporre la Tension della curva, utile al Metodo interno di disegno .DrawCurve().
Questo parametro permette di influire in un certo modo su come il Controllo "inventa" i punti mancanti nell'insieme della funzione, e di conseguenza su come la curva verrà disegnata.
Personalmente consiglio di tenere questo valore sempre a 0. Con TensioneCurva a 0, il Controllo unirà due punti consecutivi con la retta più breve possibile.
Si possono sperimentare valori >0, ad esempio per "ammorbidire" le spezzate, ma senza eccedere, perchè può portare ad effetti indesiderati...
Nella rappresentazione di una classica funzione matematica, stile "studio di funzione", meglio creare una serie di valori chiave molto ravvicinati, piuttosto che fare troppo affidamento sulla Tension.

PtFx.cs :
E' la Structure che contiene i punti originari di ogni Funzione.

namespace UserControlGrafici
{
    public struct PtFx
    {
        public double X;
        public double Y;
        public PtFx(double valueX, double valueY) : this()
        {
            X = valueX;
            Y = valueY;
        }
    }
}

E ora finalmente, un po' di esempi e il ControlGrafico in azione.
Compilare - aggiungere un "ControlGrafico1" alla Form di test - pronti - via :

1. Una classica parabola :
            List<PtFx> fx = new List<PtFx>();
            double Y = 0;
            for (double X = -10; X <= 10; X += 0.01)
            {
                Y = (Math.Pow(X, 2)) / 3 + X - 1;
                fx.Add(new PtFx(X, Y));
            }
            controlGrafico1.SetPuntiFx(fx, 0);


2. Spezzata con valori Random :
            Random R = new Random();
            List<PtFx> fx = new List<PtFx>();
            double Y = 0;
            for (double X = 0; X <= 20; X++)
            {
                Y = R.Next(0, 3);
                fx.Add(new PtFx(X, Y));
            }
            controlGrafico1.SetPuntiFx(fx, 0);


3. La Funzione di WikiPedia :
Ovvero, la funzione al momento sulla pagina Wiki : "Studio di Funzione".
http://it.wikipedia.org/wiki/File:Studio_funzione_esempio_con_derive.jpg

            List<PtFx> fx = new List<PtFx>();
            double Y = 0;
            for (double X = -20; X <= 20; X += 0.01)
            {
                Y = (Math.Pow(Math.E, (5 * X - 3 * (Math.Pow(X, 2))))) - (Math.Pow(X, 3));
                fx.Add(new PtFx(X, Y));
            }
            controlGrafico1.SetPuntiFx(fx, 0);


+ Fine Articolo.


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





 
Design by Free WordPress Themes Modificato da MarcoGG