Impostazioni di applicazione in Visual Basic Net
a cura di Diego Cattaruzza (requisiti: Framework 2.0)

Premessa
Una questione che viene posta in modo ricorrente riguarda la gestione delle impostazioni di programma, cioè l'organizzazione di informazioni variabili che si vuole siano persistenti tra una esecuzione e l'altra del programma.
Chi ha già sviluppato con Visual Basic 6 si ricorda di aver gestito questa cosa mediante file di inizializzazione (normali file di testo, con estensione .ini, strutturati in sezioni contenenti elenchi di coppie chiave-valore, da leggere o scrivere attraverso apposite API), oppure tramite le istruzioni o funzioni Get/Save/DeleteSetting che agivano in una apposita sezione del registro di configurazione di Windows, pratica assolutamente sconsigliata, su questo sito - ragion per cui non ne se ne leggerà nel seguito.
Per facilitare l'uso delle API dedicate ai file di inzializzazione, i programmatori più avvertiti avranno anche sviluppato delle classi apposite che avvolgevano le funzioni di sistema (proprio questa funzionalità è argomento del mio articolo 'La mia prima classe').

Come si fa in Visual Basic Net? Questo articolo si propone di spiegarlo, con ulteriori considerazioni.

Application Settings
Tutte le configurazioni (di quasi qualsiasi cosa), in Net, sono registrate in appositi file XML di configurazione, secondo lo schema descritto nel namespace System.Configuration.
In VB.Net ogni applicazione creata con Visual Studio ha i suoi file app.config e Settings.settings, che vengano o meno usati.

Sono file XML strutturati che possono essere assimilati ai noti file .ini, con la differenza che le coppie chiave-valore

[Sezione]
chiave=valore

sono sostituite da una gerarchia elemento-valore:

<sezione.Settings>
  <setting name="chiave" tipochiave="">
    <value>valore</value>
  </setting>
</sezione.Settings>

Un'altra differenza tra la configurazione 'ini-com' e la configurazione 'xml-net' è che quest'ultima è fortemente tipizzata (la parola tipochiave, che non esiste, serve solo a indicare questa ulteriore caratteristica). Naturalmente, è possibile creare diverse sezioni, con impostazioni per l'applicazione o per l'utente.

Con VB.Net è possibile approfittare delle funzionalità offerte in diverse maniere, dalla più comoda alla più manuale, mentre in VB6 si deve scrivere tutto il codice.

La via più comoda
Da Visual Studio si può impostare la persistenza di proprietà di una Form o di un controllo direttamente dalla finestra proprietà. Seguite questo esempio per persistere il colore di fondo di una Form. L'utente avrà la possibilità di scegliere il proprio colore preferito attraverso il classico dialogo.

  Private Sub cmdChangeBackColor_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                       Handles cmdChangeBackColor.Click
    dlgColor.Color = My.Settings.FrmMainBackColor
    Dim dr As DialogResult = dlgColor.ShowDialog()
    If dr = Windows.Forms.DialogResult.OK Then
      Me.BackColor = dlgColor.Color
    End If
    MessageBox.Show(My.Settings.FrmMainBackColor.ToString, "frmMainBackColor")
  End Sub

Questo codice usa la facilitazione fornita dal namespace My (tipico delle applicazioni Visual Basic), che espone l'oggetto Settings, il quale espone le impostazioni dell'applicazione, comprese quelle create dallo sviluppatore.
Il codice prepara la finestra di dialogo per la scelta del colore di fondo della form, informandola dell'attuale impostazione, ossia impostando la proprietà Color della dlgColor con il valore della impostazione frmMainBackColor, quindi attiva il dialogo e ne riceve il risultato, se questo è OK imposta il colore di fondo della FrmMain al colore impostato dalla finestra di dialogo; in ogni caso, mostra l'attuale valore dell'impostazione frmMainBackColor, la quale rimane la stessa oppure muta, a seconda dell'esito del dialogo.

Compilate e avviate. Fate clic sul pulsante e cambiate il colore. Chiudete e riavviate, vedrete che la Form ha ripreso l'ultimo colore impostato. Abbiamo quindi ottenuto la persistenza dell'impostazione.

Dietro le quinte
Nella finestra Solution Explorer, se non l'avete ancora fatto per abitudine, fate clic sull'icona per visualizzare tutti i file (Show All Files), espandete My Project e aprite Settings.settings.

Potete vedere l'impostazione di nome frmBackColor, di tipo Color, valida per l'utente, il cui valore di default è il colore Control. Questa è l'impostazione a livello di progettazione.
Quando viene creata una qualsiasi impostazione, Visual Studio crea il file app.config, che, tra le altre cose, contiene la dichiarazione del gruppo di sezioni userSettings, con una sola sezione, IniSettingsStudio.My.MySettings:

    <configSections>
        <sectionGroup name="userSettings" 
                      type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, 
                            Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="IniSettingsStudio.My.MySettings" 
                     type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, 
                           Culture=neutral, PublicKeyToken=b77a5c561934e089" 
                     allowExeDefinition="MachineToLocalUser" requirePermission="false" />
        </sectionGroup>
    </configSections>

nella quale troviamo l'impostazione appena creata e provata:

  <userSettings>
    <IniSettingsStudio.My.MySettings>
      <setting name="frmMainBackColor" serializeAs="String">
        <value>Control</value>
      </setting>
    </IniSettingsStudio.My.MySettings>
  </userSettings>

Qui però il valore è ancora Control, cioè il valore di default; dove sta il valore modificato, che persiste tra una sessione e l'altra di esecuzione del programma? Sta in un file di nome user.config, che si trova, secondo una affrettata lettura dell'help, nella cartella Impostazioni locali\Dati applicazioni\ dell'utente e precisamente nella cartella definita dalla proprietà LocalUserAppDataPath dell'oggetto Application.
Per facilitare la ricerca di questa informazione, aggiungete un Button (Name = cmdInfo, Text = "Mostra Info"), e digitate nel gestore del suo evento Click il seguente codice:

  Private Sub cmdInfo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                            Handles cmdInfo.Click
    Dim cr As String = Environment.NewLine
    MessageBox.Show(String.Format("{0}: {1}{2}{3}: {4}{5}", _
                                  "User.Name", My.User.Name, cr, _
                                  "LocalUserAppDataPath" & cr, Application.LocalUserAppDataPath, cr, _
                    , "Informazioni Utente")
  End Sub

Il codice si limita a predisporre una variabile il cui nome è costituito da soli due caratteri, per usarla nella costruzione di un messaggio tramite il metodo statico Format della classe String, che riceve come parametri la stringa di formato coi segnaposto, e gli elementi da introdurre in quei segnaposto.

Lanciate il programma e fate clic sul pulsante cmdInfo. Tenendo aperta la finestra di dialogo, aprite Gestione Risorse e cercate la cartella indicata.
Sorpresa: non solo la cartella è vuota, ma ce ne sono altre molto simili nel nome, con assurdi finali criptici, in ciascuna delle quali c'è un file user.config!
E' certamente una caratteristica voluta, per scopi di sicurezza e riservatezza, per quanto si sconsigli di usare user.config per archiviarvi dati 'segreti', se non criptati in qualche modo.

Tornando alla nostra indagine: qual è il file giusto? Si intuisce che ce ne sono diversi perché sono stati diversi, nel tempo, gli assembly avviati dopo ogni nostra modifica. Si può presumere che nella realtà dell'uso ce ne sarà sempre solo uno, ma se si volesse essere sicuri di trovare quello relativo all'attuale versione del programma?

Sapete già che i file di configurazione seguono lo schema definito nel Namespace System.Configuration (ve l'ho scritto all'inizio). Cercate in esso se trovate qualcosa che fa al caso nostro. Così troverete la classe omonima, che espone una proprietà FilePath. Bisogna ottenere una istanza di Configuration attraverso uno dei metodi forniti dalla classe ConfigurationManager, che è statica, cioè non è necessario produrne un'istanza per usarne un metodo.
Scoperte queste informazioni, aggiungete al progetto il riferimento al Namespace System.Configuration e implementate il seguente metodo:

  Private Function UserConfigFilePath() As String
    Dim MyConfiguration As Configuration = _
              ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal)
    Return MyConfiguration.FilePath
  End Function

Poi modificate il codice del cmdInfo_Click:

  Private Sub cmdInfo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                            Handles cmdInfo.Click
    Dim cr As String = Environment.NewLine
    MessageBox.Show(String.Format("{0}: {1}{2}{3}: {4}{5}{6}: {7}{8}", _
                                  "User.Name", My.User.Name, cr, _
                                  "LocalUserAppDataPath" & cr, Application.LocalUserAppDataPath, cr, _
                                  "UserConfigFilePath" & cr, UserConfigFilePath, cr) _
                    , "Informazioni Utente")
  End Sub

Potete così andare ad aprire questo file e trovarvi il valore attuale dell'impostazione, qualcosa di simile a:

<configuration>
  <userSettings>
    <IniSettingsStudio.My.MySettings>
      <setting name="frmMainBackColor" serializeAs="String">
        <value>221, 238, 238</value>
      </setting>
    </IniSettingsStudio.My.MySettings>
  </userSettings>
</configuration>

Il file app.config, invece, espone chiavi e valori che possono soltanto essere letti. Le impostazioni attinenti l'Application, sono stabilite in sede di progettazione, quelle relative all'utente generico fungono da indicazioni statiche dei valori di default.

La via un po' meno comoda
Come è logico, si può implementare una impostazione seguendo la strada inversa. Può succedere infatti che si abbia in mente di voler gestire una impostazione prima ancora di aver deciso come farlo.
Immaginiamo che ci serva conservare nickname e indirizzo email dell'utente, ad esempio per fornire queste info nelle richieste di assistenza.
Quindi aprite il file Setting.settings e aggiungete queste impostazioni come indicato in figura:

Queste impostazioni possono essere richieste in fase di installazione, per esempio, oppure nel momento in cui viene richiesta assistenza, oppure in seguito alla scelta di un apposito menu.
A ogni modo, costruiamo una form di dialogo apposita:
Fate clic destro sul progetto e aggiungete una Windows Form di tipo Dialog, dandogli il nome DlgUser. Modificatene il titolo in "Impostazioni account utente".
Aggiungete tre Label (con Text rispettivamente "Utente", "NickName" e "Email") e tre Textbox (txtUser, txtNickName e txtEmail); impostate a True la proprietà ReadOnly di txtUser e disponete il tutto come in figura:

Adesso selezionate la casella txtNickName, quindi nella finestra Proprietà selezionate ApplicationSettings, PropertyBindings, 'puntini'; nella finestra così apertasi selezionate la proprietà Text e vedrete che potete scegliere dall'elenco l'impostazione usrNickName. Lo stesso potrete fare per associare la proprietà Text della casella txtEmail all'impostazione usrEmail.
Notate come vengano proposte nell'elenco solo le impostazioni di tipo congruente con la proprietà che si vuole associare (per una proprietà di tipo String, non viene proposta anche frmMainBackColor, che è di tipo Color).

A questo punto bisogna gestire il contenuto di txtUser e la validità della modifica delle impostazioni, anche in conseguenza del clic sul pulsante OK o sul pulsante Cancel. Infatti, ciò che fanno 'automaticamente' questi pulsanti è solo restituire al chiamante un DialogResult specifico. Il resto deve essere gestito dal codice.
Quindi impostate il contenuto della txtUser al caricamento della Form, e nel contempo depositate le attuali impostazioni:

  Private Sub DlgUser_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                           Handles MyBase.Load
    Me.txtUser.Text = My.User.Name
    Me.txtNickName.Tag = Me.txtNickName.Text
    Me.txtEmail.Tag = Me.txtEmail.Text
  End Sub

E curate il ripristino delle impostazioni precedenti alle eventuali modifiche, nel caso si faccia clic sul pulsante Cancel:

  Private Sub Cancel_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                  Handles Cancel_Button.Click
    Me.txtNickName.Text = Me.txtNickName.Tag
    Me.txtEmail.Text = Me.txtEmail.Tag

    Me.DialogResult = System.Windows.Forms.DialogResult.Cancel
    Me.Close()
  End Sub

In questa sede, non ci si occupa di controllare la validità e l'accettabilità dei dati immessi, come si dovrebbe fare in una applicazione non di studio.
Infine, nella FrmMain, aggiungete un pulsante (Name = cmdUserProfile, Text = "Profilo utente") per verificare e studiare questo comportamento:

  Private Sub cmdUserProfile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                   Handles cmdUserProfile.Click
    Dim du As New DlgUser
    du.ShowDialog()
    du = Nothing
  End Sub

Poiché è 'faticoso' andarsi a cercare il file user.config per verificare i cambiamenti, facciamo in modo che tale fatica la svolga il nostro programmino di studio.
Aggiungete un'altra finestra di dialogo, (Name = DlgFileView, Text = File user.config), eliminate il tasto Cancel e il TableLayoutPanel1, dopo aver spostato il tasto OK. Aggiungete una Textbox (Name = txt, Multiline = True, ScrollBars = Both). Sistemate dimensioni e posizioni dei controlli, in modo che la casella occupi quasi tutta la finestra di dialogo e che il pulsante stia in basso a destra, fino a ottenere pressappoco un risultato come in figura:

Questa form deve conoscere il percorso del file user.config, per trovare il quale si usa già un metodo che non ha senso ricopiare: implementate quindi un overload del metodo New, che riceva quel dato come parametro:

  Private mPath As String

  Public Sub New(ByVal path As String)
    InitializeComponent()
    mPath = path
  End Sub

La lettura va fatta nel metodo di gestione dell'evento Load:

  Private Sub DlgFileView_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                               Handles MyBase.Load
    txt.Text = My.Computer.FileSystem.ReadAllText(mPath)
  End Sub

A questo punto, aggiungete alla FrmMain un altro pulsante (Name = cmdViewUserConfig, Text = "Visualizza user.config") e implementate questo codice per il suo evento Click:

  Private Sub cmdViewUserConfig_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                      Handles cmdViewUserConfig.Click
    Dim df As New DlgFileView(UserConfigFilePath)
    df.ShowDialog()
    df = Nothing
  End Sub

Potrete così vedere direttamente il contenuto del file user.config con le ultime impostazioni inserite:

La via più diretta
L'esame dei file .config permette di ipotizzare e sperimentare la creazione di nuove impostazioni senza far uso del designer.
Aprite il file app.config e aggiungete un nodo setting:

            <setting name="frmDlgUserText" serializeAs="String">
              <value>Impostazioni account utente</value>
            </setting>

Aprite il file Settings.Designer.vb e trovate la classe MySettings, cui aggiungete, in coda alle altre, la proprietà che definisce la nuova impostazione:

    <Global.System.Configuration.UserScopedSettingAttribute(), _
     Global.System.Diagnostics.DebuggerNonUserCodeAttribute(), _
     Global.System.Configuration.DefaultSettingValueAttribute("Impostazioni account utente")> _
    Public Property frmDlgUserText() As String
      Get
        Return CType(Me("frmDlgUserText"), String)
      End Get
      Set(ByVal value As String)
        Me("frmDlgUserText") = value
      End Set
    End Property

Aprite la form DlgUser, modificate, a scopo di successiva verifica, la proprietà Text con una parola qualsiasi, e impostate l'ApplicationSettings/Text alla nuova impostazione, il cui nome trovate nell'elenco, come forse vi aspettavate.
Ciò produce, nel metodo InitializeComponent della DlgUser (nel file DlgUser.Designer.vb) la seguente riga di codice:

    Me.Text = Global.IniSettingsStudio.My.MySettings.Default.frmDlgUserText

L'impostazione creata nell'app.config serve da 'traccia' per l'analoga impostazione presente nello user.config, che va gestito nel primo avvio dell'applicazione:

  Private Sub FrmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    If My.Settings.frmDlgUserText.Contains("utente") Then
      My.Settings.frmDlgUserText = My.Settings.frmDlgUserText.Replace("utente", My.User.Name)
    End If
  End Sub

Se l'impostazione frmDlgUserText ha ancora il valore di default, contiene la parola 'utente', quindi, sostituendola con il nome dell'utente, viene corretta l'impostazione.
Avviamo il programma e verifichiamo che tutto si svolge come previsto.

Conclusioni
Tutto questo ci aiuta a capire come funziona, anche se è poco probabile che davvero serva a qualcuno avere un accesso al file diverso da quello agevolmente permesso dalle vie più o meno comode offerte dall'IDE.

Finora abbiamo constatato che possiamo rimpiazzare i vecchi file .ini con i nuovi .xml, app.config e user.config, con molta maggior efficacia. Almeno per le impostazioni meno complesse.
In un prossimo articolo vedremo come impostare una configurazione completamente sotto il nostro controllo, per i casi in cui gli automatismi offertici dall'IDE non siano soddisfacenti, per quanto comodissimi.

Come al solito, il codice a corredo di questo articolo si trova in area download, e potete scrivere al mio indirizzo per chiarimenti, critiche o suggerimenti.