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.



[VB.NET] 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 :
Public Class FormMain

    Private nomeProgetto As String = Reflection.Assembly.GetExecutingAssembly.GetName.Name

    Private Function GetResxFiles(ByVal nomeFileResx As String) As Resources.ResourceSet

        'Nome Risorsa senza estensione .resx
        Dim RM As New Resources.ResourceManager(nomeProgetto & "." & nomeFileResx, Reflection.Assembly.GetExecutingAssembly())
        Dim RS As Resources.ResourceSet = RM.GetResourceSet(Globalization.CultureInfo.InvariantCulture, True, False)
        Return RS

    End Function

    Private Function GetResxFile(ByVal nomeFileResx As String, ByVal nomeRisorsa As String) As Object

        Return GetResxFiles(nomeFileResx).GetObject(nomeRisorsa, True)

    End Function

    Private Sub FormMain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Dim listaSuoni As New List(Of String)
        For Each DE As DictionaryEntry In GetResxFiles("SUONI")
            listaSuoni.Add(DE.Key)
        Next
        listaSuoni.Sort()
        With cmb_suoni
            .DropDownStyle = ComboBoxStyle.DropDownList
            .Items.AddRange(listaSuoni.ToArray)
            .SelectedIndex = 0
        End With

    End Sub

    Private Sub cmb_suoni_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmb_suoni.SelectedIndexChanged

        'Play Risorsa Audio
        Using SP As New System.Media.SoundPlayer
            SP.Stream = GetResxFile("SUONI", cmb_suoni.SelectedItem)
            SP.Play()
        End Using

        'Assegna Risorsa Immagine
        pbx_img.Image = GetResxFile("IMMAGINI", cmb_suoni.SelectedItem)

        'Assegna Risorsa Testo
        rtb_testo.Text = GetResxFile("TESTI", cmb_suoni.SelectedItem)

    End Sub

End Class

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.



venerdì 3 febbraio 2012

[Excel VBA] Ricerche e Azioni Multiple su WorkBook

Descrizione :
Un mio esempio pratico su come impostare Ricerche e Azioni conseguenti su tutti i Fogli di un WorkBook.

+ Articolo :

In questo Articolo ho cercato di riunire alcune tecniche che fossero comuni più o meno a tutte le versioni di Excel e relativo VBA. Lo scopo è di eseguire una o più ricerche su tutti i Fogli di un WorkBook, utilizzando il più possibile Oggetti e Metodi base. Una ricerca Cella-per-Cella che sia in grado di eseguire più criteri contemporaneamente, e quindi più azioni da intraprendere, a seconda dei casi e degli esiti delle ricerche stesse.
Il tutto con il massimo controllo possibile, che spesso invece, utilizzando solo funzioni automatiche di Excel, viene perso.

L'esempio si basa sull'utilizzo di UserForm e di Funzioni parametrizzate definite a livello di Modulo.
Questa tecnica garantisce che il codice "di comando" sia eseguito ad un livello superiore rispetto ai moduli VBA dei singoli Fogli, e quindi senza rischio di conflitti o errori, anche in caso di selezioni.

Il WorkBook di esempio è semplice. Basta che contenga un certo numero di Fogli :

Foglio1
Foglio2
Foglio3
...
FoglioN


Su Foglio1 si inserisce un CommandButton ( del tipo ActiveX ) "cmd_formricerca".
In questo caso Foglio1 è escluso dalla ricerca perchè si presume che Foglio1 abbia il ruolo di "Foglio di Comando", e non contenga dati.

--> Codice cmd_formricerca :
Private Sub cmd_formricerca_Click()

    UserForm1.Show

End Sub

I rimanenti Fogli ( Foglio2 --> FoglioN ) conterranno dati organizzati in qualsivoglia struttura, come tabelle, righe o colonne separate da spazi, ecc...

Elemento principale dell'esempio è la UserForm1, da creare e disegnare all'interno dell'Editor VBA.
UserForm1 permetterà di :

1. Decidere se restringere o meno il campo di ricerca su ogni Foglio analizzato, ad un Range che sia effettivamente popolato da dati.

2. Decidere se la determinazione di tale Range-Dati vada fatta automaticamente o abbia dimensioni fisse predefinite.

3. Scegliere quali Fogli siano da escludere/includere nella ricerca.

4. Impostare una keyword e avviare la ricerca.

Struttura e funzionamento possono essere descritti con la seguente figura :


--> UserForm1 :
I controlli utilizzati sono di base e comuni a tutte le versioni di Excel VBA :
--> CheckBox : chk_autour
--> TextBox : txt_ricerca
--> CommandButton : cmd_selt
--> CommandButton : cmd_seln
--> CommandButton : cmd_cerca
--> ListBox : lst_fogli

--> Codice UserForm1 :

Private Const defaultStartC As Long = 1
Private Const defaultStartR As Long = 1
Private Const defaultEndC As Long = 50
Private Const defaultEndR As Long = 100

Private startC As Long
Private startR As Long
Private endC As Long
Private endR As Long

Private Sub cmd_selt_Click()

    Dim i As Integer
    For i = 0 To lst_fogli.ListCount - 1
        lst_fogli.Selected(i) = True
    Next i

End Sub

Private Sub cmd_seln_Click()

    Dim i As Integer
    For i = 0 To lst_fogli.ListCount - 1
        lst_fogli.Selected(i) = False
    Next i

End Sub

Private Sub UserForm_Initialize()

    Dim i As Integer
    With lst_fogli
        .ListStyle = fmListStyleOption
        .MultiSelect = fmMultiSelectMulti
        
        For i = 2 To ThisWorkbook.Worksheets.Count
            .AddItem (ThisWorkbook.Worksheets(i).Name)
            .Selected(.ListCount - 1) = True
        Next i
    End With

End Sub

Private Sub cmd_cerca_Click()

    If txt_ricerca.Text = "" Then
        MsgBox "La Text Ricerca non può essere vuota.", vbExclamation, "Errore"
        Exit Sub
    End If
    Dim strRicerca As String
    strRicerca = txt_ricerca.Text
    
    Dim c As Long
    Dim r As Long
    Dim msgr As VbMsgBoxResult
    
    Dim WS As Worksheet
    For i = 0 To lst_fogli.ListCount - 1
    
        If lst_fogli.Selected(i) = True Then
            Set WS = ThisWorkbook.Worksheets(lst_fogli.List(i))
           
            If chk_autour.Value = Checked Then
            
                '--------------------------------------------------
                'Qui il metodo di determinazione del Range Dati ...
                
                With WS.UsedRange
                    startC = .Column
                    endC = startC + .Columns.Count - 1
                    startR = .Row
                    endR = startR + .Rows.Count - 1
                End With
                
                '--------------------------------------------------
                
            Else
                startC = defaultStartC
                startR = defaultStartR
                endC = defaultEndC
                endR = defaultEndR
            End If
    
            For r = startR To endR
            
                For c = startC To endC
                
                    '----------------------------------------------------------
                    'Qui il blocco Condizioni / Azioni della cella corrente ...
                
                    'CondizioneAzione1
                    If CondizioneAzione1(WS, r, c, strRicerca) = True Then Exit Sub
                    
                    'CondizioneAzione2
                    CondizioneAzione2 WS, r, c, strRicerca
                    
                    '...
                    
                    'CondizioneAzioneN
                    
                    '...
                    
                    '----------------------------------------------------------
                
                Next c
                
            Next r
        End If
        
    Next i
   
    MsgBox "Ricerca terminata"

End Sub

I due "blocchi" di codice evidenziati e delimitati da commenti sono :
- Il blocco in cui inserire il metodo con cui il Range Dati viene definito : nell'esempio viene usato uno dei più immediati, che è .UsedRange dell'Oggetto WorkSheet, ma non è detto sia l'unico possibile.
- Il blocco in cui inserire le Condizioni da verificare e le Azioni conseguenti. Ogni Condizione/Azione corrisponde ad una Function o Sub parametrizzata e definita in un Modulo.

--> Codice Modulo :

Public Function CondizioneAzione1(ByVal WS As Worksheet, ByVal indiceR As Long, ByVal indiceC As Long, ByVal arg As String) As Boolean

    Dim msgr As VbMsgBoxResult
    If InStr(1, WS.Cells(indiceR, indiceC).Text, arg) > 0 Then
        WS.Select
        WS.Cells(indiceR, indiceC).Select
        msgr = MsgBox("Continuare la ricerca ?", vbYesNo, "Domanda")
        If msgr = vbNo Then
            MsgBox "Ricerca interrotta"
            CondizioneAzione1 = True
        Else
            CondizioneAzione1 = False
        End If
    End If

End Function

Public Sub CondizioneAzione2(ByVal WS As Worksheet, ByVal indiceR As Long, ByVal indiceC As Long, ByVal arg As String)

    Dim words() As String
    Dim word As Variant
    Dim FileNumber As Integer
    If InStr(1, WS.Cells(indiceR, indiceC).Text, arg) > 0 Then
        words = Split(WS.Cells(indiceR, indiceC).Text, " ")
        For Each word In words
            If IsNumeric(word) Then
                FileNumber = FreeFile
                Open ThisWorkbook.Path & "\report.txt" For Append As #FileNumber
                Print #FileNumber, Now & " - " & WS.Cells(indiceR, indiceC).Text
                Close #FileNumber
                Exit For
            End If
        Next word
    End If

End Sub

- Il primo è un esempio con una Function : la keyword viene cercata all'interno della cella ( Instr() ), perciò se la cella contiene la keyword anche come sottostringa, la ricerca da esito positivo.
In questo caso il valore Boolean restituito viene usato dal codice principale per decidere se continuare o meno con la ricerca.

- Il secondo esempio è una Sub : la keyword viene cercata allo stesso modo, ma se la ricerca da esito positivo viene eseguito un secondo test su tutte le parole della cella. Se una di esse è di tipo numerico, il risultato viene accodato in un file di testo.

Risulta abbastanza evidente come sia possibile estendere l'esempio a molti altri casi, con ricerche e operazioni difficilmente ( o per nulla ) ottenibili utilizzando le consuete procedure di ricerca...

+ 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