Guarda! Senza mani! Sesta parte
a cura di Sabrina Cosolo e Diego Cattaruzza (requisiti: conoscenza intermedia di Sql Server e di .Net)

Fra poco...
Finalmente iniziamo a costruire una form che farà parte dell'applicazione vera e propria: questa form deve permettere di visualizzare e modificare i setting utente o i setting applicativi da noi predisposti all'interno della classe UsingSqlServerConfig, di salvarli su disco e di testare la connessione a database.

ma prima...
Come sempre, verificando quel che ci serve per la gestione della form, abbiamo bisogno di qualche funzionalità ausiliaria, pertanto dovremo modificare o aggiungere classi nelle librerie. Le funzioni di cui abbiamo bisogno sono le seguenti:

Per la prima funzione, ce la caviamo con poco: infatti andremo ad aggiungere un metodo all'interno della classe Warnings che abbiamo predisposto in TAndT.UI

     public static DialogResult SiNo(string pMessage)
    {
      return MessageBox.Show(pMessage, "Conferma", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
    }
   Public Shared Function SiNo(ByVal message As String) As DialogResult
    Return MessageBox.Show(message, "Conferma", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
  End Function

Come possiamo notare, anche in questo caso abbiamo semplicemente mappato la messagebox, lasciandoci solo la necessità di scrivere la domanda, sempre perché siamo pigri.

La classe SqlHelper
Per la seconda funzione, creeremo un'altra classe helper, la prima classe che utilizza SQL Server, andando ad inaugurare la libreria TAndT.Data, finora vuota. Selezioniamola nel Solution Explorer, click destro Add>Class> e generiamo la classe SqlHelper, una nuova classe statica.

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Data.SqlClient

Nelle direttive, oltre a quelle standard, abbiamo aggiunto System.Data.SqlClient.

  public class SqlHelper
  {
    private static readonly string mClassName = 
                            System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;
  }
Public Class SqlHelper
  Private Shared ReadOnly mClassName As String = _
                          System.Reflection.MethodBase.GetCurrentMethod.ReflectedType.Name
End Class

La struttura base della classe, ricordo a chi scrive in VB a cui il namespace non compare, di verificare che il Root Namespace sia corretto (nella zona My.Project, oppure nella visualizzazione classi)

     public static bool TestConnection(string pCnString)
    {
      bool retResult = false;
      try
      {
        SqlConnection cn = new SqlConnection();
        cn.ConnectionString = pCnString;
        cn.Open();
        cn.Close();
        retResult = true;
      }
      catch (Exception)
      {
        retResult = false;
      }
      return (retResult);
    }
   Public Shared Function TestConnection(ByVal cnString As String) As Boolean
    Dim retResult As Boolean = False
    Try
      Dim cn As New SqlConnection
      cn.ConnectionString = cnString
      cn.Open()
      cn.Close()
      retResult = True

    Catch ex As Exception
      retResult = False
    End Try
    Return retResult
  End Function

Il nostro primo metodo che lavora con i database, un metodo molto semplice, che testa la connessione a database semplicemente aprendola e chiudendola, e restituisce la riuscita o il fallimento.
Io non ho ancora trovato un metodo più elegante, anche se sono certa che c'è il modo per verificare che il SQL Server esista e sia raggiungibile, di certo, per quanto sia un po' lenta, questa funzione è quella più facile da applicare. Dico che è lenta perché chiaramente se la connessione è sbagliata deve aspettare il timeout per dare errore. Chi sa un modo migliore, faccia un fischio e ci dia il link al suo articolo, al suo blog, o ci mandi il codice che lo inseriamo e lo citiamo, oltre a ringraziarlo sentitamente.

La classe FrmConfig
Aggiungiamo una form al progetto UsingSqlServer (tasto destro Add>Windows Forms>), chiamiamola FrmConfig e, dopo averla generata, apriamola nel designer, assegnamo "Configura Setting" alla sua Text.
Dentro, ci disegnamo:

Form Configurazione tab Applicazione Form Configurazione dati Utente

Disegnata la form, vediamo che cosa metteremo nella partial class .cs o nella class .vb (per VB è partial class solo la parte designer, per C# sono entrambe partial class, come è più giusto, ma non stiamo a disquisire):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using TAndT.Base.Collections;
using TAndT.UI;
using TAndT.Data;
Imports System
Imports System.Collections
Imports System.Data
Imports System.Drawing
Imports System.Text
Imports System.Windows.Forms
Imports TAndT.Base.Collections
Imports TAndT.UI
Imports TAndT.Data

Le direttive using/Imports: oltre ad alcuni namespace del Framework, inseriamo quelli delle nostre librerie, ove si trovano membri che utilizzeremo nella form. Naturalmente, abbiamo anche aggiunto TAndT.Data alle References di UsingSqlServer.

namespace TAndT.UsingSqlServer
{

  public partial class FrmConfig : Form
  {

    private static readonly string mClassName = 
                            System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;
  }
}
Public Class FrmConfig
 
  Private Shared ReadOnly mClassName As String = _
                          System.Reflection.MethodBase.GetCurrentMethod.ReflectedType.Name

End Class

La struttura base della classe, come sempre non cambia rispetto alle classi che non sono parte della User Interface.

    private const string FMP_ConnectionKo = "Impossibile connettersi al database {0} su server {1}";
    private const string FMP_ConnectionOk = "Connessione a database {0} su server {1}  OK";
    private const string TBL_ApplicationSettings = "ApplicationSettings";
    private const string TBL_UserSettings = "UserSettings";
    private const string TXT_Nome = "Nome";
    private const string TXT_Valore = "Valore";
    private const string WAR_DatiModificati = "I dati sono stati modificati, si desidera salvare " 
                                            & "la configurazione prima di testare la connessione?";
    private const string WAR_SaveOnExit = 
                            "Ci sono state modifiche ai dati, si desidera salvarle prima di uscire";
    private const string WAR_Salva = "Salvo i settings?";
    private const string WAR_Salvato = "Setting salvati con successo.";
  Private Const FMP_ConnectionKo As String = "Impossibile connettersi al database {0} su server {1}"
  Private Const FMP_ConnectionOk As String = "Connessione a database {0} su server {1}  OK"
  Private Const TBL_ApplicationSettings As String = "ApplicationSettings"
  Private Const TBL_UserSettings As String = "UserSettings"
  Private Const TXT_Nome As String = "Nome"
  Private Const TXT_Valore As String = "Valore"
  Private Const WAR_DatiModificati As String = "I dati sono stati modificati, si desidera salvare " _
                                             & "la configurazione prima di testare la connessione?"
  Private Const WAR_SaveOnExit As String = _
                             "Ci sono state modifiche ai dati, si desidera salvarle prima di uscire"
  Private Const WAR_Salva As String = "Salvo i settings?"
  Private Const WAR_Salvato As String = "Setting salvati con successo."

Le costanti sopraelencate rappresentano varie stringhe che useremo nella form. I loro nomi sono contrassegnati da particolari prefissi:

Usare costanti (come fatto già nella puntata precedente) è uno dei due metodi per fare in modo che le stringhe fisse di applicazione si trovino tutte in uno stesso luogo, onde facilitarne la gestione. E' anche un buon modo per dare più lavoro al compilatore e meno lavoro all'applicazione. Se prevedete di tradurre i vostri programmi in più lingue, usate invece i file di risorse come resources.resx, dove abbiamo messo anche le immagini delle icone. Nel corso dello sviluppo del codice relativo a questa serie di articoli, useremo un po' le costanti nel codice, un po' le risorse, a seconda di come ci sembra meglio fare in base al contesto.

    bool mDataModified = false;
    DataTable mDtApp;
    DataTable mDtUser;
  Private mDataModified As Boolean = False
  Private mDtApp As DataTable
  Private mDtUser As DataTable

Abbiamo tre campi in questa classe, un flag per segnarci se ci sono state modifiche nei dati e due DataTable, che alimenteranno le datagrid.

    public FrmConfig()
    {
      InitializeComponent();
      mDtApp = null;
      mDtUser = null;
    }
  Public Sub New()

    InitializeComponent()
    mDtApp = Nothing
    mDtUser = Nothing

  End Sub

Il costruttore, ove non facciamo nulla di speciale, salvo indicare che all'inizio le DataTable sono nulle.

In fase di caricamento della form, vogliamo caricare le nostre DataTable nelle nostre DataGrid, delle quali dobbiamo curare la formattazione. Inoltre, vogliamo gestire le eventuali modifiche ai dati con un solo gestore di evento.
Quindi prepariamo due metodi:

    private void ConfiguraDataGrid()
    {
      try
      {
        dgApp.AllowUserToAddRows = false;
        dgApp.AllowUserToDeleteRows = false;
        dgApp.AllowUserToResizeColumns = false;
        dgApp.AllowUserToResizeRows = false;
        dgUser.AllowUserToAddRows = false;
        dgUser.AllowUserToDeleteRows = false;
        dgUser.AllowUserToResizeColumns = false;
        dgUser.AllowUserToResizeRows = false;

        DataGridViewColumn col = this.dgApp.Columns[TAndTSettings.FLD_NAME];
        col.ReadOnly = true;
        col.HeaderText = TXT_Nome;
        col.Width = 100;

        col = this.dgApp.Columns[TAndTSettings.FLD_VALUE];
        col.HeaderText = TXT_Valore;
        col.Width = 250;

        col = this.dgUser.Columns[TAndTSettings.FLD_NAME];
        col.HeaderText = TXT_Nome;
        col.Width = 100;

        col = this.dgUser.Columns[TAndTSettings.FLD_VALUE];
        col.HeaderText = TXT_Valore;
        col.Width = 250;

      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Private Sub ConfiguraDataGrid()
    Try
      dgApp.AllowUserToAddRows = False
      dgApp.AllowUserToDeleteRows = False
      dgApp.AllowUserToResizeColumns = False
      dgApp.AllowUserToResizeRows = False
      dgUser.AllowUserToAddRows = False
      dgUser.AllowUserToDeleteRows = False
      dgUser.AllowUserToResizeColumns = False
      dgUser.AllowUserToResizeRows = False

      Dim col As DataGridViewColumn = dgApp.Columns(TAndTSettings.FLD_NAME)
      col.ReadOnly = True
      col.HeaderText = TXT_Nome
      col.Width = 100

      col = dgApp.Columns(TAndTSettings.FLD_VALUE)
      col.HeaderText = TXT_Valore
      col.Width = 250

      col = dgUser.Columns(TAndTSettings.FLD_NAME)
      col.HeaderText = TXT_Nome
      col.Width = 100

      col = dgUser.Columns(TAndTSettings.FLD_VALUE)
      col.HeaderText = TXT_Valore
      col.Width = 250

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try
  End Sub

Il metodo ConfiguraDatagrid, una volta assegnate le DataSource (le nostre DataTable) alle due DataGrid, si occupa di formattare le colonne e di verificare che l'utente possa modificare solo i dati che decidiamo noi. Ci siamo limitati a qualcosa di semplice, ma con un po' di fantasia e curiosando negli oggetti della DataGrid sicuramente si possono fare molte cose simpatiche.

    void DataChangesHandler(object sender, DataColumnChangeEventArgs e)
    {
      try
      {
        this.mDataModified = true;
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Private Sub DataChangeHandler(ByVal sender As Object, ByVal e As DataColumnChangeEventArgs)

    Try
      mDataModified = True

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try

  End Sub

L'event handler della modifica dati su un campo delle DataTable fa una cosa molto semplice, pone a true il flag mDataModified. Gli eventi della DataTable sono un buon posto per gestire varie cose, oltre al segnalino che eviterà la chiusura senza salvataggio: anche una validazione dei dati, ad esempio. Infatti, se curiosate dentro ai DataColumnChangeEventArgs vi troverete vari oggetti interessanti. Ovviamente vi sono vari eventi che hanno lo scopo di aiutarci in questo anche sulla DataGridView e probabilmente li vedremo nei futuri articoli.

    private void FrmConfig_Load(object sender, EventArgs e)
    {
      try
      {

        mDtApp = UsingSqlServerConfig.AppSettings.GetDataTable(TBL_ApplicationSettings);
        mDtApp.AcceptChanges();
        mDtApp.ColumnChanged += new DataColumnChangeEventHandler(DataChangesHandler);
        mDtUser = UsingSqlServerConfig.UserSettings.GetDataTable(TBL_UserSettings);
        mDtApp.AcceptChanges();
        mDtUser.ColumnChanged += new DataColumnChangeEventHandler(DataChangesHandler);
        this.dgApp.DataSource = mDtApp;
        this.dgUser.DataSource = mDtUser;
        ConfiguraDataGrid();
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub FrmConfig_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Try
      mDtApp = UsingSqlServerConfig.AppSettings.GetDataTable(TBL_ApplicationSettings)
      mDtApp.AcceptChanges()
      AddHandler mDtApp.ColumnChanged, AddressOf DataChangeHandler
      mDtUser = UsingSqlServerConfig.UserSettings.GetDataTable(TBL_UserSettings)
      mDtApp.AcceptChanges()
      AddHandler mDtUser.ColumnChanged, AddressOf DataChangeHandler
      dgApp.DataSource = mDtApp
      dgUser.DataSource = mDtUser
      ConfiguraDataGrid()

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Nella gestione dell'evento Load della FrmConfig carichiamo le nostre DataTable fissando i dati con un AcceptChanges e assegnamo all'evento ColumnChanged il gestore precedentemente implementato. Inoltre assegnamo alle DataGrid le tabelle come DataSource e lanciamo il metodo ConfiguraDataGrid.

Il salvataggio dei dati è opportuno sia fatto non solo manualmente, tramite il metodo che gestisce il click sul pulsante tooSalva, ma anche in modo automatico, dal metodo che controlla la connessione al database, perché la connection string viene composta usando i setting eventualmente appena modificati. Quindi implementiamo un metodo a parte.

    private void Salva()
    {
      try
      {
        UsingSqlServerConfig.AppSettings.SetFromDataTable(mDtApp, false);
        UsingSqlServerConfig.UserSettings.SetFromDataTable(mDtUser, false);
        UsingSqlServerConfig.SaveSettings();
        UsingSqlServerConfig.LoadSettings();
        this.mDataModified = false;
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Private Sub Salva()
    Try
      UsingSqlServerConfig.AppSettings.SetFromDataTable(mDtApp, False)
      UsingSqlServerConfig.UserSettings.SetFromDataTable(mDtUser, False)
      UsingSqlServerConfig.SaveSettings()
      UsingSqlServerConfig.LoadSettings()
      mDataModified = False

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try
  End Sub

Il metodo di salvataggio dati aggiorna le collection sulla classe dei setting, salva i dati e per sicurezza ricarica i dati salvati, impostando a false il flag mDataModified.

    private void tooSalva_Click(object sender, EventArgs e)
    {
      try
      {
        this.dgApp.EndEdit();
        this.dgUser.EndEdit();
        if (Warnings.SiNo(WAR_Salva) == DialogResult.Yes)
        {
          Salva();
          Warnings.Info(WAR_Salvato);
        }
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName,
          System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub tooSalva_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                             Handles tooSalva.Click
    Try
      dgApp.EndEdit()
      dgUser.EndEdit()
      If Warnings.SiNo(WAR_Salva) = DialogResult.Yes Then
        Salva()
        Warnings.Info(WAR_Salvato)
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Il bottone tooSalva, molto semplicemente, salva gli eventuali dati non ancora aggiornati sulle due DataGrid, chiede conferma e chiama la funzione di salvataggio dati. Se tutto va bene, viene mostrato il messaggio di salvataggio effettuato.

    private void tooAnnulla_Click(object sender, EventArgs e)
    {
      this.dgApp.EndEdit();
      this.dgUser.EndEdit();

      this.mDtApp.RejectChanges();
      this.mDtUser.RejectChanges();
      this.mDataModified = false;
    }
  Private Sub tooAnnulla_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                                                                            Handles tooAnnulla.Click

    dgApp.EndEdit()
    dgUser.EndEdit()

    mDtApp.RejectChanges()
    mDtUser.RejectChanges()
    mDataModified = False

  End Sub

Il bottone tooAnnulla termina le modifiche effettuate sulle DataGrid, per prassi, poi annulla le modifiche alle due DataTable, ripristinando quanto caricato al caricamento della form o dopo l'ultimo uso del tasto tooSalva, infine ripristina il flag mDataModified.

    private void tooTestConnessione_Click(object sender, EventArgs e)
    {
      try
      {
        this.dgApp.EndEdit();
        this.dgUser.EndEdit();
        if (this.mDataModified)
        {
          if (Warnings.SiNo(WAR_DatiModificati) == DialogResult.Yes)
          {
            Salva();
          }
        }
        if (SqlHelper.TestConnection(UsingSqlServerConfig.CnString))
        {
          Warnings.Info(
            string.Format(FMP_ConnectionOk, UsingSqlServerConfig.SqlDatabase, 
                          UsingSqlServerConfig.SqlServer));
        }
        else
        {
          Warnings.Avviso(
            string.Format(FMP_ConnectionKo, UsingSqlServerConfig.SqlDatabase, 
                          UsingSqlServerConfig.SqlServer));
        }
      }
      catch (Exception ex)
      {
        Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex);
      }
    }
  Private Sub tooTestConnessione_Click(ByVal sender As Object, ByVal e As System.EventArgs) 
                                                                    Handles tooTestConnessione.Click
    Try
      dgApp.EndEdit()
      dgUser.EndEdit()
      If mDataModified Then
        If Warnings.SiNo(WAR_DatiModificati) = DialogResult.Yes Then
          Salva()
        End If
      End If

      If SqlHelper.TestConnection(UsingSqlServerConfig.CnString) Then
        Warnings.Info(String.Format(FMP_ConnectionOk, UsingSqlServerConfig.SqlDatabase, _
                                    UsingSqlServerConfig.SqlServer))
      Else
        Warnings.Avviso(String.Format(FMP_ConnectionKo, UsingSqlServerConfig.SqlDatabase, _
                                      UsingSqlServerConfig.SqlServer))
      End If

    Catch ex As Exception
      Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
    End Try
  End Sub

Il tasto di controllo della connessione verifica ed eventualmente salva automaticamente le modifiche, poi esegue un test della connessione utilizzando i setting 'attuali' e il metodo helper precedentemente implementato.

    private void FrmConfig_FormClosing(object sender, FormClosingEventArgs e)
    {
      if (e.CloseReason != CloseReason.ApplicationExitCall &&
        e.CloseReason != CloseReason.WindowsShutDown &&
        e.CloseReason != CloseReason.TaskManagerClosing)
      {
        if (this.mDataModified)
        {
          if (Warnings.SiNo(WAR_SaveOnExit) == DialogResult.Yes)
          {
            e.Cancel = true;
          }
        }
      }
    }
  Private Sub FrmConfig_FormClosing(ByVal sender As Object, _
                                    ByVal e As System.Windows.Forms.FormClosingEventArgs) _
                                                                              Handles Me.FormClosing
    If Not (e.CloseReason = CloseReason.ApplicationExitCall _
            OrElse e.CloseReason = CloseReason.WindowsShutDown _
            OrElse e.CloseReason = CloseReason.TaskManagerClosing) Then
      If mDataModified Then
        If Warnings.SiNo(WAR_SaveOnExit) = DialogResult.Yes Then
          e.Cancel = True
        End If
      End If
    End If
  End Sub

Il controllo di chiusura della form verifica che la chiusura non sia richiesta tramite una di tre possibilità, che i dati siano da salvare, e che si vogliano salvare, nel qual caso annulla la chiusura.

La FrmMain
Vediamo ora le modifiche alla form MDI per gestire la nuova form. Aggiungiamo sul MenuStrip un nuovo menu, che chiamiamo "Strumenti" (ci viene generato un menuitem strumentiToolStripMenuItem, nella cui Text aggiungiamo un & prima della S), entriamo nel menu e aggiungiamo un'opzione "Configura" (il che ci genera un configuraToolStripMenuItem, nella cui Text aggiungiamo un & prima della C). Facciamoci doppio click e generiamo il metodo di gestione del click dell'opzione Configura.
In questo metodo vogliamo verificare se la form è già aperta: se non lo è, la apriamo; altrimenti la portiamo in primo piano. Quindi prepariamo un metodo a questo scopo:

    private Form VediSeIlFormEAperto(string pName)
    {
      try
      {
        Form retForm = null;
        foreach (Form child in this.MdiChildren)
        {
          if (child.Name == pName)
          {
            retForm = child;
            break;
          }
        }
        return (retForm);
      }
      catch (Exception ex)
      {
        throw new ApplicationException(" " + mClassName + "."
          + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
      }
    }
  Private Function VediSeIlFormEAperto(ByVal name As String) As Form
    Try
      Dim retForm As Form = Nothing
      For Each child As Form In Me.MdiChildren
        If child.Name = name Then
          retForm = child
          Exit For
        End If
      Next
      Return retForm

    Catch ex As Exception
      Throw New ApplicationException(" " + mClassName + "." _
                                         + System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
    End Try
  End Function

Il metodo per il controllo dei form aperti è molto semplice: scorre i nomi delle form figlie dell'MDI.
Ovviamente non è il solo modo né il più efficiente; magari in un prossimo futuro adotteremo una soluzione più professionale, ma, per ora e per i nostri limitati scopi, possiamo accontentarcene.

    private void configuraToolStripMenuItem_Click(object sender, EventArgs e)
    {
      FrmConfig frm = (FrmConfig)VediSeIlFormEAperto("FrmConfig");
      if (frm == null)
      {
        frm = new FrmConfig();
        frm.Icon = this.Icon;
        frm.MdiParent = this;
        frm.Show();
      }
      else
      {
        frm.WindowState = FormWindowState.Normal;
        frm.BringToFront();
      }
    }
  Private Sub ConfiguraToolStripMenuItem_Click(ByVal sender As System.Object, _
                                               ByVal e As System.EventArgs) _
                                               Handles ConfiguraToolStripMenuItem.Click

    Dim frm As FrmConfig = DirectCast(VediSeIlFormEAperto("FrmConfig"), FrmConfig)
    If frm Is Nothing Then
      frm = New FrmConfig
      frm.Icon = Me.Icon
      frm.MdiParent = Me
      frm.Show()
    Else
      frm.WindowState = FormWindowState.Normal
      frm.BringToFront()
    End If

  End Sub

Come già detto, facciamo in modo di avere una sola FrmConfig aperta: o una nuova, o quella già aperta.

Arrivederci alla prossima puntata
Bene, se non abbiamo dimenticato qualcosa, ora abbiamo gli strumenti (sia pure rozzi) per iniziare a vedere le cose più interessanti riguardo SQL Server 2005 e ADO.NET.
Nella prossima puntata, inizieremo ad introdurre le strutture che ci servono per generare un database.
Come al solito, il codice fin qui prodotto è scaricabile dall'Area Download.
(nota: il codice è 'sempre' aggiornato, con eventuali correzioni al codice precedente, non citate nell'articolo).
Potete scrivere Feedback (commenti, critiche, suggerimenti, correzioni) sul blog di Sabrina.