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.





sabato 28 gennaio 2012

[C#] Dati Controllo Chart

Descrizione :
Esempio pratico di associazione dati e loro rappresentazione con il controllo Chart.

+ Articolo :

Anzitutto, dal momento che di "controlli chart" in giro ce ne sono davvero tanti, voglio precisare che l'argomento in esame riguarda esclusivamente il controllo incluso in Visual Studio ( in questo caso : 2010 ), e per l'esattezza quello in Forms :
System.Windows.Forms.DataVisualization.Charting.Chart().

Ho creato questo articolo per un duplice scopo :
1. Mostrare una tecnica valida per collegare un Chart con le sue varie Serie, ad una unica fonte dati, come un DataTable.
2. Illustrare 4 diversi modi per evidenziare ciascun dato, una volta che è rappresentato graficamente nel Chart.

Oltre a questo, si noterà come diversi controlli, come le ComboBox per la scelta di ciascun Punto-Dati, vengano automaticamente messe in relazione all'atto della loro associazione con una fonte dati comune.

Per replicare l'articolo occorre una semplice Form "FormMain", con i seguenti controlli essenziali :
--> Chart : chart1
--> ComboBox : cmb_x
--> ComboBox : cmb_serie2
--> ComboBox : cmb_serie3
Le Label sono a scopo descrittivo.

Un'immagine rende bene l'idea della struttura desiderata e del funzionamento ottenuto :


--> Codice completo FormMain :
using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;

namespace DatiControlloChart
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private Font fontLab = new Font(FontFamily.GenericSansSerif, 14, FontStyle.Regular);
        private DataTable DT = new DataTable("TabellaDati");
        private string percorsoBmp = Application.StartupPath + "\\";
        private string nomeBmp = "arrow.bmp";

        private int RandomInteger(int min, int max, int seed)
        {
            return (new Random(seed)).Next(min, max);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //Formattazione DataTable Tabella Dati per contenere due serie
            //Asse X comune alle Serie Dati
            DT.Columns.Add("X", typeof(System.Int32));
            DT.PrimaryKey = new DataColumn[] {DT.Columns["X"]};
            //Asse Y Serie1
            DT.Columns.Add("YSerie1", typeof(System.Single));
            //Asse Y Serie2
            DT.Columns.Add("YSerie2", typeof(System.Int32));
            //Asse Y Serie3
            DT.Columns.Add("YSerie3", typeof(System.Int32));
            //Asse Y Serie4
            DT.Columns.Add("YSerie4", typeof(System.Int32));

            //Creazione Dati ( Pseudo-Random )
            for (int i = 1; i <= 20; i++)
            {
                DT.Rows.Add( new object[] { i, RandomInteger(10, 25, Convert.ToInt32((Math.Pow(i, 2)))) * 1.2, 
                                               RandomInteger(25, 50, Convert.ToInt32((Math.Pow(i, 3)))), 
                                               RandomInteger(50, 75, Convert.ToInt32((Math.Pow(i, 4)))), 
                                               RandomInteger(75, 100, Convert.ToInt32((Math.Pow(i, 5))))});
            }

            //Creazione Serie
            chart1.Series.Clear();
            chart1.DataSource = DT;
            //Serie1
            Series serie1 = new Series("Serie1");
            serie1.ChartType = SeriesChartType.Column;
            serie1.Color = Color.Goldenrod;
            serie1.BorderWidth = 1;
            serie1.BorderColor = Color.Black;
            serie1.XValueMember = DT.Columns["X"].ToString();
            serie1.YValueMembers = DT.Columns["YSerie1"].ToString();
            chart1.Series.Add(serie1);
            serie1.ToolTip = serie1.Name + " [ #VALX{F0} | #VALY{F2} ]";
            //Serie2
            Series serie2 = new Series("Serie2");
            serie2.ChartType = SeriesChartType.Line;
            serie2.Color = Color.Red;
            serie2.BorderWidth = 2;
            serie2.MarkerSize = 12;
            serie2.MarkerBorderColor = Color.Black;
            serie2.MarkerBorderWidth = 2;
            serie2.MarkerStyle = MarkerStyle.Diamond;
            serie2.XValueMember = DT.Columns["X"].ToString();
            serie2.YValueMembers = DT.Columns["YSerie2"].ToString();
            chart1.Series.Add(serie2);
            //Serie3
            Series serie3 = new Series("Serie3");
            serie3.ChartType = SeriesChartType.Line;
            serie3.Color = Color.SlateBlue;
            serie3.BorderWidth = 2;
            serie3.MarkerSize = 10;
            serie3.MarkerBorderColor = Color.Blue;
            serie3.MarkerBorderWidth = 2;
            serie3.MarkerStyle = MarkerStyle.Circle;
            serie3.XValueMember = DT.Columns["X"].ToString();
            serie3.YValueMembers = DT.Columns["YSerie3"].ToString();
            chart1.Series.Add(serie3);
            //Serie4
            Series serie4 = new Series("Serie4");
            serie4.ChartType = SeriesChartType.Line;
            serie4.Color = Color.Green;
            serie4.BorderWidth = 2;
            serie4.MarkerSize = 8;
            serie4.MarkerBorderColor = Color.DarkGreen;
            serie4.MarkerBorderWidth = 2;
            serie4.MarkerStyle = MarkerStyle.Square;
            serie4.XValueMember = DT.Columns["X"].ToString();
            serie4.YValueMembers = DT.Columns["YSerie4"].ToString();
            chart1.Series.Add(serie4);

            cmb_x.DropDownStyle = ComboBoxStyle.DropDownList;
            cmb_x.DataSource = DT;
            cmb_x.ValueMember = DT.Columns["X"].ToString();
            cmb_x.DisplayMember = DT.Columns["X"].ToString();

            cmb_serie2.DropDownStyle = ComboBoxStyle.DropDownList;
            cmb_serie2.DataSource = DT;
            cmb_serie2.ValueMember = DT.Columns["X"].ToString();
            cmb_serie2.DisplayMember = DT.Columns["YSerie2"].ToString();

            cmb_serie3.DropDownStyle = ComboBoxStyle.DropDownList;
            cmb_serie3.DataSource = DT;
            cmb_serie3.ValueMember = DT.Columns["X"].ToString();
            cmb_serie3.DisplayMember = DT.Columns["YSerie3"].ToString();

        }

        private void cmb_serie2_SelectedIndexChanged(object sender, EventArgs e)
        {
            var s2 = chart1.Series["Serie2"];
            if (s2.Points.Count == 0)
            {
                return;
            }
            for (int i = 0; i < s2.Points.Count; i++)
            {
                s2.Points[i].Label = string.Empty;
                s2.Points[i].MarkerStyle = MarkerStyle.Diamond;
            }

            var pts2 = chart1.Series["Serie2"].Points[cmb_serie2.SelectedIndex];
            pts2.Font = fontLab;
            pts2.Label = "Serie2 - " + "X = " + cmb_serie2.SelectedValue + " | Y = " + cmb_serie2.Text;
            pts2.LabelBackColor = Color.Gold;
            pts2.LabelBorderColor = Color.Black;
            pts2.LabelBorderWidth = 1;
            pts2.LabelForeColor = Color.Black;
            pts2.MarkerStyle = MarkerStyle.Star10;
        }

        private void cmb_serie3_SelectedIndexChanged(object sender, EventArgs e)
        {
            var s3 = chart1.Series["Serie3"];
            if (s3.Points.Count == 0)
            {
                return;
            }
            for (int i = 0; i < s3.Points.Count; i++)
            {
                s3.Points[i].MarkerImage = string.Empty;
            }

            var pts3 = chart1.Series["Serie3"].Points[cmb_serie3.SelectedIndex];
            pts3.MarkerImage = percorsoBmp + nomeBmp;
            pts3.MarkerImageTransparentColor = Color.White;
        }

        private void chart1_MouseMove(object sender, MouseEventArgs e)
        {
            HitTestResult HTR = chart1.HitTest(e.X, e.Y);
            //Controllo sul fatto che sia un DataPoint valido
            if (!(HTR.ChartElementType == ChartElementType.DataPoint))
            {
                return;
            }
            //Controllo sul fatto che sia la Serie desiderata ( Serie4 )
            if (!(HTR.Series.Name == "Serie4"))
            {
                return;
            }

            var s4 = chart1.Series["Serie4"];
            for (int i = 0; i < s4.Points.Count; i++)
            {
                s4.Points[i].Label = string.Empty;
                s4.Points[i].MarkerStyle = MarkerStyle.Square;
            }
            var pts4 = chart1.Series["Serie4"].Points[HTR.PointIndex];
            pts4.Font = fontLab;
            pts4.Label = "Serie4 - " + "X = " + pts4.XValue + " | Y = " + pts4.YValues[0];
            pts4.LabelBackColor = Color.YellowGreen;
            pts4.LabelBorderColor = Color.Black;
            pts4.LabelBorderWidth = 1;
            pts4.LabelForeColor = Color.Black;
            pts4.MarkerStyle = MarkerStyle.Star10;
        }
    }
}

NOTE :

--> Il Chart, le Serie e anche le ComboBox puntano al medesimo DataTable, chiaramente ciascuno al suo o suoi campi di pertinenza.

--> Serie1 : evidenzia il dato al passaggio del Mouse come semplice ToolTip.

--> Serie2 : evidenzia il dato ( scelto in cmb_serie2 ) con una Label. A mio avviso la scelta più completa e flessibile.

--> Serie3 : evidenzia il dato ( scelto in cmb_serie3 ) con una bitmap caricata da disco. In questo caso sarà necessario predisporre un'immagine che abbia al suo centro il Marker, e uno o più indicatori esterni. La figura seguente mostra come ho creato la mia "arrow.bmp" in 3 semplici passi, aiutandomi semplicemente con le Forme di Word 2007 :


--> Serie4 : evidenzia il dato al passaggio del Mouse con una Label. Comportamento molto simile al ToolTip della Serie1, ma molto più flessibile nella gestione.

--> Come già accennato, una selezione su ciascuna delle 3 ComboBox si propaga alle altre in automatico e senza che sia necessario estendere gli Handles nelle routine di evento, o scrivere alcun codice aggiuntivo, perchè ciò accade solo grazie alle associazioni fatte in precedenza con i Campi del DataTable.

+ Fine Articolo.

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



[VB.NET] 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 VB.NET, nel momento in cui si rende necessario disporre di un DB file based locale, puntano ancora istintivamente su Access.
Spesso si tratta di utenti VBA o VB6 che arrivano addirittura a caricare il Rif. COM ADODB in .NET per poter disporre del tanto ( ingiustamente ) "amato" Recordset...

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
        With oleDbCnStr
            .Provider = "Microsoft.Jet.OLEDB.4.0"
            .DataSource = percorsoLocale & nomeFileDB
            .PersistSecurityInfo = False
        End With
        oleDbCn.ConnectionString = 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 : un Modulo "ModuloPublics" e una Form "FormMain", che è l'oggetto di avvio.

- Codice ModuloPublics :
Module ModuloPublics

    'Percorso Locale in cui è situato il DB Access
    Public percorsoLocale As String = Application.StartupPath & "\"
    'Percorso LAN in cui è situato il DB Access
    Public nomeMacchina As String = "nomeMacchinaLAN"
    Public nomeDirCondivisa As String = "nomeDirCondivisaLAN"
    Public percorsoLan As String = "\\" & nomeMacchina & "\" & nomeDirCondivisa & "\"
    'Nome file Access compresa estensione
    Public nomeFileDB As String = "testDB.mdb"

    Public oleDbCnStr As New OleDb.OleDbConnectionStringBuilder
    Public oleDbCn As New OleDb.OleDbConnection

    Public F As New Font(FontFamily.GenericMonospace, 12, FontStyle.Bold)

End Module

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

Public Class FormMain

    Private maxId As Integer

    Private Sub setMaxId()

        Dim strSql As String = "SELECT MAX(id) FROM T_Persone"
        Using CMD As New OleDb.OleDbCommand(strSql, oleDbCn)
            oleDbCn.Open()
            maxId = CMD.ExecuteScalar
            oleDbCn.Close()
        End Using

    End Sub

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

        'OleDb.OleDbConnectionStringBuilder
        With oleDbCnStr
            .Provider = "Microsoft.Jet.OLEDB.4.0"
            .DataSource = percorsoLocale & nomeFileDB
            .PersistSecurityInfo = False
        End With
        oleDbCn.ConnectionString = oleDbCnStr.ToString

        With txt_risultati
            .Font = F
            .Multiline = True
        End With

        With DGV
            .AllowUserToAddRows = False
            .ReadOnly = True
            .Font = F
        End With

        setMaxId()

    End Sub

    Private Sub btn_selectparampclan_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_selectparampclan.Click

        'Modifica SOLO al .DataSource di oleDbCnStr
        oleDbCnStr.DataSource = percorsoLan & nomeFileDB

        Dim risultati As New System.Text.StringBuilder
        Dim strSql As String = "SELECT * FROM T_Persone WHERE datanascita>=@datanascita"

        Using CMD As New OleDb.OleDbCommand(strSql, oleDbCn)
            With CMD
                'Definizione / Assegnazione
                .Parameters.Add("@datanascita", OleDb.OleDbType.Date).Value = New DateTime(1967, 12, 25)
                '... altri Parametri
            End With

            oleDbCn.Open()
            Using RDR As OleDb.OleDbDataReader = CMD.ExecuteReader

                'Lettura Campi DataReader
                For i As Integer = 0 To RDR.FieldCount - 1
                    risultati.Append(RDR.GetName(i).PadRight(19, " "))
                Next
                risultati.AppendLine().AppendLine()
                'Lettura Campi Query
                While (RDR.Read())
                    For i As Integer = 0 To RDR.FieldCount - 1
                        risultati.Append(RDR(i).ToString.PadRight(19, " "))
                    Next
                    risultati.AppendLine()
                End While
            End Using
            oleDbCn.Close()
        End Using

        txt_risultati.Text = risultati.ToString

    End Sub

    Private Sub btn_selectparam_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_selectparam.Click

        Dim risultati As New System.Text.StringBuilder
        Dim strSql As String = "SELECT * FROM T_Persone WHERE datanascita>=@datanascita AND cognome LIKE @cognome"

        Using CMD As New OleDb.OleDbCommand(strSql, oleDbCn)
            With CMD
                'Definizione / Assegnazione
                .Parameters.Add("@datanascita", OleDb.OleDbType.Date).Value = New DateTime(1967, 12, 25)
                .Parameters.Add("@cognome", OleDb.OleDbType.VarChar).Value = "Ro" & "%"
                '... altri Parametri
            End With

            oleDbCn.Open()
            Using RDR As OleDb.OleDbDataReader = CMD.ExecuteReader

                'Lettura Campi DataReader
                For i As Integer = 0 To RDR.FieldCount - 1
                    risultati.Append(RDR.GetName(i).PadRight(19, " "))
                Next
                risultati.AppendLine().AppendLine()
                'Lettura Campi Query
                While (RDR.Read())
                    For i As Integer = 0 To RDR.FieldCount - 1
                        risultati.Append(RDR(i).ToString.PadRight(19, " "))
                    Next
                    risultati.AppendLine()
                End While
            End Using
            oleDbCn.Close()
        End Using

        txt_risultati.Text = risultati.ToString

    End Sub

    Private Sub btn_selectparamdgv_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_selectparamdgv.Click

        'Reset DGV
        With DGV
            .Rows.Clear()
            .Columns.Clear()
        End With

        Dim dgvr As DataGridViewRow
        Dim strSql As String = "SELECT * FROM T_Persone WHERE datanascita>=@datanascita AND cognome LIKE @cognome"

        Using CMD As New OleDb.OleDbCommand(strSql, oleDbCn)
            With CMD
                'Definizione / Assegnazione
                .Parameters.Add("@datanascita", OleDb.OleDbType.Date).Value = New DateTime(1967, 12, 25)
                .Parameters.Add("@cognome", OleDb.OleDbType.VarChar).Value = "Ro" & "%"
                '... altri Parametri
            End With

            oleDbCn.Open()
            Using RDR As OleDb.OleDbDataReader = CMD.ExecuteReader
                'Lettura Campi DataReader
                For i As Integer = 0 To RDR.FieldCount - 1
                    DGV.Columns.Add(RDR.GetName(i).ToString, RDR.GetName(i).ToString)
                Next
                'Lettura Campi Query
                While (RDR.Read())
                    dgvr = New DataGridViewRow
                    dgvr.CreateCells(DGV)
                    For i As Integer = 0 To RDR.FieldCount - 1
                        dgvr.Cells(i).Value = RDR(i).ToString
                    Next
                    DGV.Rows.Add(dgvr)
                End While
            End Using
            oleDbCn.Close()
        End Using

    End Sub

    Private Sub btn_selectmaxid_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_selectmaxid.Click

        setMaxId()
        MessageBox.Show(maxId)

    End Sub

    Private Sub btn_selectstar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_selectstar.Click

        Dim risultati As New System.Text.StringBuilder
        Dim strSql As String = "SELECT * FROM T_Persone"

        Using CMD As New OleDb.OleDbCommand(strSql, oleDbCn)
            oleDbCn.Open()
            Using RDR As OleDb.OleDbDataReader = CMD.ExecuteReader
                'Lettura Campi DataReader
                For i As Integer = 0 To RDR.FieldCount - 1
                    risultati.Append(RDR.GetName(i).PadRight(19, " "))
                Next
                risultati.AppendLine().AppendLine()
                'Lettura Campi Query
                While (RDR.Read())
                    For i As Integer = 0 To RDR.FieldCount - 1
                        risultati.Append(RDR(i).ToString.PadRight(19, " "))
                    Next
                    risultati.AppendLine()
                End While
            End Using
            oleDbCn.Close()
        End Using

        txt_risultati.Text = risultati.ToString

    End Sub

    Private Sub btn_selectstardgv_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_selectstardgv.Click

        'Reset DGV
        With DGV
            .Rows.Clear()
            .Columns.Clear()
        End With

        Dim dgvr As DataGridViewRow
        Dim strSql As String = "SELECT * FROM T_Persone"

        Using CMD As New OleDb.OleDbCommand(strSql, oleDbCn)
            oleDbCn.Open()
            Using RDR As OleDb.OleDbDataReader = CMD.ExecuteReader
                'Lettura Campi DataReader
                For i As Integer = 0 To RDR.FieldCount - 1
                    DGV.Columns.Add(RDR.GetName(i).ToString, RDR.GetName(i).ToString)
                Next
                'Lettura Campi Query
                While (RDR.Read())
                    dgvr = New DataGridViewRow
                    dgvr.CreateCells(DGV)
                    For i As Integer = 0 To RDR.FieldCount - 1
                        dgvr.Cells(i).Value = RDR(i).ToString
                    Next
                    DGV.Rows.Add(dgvr)
                End While
            End Using
            oleDbCn.Close()
        End Using

    End Sub

    Private Sub btn_insertparam_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_insertparam.Click

        Dim strSql As String = "INSERT INTO T_Persone (id, nome, cognome, datanascita, contatti) " & _
                               "VALUES (@id, @nome, @cognome, @datanascita, @contatti)"

        Using CMD As New OleDb.OleDbCommand(strSql, oleDbCn)
            With CMD
                'Definizione / Assegnazione
                .Parameters.Add("@id", OleDb.OleDbType.Integer).Value = maxId + 1
                .Parameters.Add("@nome", OleDb.OleDbType.VarChar).Value = "insertNome"
                .Parameters.Add("@cognome", OleDb.OleDbType.VarChar).Value = "insertCognome"
                .Parameters.Add("@datanascita", OleDb.OleDbType.Date).Value = New DateTime(1970, 1, 31)
                .Parameters.Add("@contatti", OleDb.OleDbType.Integer).Value = 12

                oleDbCn.Open()
                .ExecuteNonQuery()
                oleDbCn.Close()
            End With
        End Using
        setMaxId()
        MessageBox.Show("INSERT OK")

    End Sub

    Private Sub btn_updateparam_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_updateparam.Click

        Dim strSql As String = "UPDATE T_Persone SET nome=@nome, cognome=@cognome, datanascita=@datanascita, contatti=@contatti " & _
                               "WHERE id=@id"

        Using CMD As New OleDb.OleDbCommand(strSql, oleDbCn)
            With CMD
                'Definizione / Assegnazione
                .Parameters.Add("@nome", OleDb.OleDbType.VarChar).Value = "updateNome"
                .Parameters.Add("@cognome", OleDb.OleDbType.VarChar).Value = "updateCognome"
                .Parameters.Add("@datanascita", OleDb.OleDbType.Date).Value = New DateTime(1968, 8, 8)
                .Parameters.Add("@contatti", OleDb.OleDbType.Integer).Value = 8
                .Parameters.Add("@id", OleDb.OleDbType.Integer).Value = maxId

                oleDbCn.Open()
                .ExecuteNonQuery()
                oleDbCn.Close()
            End With
        End Using
        MessageBox.Show("UPDATE OK")

    End Sub

    Private Sub btn_deleteparam_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_deleteparam.Click

        Dim strSql As String = "DELETE FROM T_Persone WHERE id=@id"

        Using CMD As New OleDb.OleDbCommand(strSql, oleDbCn)
            With CMD
                'Definizione / Assegnazione
                .Parameters.Add("@id", OleDb.OleDbType.Integer).Value = maxId

                oleDbCn.Open()
                .ExecuteNonQuery()
                oleDbCn.Close()
            End With
        End Using
        setMaxId()
        MessageBox.Show("DELETE OK")

    End Sub

End Class

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.




lunedì 16 gennaio 2012

[VB.NET] Web Image Search con WebBrowser

Descrizione :
Un mio esempio pratico abbastanza completo basato sul Controllo WebBrowser.

+ Articolo :

Questo Articolo nasce da alcune discussioni su Forum Tecnici a cui ho partecipato con vari suggerimenti.
Ho deciso di riunire questi suggerimenti in qualcosa che avesse anche un minimo di "scopo pratico".
Inoltre le modifiche e aggiunte rispetto al codice originario sono parecchie.

Lo scopo è costruire una Windows Form basata sul WebBrowser che permetta la ricerca e il salvataggio veloce di immagini online, basandosi sul motore di Google Images.
Le immagini vengono cercate in base ad una o più Keywords e su Form sono presenti anche tutti i Controlli che servono a gestire le Keywords in modo ordinato.
Le immagini desiderate non vengono semplicemente salvate come thumbnails ( le stesse ottenute da Google ), ma il codice risale al link originale di ogni file e se possibile lo scarica alla fonte.
La selezione avviene con un menu contestuale.
Completa il tutto il salvataggio e caricamento delle Keywords usate in un file di testo.

La struttura dell'unica Form necessaria "FormMain" è semplice.
Tutte le proprietà sono assegnate via codice perciò non occorre alcuna particolare impostazione a Design.
I Controlli necessari sono :
--> Button : btn_salva
--> TextBox : txt_keywords
--> Button : btn_ok
--> CheckedListBox : chl_keywords
--> WebBrowser : wbr_google

La figura seguente rende bene l'idea della struttura e del funzionamento :


--> Codice FormMain :
Public Class FormMain

    Private percorso As String = Application.StartupPath & "\"
    Private cartellaAppunti As String = percorso
    Private cartellaImmaginiSalvate As New IO.DirectoryInfo(percorso & "ImmaginiSalvate\")
    Private nomeFileAppunti As String = "APPUNTI.txt"
    Private SelectedImageUrl As String
    Private urlGoogleImages As String = "http://www.google.com/images?&q="
    Private keywords As New List(Of String)
    Private selectedKeywords As New List(Of String)
    'Menu Contestuale
    Private WithEvents MenuImages As New System.Windows.Forms.ContextMenuStrip
    Private WithEvents ScaricaToolStripMenuItem As New System.Windows.Forms.ToolStripMenuItem

    Private Sub ListaKeywords()

        keywords.Sort()
        selectedKeywords.Sort()
        chl_keywords.Items.Clear()
        chl_keywords.Items.AddRange(keywords.ToArray)
        For Each kw As String In selectedKeywords
            If chl_keywords.Items.IndexOf(kw) > -1 Then chl_keywords.SetItemChecked(chl_keywords.Items.IndexOf(kw), True)
        Next

    End Sub

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

        'Impostazioni Controlli
        With ScaricaToolStripMenuItem
            .Name = "ScaricaToolStripMenuItem"
            .Text = "Scarica"
        End With
        With MenuImages
            .Items.AddRange(New System.Windows.Forms.ToolStripItem() {ScaricaToolStripMenuItem})
            .Name = "MenuImages"
        End With

        With chl_keywords
            .Anchor = AnchorStyles.Top Or AnchorStyles.Bottom Or AnchorStyles.Left
            .CheckOnClick = True
        End With

        With wbr_google
            .Anchor = AnchorStyles.Top Or AnchorStyles.Bottom Or AnchorStyles.Left Or AnchorStyles.Right
            .IsWebBrowserContextMenuEnabled = False
        End With

        btn_ok.Anchor = AnchorStyles.Top Or AnchorStyles.Right
        txt_keywords.Anchor = AnchorStyles.Top Or AnchorStyles.Right Or AnchorStyles.Left

        'File Appunti / Keywords
        If IO.File.Exists(cartellaAppunti & nomeFileAppunti) = False Then IO.File.Create(cartellaAppunti & nomeFileAppunti)
        keywords = IO.File.ReadAllLines(cartellaAppunti & nomeFileAppunti).ToList
        ListaKeywords()

    End Sub

    Private Sub btn_salva_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_salva.Click

        IO.File.WriteAllLines(cartellaAppunti & nomeFileAppunti, keywords)

    End Sub

    Private Sub btn_ok_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_ok.Click

        If txt_keywords.Text = "" Then
            MessageBox.Show("Immettere almeno una keyword di ricerca !")
            Exit Sub
        End If

        Dim txt As String = txt_keywords.Text
        selectedKeywords = txt.Split(" ").ToList
        'Controllo correttezza keywords
        For i As Integer = selectedKeywords.Count - 1 To 0 Step -1
            selectedKeywords(i) = selectedKeywords(i).Trim
            If selectedKeywords(i) = "" Then selectedKeywords.RemoveAt(i)
            'Qui altri eventuali controlli sulle keywords...
            '...
        Next

        'Confronto con le keywords già in lista su chl_keywords
        For Each kw As String In selectedKeywords
            If Not keywords.Contains(kw) Then keywords.Add(kw)
        Next
        ListaKeywords()

        txt = String.Join("+", selectedKeywords.ToArray)
        wbr_google.Navigate(urlGoogleImages & txt)

    End Sub

    Private Sub chl_keywords_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles chl_keywords.SelectedIndexChanged

        With chl_keywords
            If .CheckedItems.Contains(.SelectedItem) Then
                If Not selectedKeywords.Contains(.SelectedItem) Then selectedKeywords.Add(.SelectedItem)
            Else
                selectedKeywords.Remove(.SelectedItem)
            End If
        End With
        txt_keywords.Text = String.Join(" ", selectedKeywords.ToArray)

    End Sub

    Private Sub GoogleHtmlElement_MouseUp(ByVal sender As Object, ByVal e As HtmlElementEventArgs)

        If e.MouseButtonsPressed = Windows.Forms.MouseButtons.Right Then
            Dim HE As HtmlElement = DirectCast(sender, HtmlElement).Parent
            Dim LinkMatch As System.Text.RegularExpressions.Match = System.Text.RegularExpressions.Regex.Match(HE.OuterHtml, "imgurl=(?<imgurl>.*?)&amp", _
                                                                                                               System.Text.RegularExpressions.RegexOptions.IgnoreCase)
            If LinkMatch.Success Then
                SelectedImageUrl = LinkMatch.Groups("imgurl").Value
                MenuImages.Show(MousePosition)
            End If
        End If

    End Sub

    Private Sub ScaricaToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ScaricaToolStripMenuItem.Click

        Dim fileImmagine As System.IO.FileInfo
        Dim nomeImmagine As String
        Dim soggettoImmagine As String
        Dim soggettoRicerca As String = String.Join("+", selectedKeywords.ToArray)
        Dim contaImmaginiPerSoggetto As Integer

        For Each fileImmagine In cartellaImmaginiSalvate.GetFiles
            nomeImmagine = fileImmagine.Name
            soggettoImmagine = nomeImmagine.Split("_")(0)
            If soggettoImmagine = soggettoRicerca Then contaImmaginiPerSoggetto += 1
        Next

        Dim numero As String = (contaImmaginiPerSoggetto + 1).ToString("00000")
        '00000 --> nomi da 00001 a 99999
        Dim nomeFile As String = cartellaImmaginiSalvate.FullName & soggettoRicerca & "_" & numero & ".jpg"

        Dim immagineDaScaricare As New Net.WebClient
        Try
            immagineDaScaricare.DownloadFile(SelectedImageUrl, nomeFile)
            MessageBox.Show("Ok: Immagine Salvata " & nomeFile)
        Catch ex As Exception
            MessageBox.Show("Errore : Impossibile Salvare l'Immagine", "masterdrive", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
        Finally
            immagineDaScaricare.Dispose()
        End Try

    End Sub

    Private Sub wbr_google_DocumentCompleted(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) Handles wbr_google.DocumentCompleted

        Dim readyState As WebBrowserReadyState = DirectCast(sender, WebBrowser).ReadyState
        If readyState = WebBrowserReadyState.Complete Then
            If e.Url.Host = "www.google.it" Then
                For Each img As HtmlElement In wbr_google.Document.Images
                    AddHandler img.MouseUp, AddressOf GoogleHtmlElement_MouseUp
                Next
            Else
                For Each img As HtmlElement In wbr_google.Document.Images
                    AddHandler img.MouseUp, AddressOf GoogleHtmlElement_MouseUp
                Next
            End If
        End If

    End Sub

End Class

La scelta delle Keywords viene fatta tipicamente dalla CheckedListBox, ma è anche possibile scrivere direttamente nella TextBox centrale.
Il programma verificherà l'inserimento di nuove Keywords e se caso le metterà in lista.
Pensa il codice, di volta in volta, a creare la giusta stringa da dare in pasto al browser nel caso di più Keywords :


Ho volutamente scritto alcuni spazi dei nomi per esteso per rendere più chiaro il percorso delle Classi .NET usate.
Inoltre parecchie impostazioni sono effettuate via codice e lo stesso menu contestuale è completamente creato e gestito via codice.
In uno scenario pratico, con i dovuti Imports ecc. il codice sarebbe più snello.

Ci sono inoltre vari spunti interessanti su cui focalizzare l'attenzione :
- Creazione e gestione di menu contestuale da codice ( no Design ).
- L'Evento DocumentCompleted() del WebBrowser e relativo uso del WebBrowserReadyState.
- L'estrazione dei link desiderati dal Document, grazie anche alle Regular Expression, e successivo download con Net.WebClient.
- Un valido sistema per comporre dinamicamente e progressivamente i nomi dei file salvati, ecc...

+ Fine Articolo.


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



domenica 15 gennaio 2012

[VB.NET] Gestire File INI con Classi

Descrizione :
Una mia tecnica essenziale per gestire file INI e simili con alcune Classi.

+ Articolo :
Accanto ai ben conosciuti .cfg, .conf, ecc., il file INI è un formato di file testuale utilizzato da numerosi programmi per la memorizzazione delle opzioni di funzionamento dei programmi stessi.
Nonostante in .NET la gestione di file INI sia considerata decisamente "Legacy", può capitare sempre di imbattersi in Applicazioni che ne fanno uso.

Personalmente vedo che esistono già molti How-To e suggerimenti online che spiegano come gestire in VB e C# un tipico file INI, ma spesso fanno uso di diverse dichiarazioni API esplicite.
Il mio scopo è invece quello di gestire un classico file INI semplicemente con alcune Classi essenzialmente .NET.

Anzitutto è necessario stabilire nomi e convenzioni precise e, prendendo a prestito un po' di "nomenclatura XML", definire come dovrebbe essere un file INI "ben formato".
Dato che siamo in VB.NET, applico senza dubbio lo "Standard Microsoft", e senza cercare troppo in giro mi affido alla definizione presente su Wikipedia :
http://it.wikipedia.org/wiki/File_INI

- Un file INI risulta suddiviso in Sezioni.
- Una Sezione inizia con il nome, racchiuso tra parentesi quadre :
[NomeSezione]
- Una Sezione può contenere un Commento e diversi Parametri.
- Un Parametro viene definito come coppia Chiave-Valore :
Chiave = Valore
- Un Commento deve iniziare con ";" :
; Commento Sezione
- Due Sezioni successive sono separate da una riga vuota.

--> Il file di test "INITest.ini" usato in questo articolo è lo stesso della pagina Wikipedia e ha inizialmente questo contenuto :
[Sezione1]
; Un commento su questa sezione.
Parametro1 = Questo è un valore assegnato, come quello seguente.
Parametro2 = 1

[Sezione2]
; Un altro commento.
Parametro1 = Altro esempio...
Parametro2 = ...e l'ultimo.

--> La Classe Parametro :
Public Class Parametro

    Private m_chiave As String
    Private m_valore As String

    Public Property Chiave() As String
        Get
            Return m_chiave
        End Get
        Set(ByVal value As String)
            m_chiave = value
        End Set
    End Property

    Public Property Valore() As String
        Get
            Return m_valore
        End Get
        Set(ByVal value As String)
            m_valore = value
        End Set
    End Property

    Public Sub New(ByVal nomeChiave As String, ByVal valoreChiave As String)

        m_chiave = nomeChiave
        m_valore = valoreChiave

    End Sub

    Public Overrides Function ToString() As String

        Return m_chiave & " = " & m_valore

    End Function

End Class

--> La Classe Sezione :
Public Class Sezione
    Inherits List(Of Parametro)

    Private m_nome As String
    Private m_commento As String

    Public Property Nome() As String
        Get
            Return m_nome
        End Get
        Set(ByVal value As String)
            m_nome = value
        End Set
    End Property

    Public Property Commento() As String
        Get
            Return m_commento
        End Get
        Set(ByVal value As String)
            m_commento = value
        End Set
    End Property

    Public Sub New(ByVal nome As String, Optional ByVal commento As String = "")

        m_nome = nome
        If commento <> "" Then m_commento = commento

    End Sub

    Public Function GetParametroByNome(ByVal nomeParametro As String) As Parametro

        For Each P As Parametro In Me
            If P.Chiave = nomeParametro Then Return P
        Next
        Return Nothing

    End Function

End Class

--> La Classe FileIni :
Public Class FileIni
    Inherits List(Of Sezione)

    Private m_nomeFileCompleto As String

    Public Sub New(ByVal nomeFileCompleto As String)

        m_nomeFileCompleto = nomeFileCompleto

    End Sub

    Public Sub Carica()

        Me.Clear()
        Dim linee As String() = IO.File.ReadAllLines(m_nomeFileCompleto)
        For Each l As String In linee
            If l <> "" Then
                Select Case l.First
                    Case ";" 'Commento di Sezione
                        Me.Last.Commento = l.Replace("; ", "")

                    Case "[" 'Sezione
                        Me.Add(New Sezione(l.Replace("[", "").Replace("]", "")))

                    Case Else 'Parametro ?
                        If l.Contains("=") Then Me.Last.Add(New Parametro(l.Split("=")(0).Trim, l.Split("=")(1).Trim))
                End Select
            End If
        Next

    End Sub

    Public Sub Salva()

        If Me.Count = 0 Then Exit Sub
        Dim linee As New List(Of String)

        For Each S As Sezione In Me
            linee.Add("[" & S.Nome & "]")
            If S.Commento <> "" Then linee.Add("; " & S.Commento)
            For Each P As Parametro In S
                linee.Add(P.ToString)
            Next
            linee.Add("")
        Next

        IO.File.WriteAllLines(m_nomeFileCompleto, linee)

    End Sub

    Public Function GetSezioneByNome(ByVal nomeSezione As String) As Sezione

        For Each S As Sezione In Me
            If S.Nome = nomeSezione Then Return S
        Next
        Return Nothing

    End Function

End Class

Come è facile vedere, Sezione è una List() di Parametri, e FileIni è List() di Sezioni.
La logica del tutto rimane semplice e senza nemmeno una chiamata esplicita API.
Per semplicità ho volutamente evitato di gestire Commenti di Parametro o fuori dalle Sezioni, così come altre eccezioni o formattazioni non-Standard.

--> Alcuni Esempi di utilizzo ... :
        'Carica
        Dim INI As New FileIni(Application.StartupPath & "\INITest.ini")
        INI.Carica()

        'Trova e Modifica
        INI.GetSezioneByNome("Sezione2").GetParametroByNome("Parametro2").Valore = DateTime.Now

        'Oppure (indici)
        INI(0)(1).Valore = DateTime.Now 'Valore su Parametro a indice 0 della Sezione a indice 1

        'Aggiungi Nuova Sezione
        INI.Add(New Sezione("Sezione" & INI.Count + 1, "Commento Sezione" & INI.Count + 1))
        With INI.Last
            .Add(New Parametro("Chiave" & .Count + 1, "Valore" & .Count + 1))
            .Add(New Parametro("Chiave" & .Count + 1, "Valore" & .Count + 1))
        End With
     
        'Salvataggio
        INI.Salva()

        MessageBox.Show("OK")

... E il gioco è fatto.

+ 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