Impostazioni di applicazione in Visual Basic Net (2)
a cura di Diego Cattaruzza (requisiti: Framework 2.0)Premessa
Questo articolo è la continuazione di un articolo precedente, anche per quanto riguarda il codice. Si invita quindi a leggerlo e particolarmente a scaricare il codice che ne è a corredo, dato che in questa seconda parte si continua lo sviluppo dello stesso progetto.Nel precedente articolo sono state esposte alcune modalità di gestione delle impostazioni di programma, legate più o meno all'ambiente di programmazione e agli automatismi da esso offerti (cioè gestioni implementate in sede di progettazione, non 'soltanto nel codice', in modo da venir esplicate solo in fase di esecuzione).
In questo articolo si presenteranno alternative di gestione per sfuggire agli automatismi, il che permette di avere un maggior controllo, e per offrire una maggior libertà di azione e di intervento, particolarmente nelle necessità di assistenza.
Avvertenza: nel corso dell'articolo si farà vedere del codice che poi verrà cambiato, cioè non sarà presente nel codice a corredo. Quindi si raccomanda di seguire l'articolo, prima di scaricare il codice a corredo.
Il percorso del file.config
Una delle questioni più intriganti poste dal sistema di configurazione proposto automaticamente da .Net riguarda il percorso dei file di configurazione. Le possibilità sono diverse e dipendenti da più fattori, tra cui il tipo di installazione (ad esempio, con Click-Once il file user.config ha un percorso diverso da quello che avrebbe in seguito a una installazione 'normale') e il sistema operativo (tra Windows XP e Windows Vista le cartelle dell'utente hanno percorsi diversi e diverse accessibilità).
Questa situazione porta a una certa confusione che, per un programmatore, bisogna cercare di evitare per quanto possibile.Nell'articolo precedente s'era fatto cenno a una mia lettura affrettata dell'help, circa il percorso del file user.config, creduto esposto dalla proprietà LocalUserAppDataPath dell'oggetto Application e in realtà scoperto come ricavabile dalla proprietà FilePath di un oggetto Configuration appositamente istanziato.
Application espone altri percorsi, ma è opportuno sperimentare direttamente nella pratica: riaprite il progetto IniSettingsStudio, il codice della FrmMain, modificate il metodo cmdInfo_Click come segue:
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}{9}", _ "User.Name: ", My.User.Name & cr, _ "LocalUserAppDataPath: ", cr & Application.LocalUserAppDataPath & cr, _ "UserAppDataPath: ", cr & Application.UserAppDataPath & cr, _ "CommonAppDataPath: ", cr & Application.CommonAppDataPath & cr, _ "UserConfigFilePath: ", cr & UserConfigFilePath() & cr _ ), _ "Informazioni Utente") End SubCompilate ed eseguite. Come potete vedere, i vari percorsi sono composti più o meno dalle stesse voci: utente, ditta, prodotto, versione, che sono, rispettivamente: System.Environment.UserName, Application.CompanyName, Application.ProductName, Application.ProductVersion, come possiamo verificare se modifichiamo il codice in questo modo:
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}{9}{10}{11}{12}{13}{14}{15}{16}{17}", _ "UserName: ", Environment.UserName & cr, _ "CompanyName: ", Application.CompanyName & cr, _ "ProductName: ", Application.ProductName & cr, _ "ProductVersion: ", Application.ProductVersion & cr & cr, _ "User.Name: ", My.User.Name & cr, _ "LocalUserAppDataPath: ", cr & Application.LocalUserAppDataPath & cr, _ "UserAppDataPath: ", cr & Application.UserAppDataPath & cr, _ "CommonAppDataPath: ", cr & Application.CommonAppDataPath & cr, _ "UserConfigFilePath: ", cr & UserConfigFilePath() & cr _ ), _ "Informazioni Applicazione e Utente") End SubAl di là di queste informazioni, si fa notare che è opportuno comprendere la ragione di ciascuno di questi percorsi, per decidere quale sfruttare per le nostre necessità e perché.
La prima distinzione è tra le impostazioni valide per un solo utente o per tutti gli utenti; queste ultime riguardano, evidentemente, l'applicazione di per sé. E' questo (CommonAppDataPath) il luogo deputato ad accogliere il 'vero' app.config (naturalmente con un nome diverso), quello i cui parametri possono essere cambiati, quando serve (si ricorda che il file app.config prodotto in sede di progettazione è di sola lettura ed espone, per quanto riguarda le impostazioni lato utente, soltanto i valori di default).
La seconda distinzione è tra gli ambiti di visibilità dell'utente: quando l'account utente è un account valido per una rete di pc (in una LAN Intranet, ad esempio), è opportuno usare, per i 'nostri' user.config il percorso 'locale' (LocalUserAppDataPath); quando questa localizzazione non ha importanza, è facoltativo usare il percorso utente 'classico' (UserAppDataPath), tenendo però presente che, ove presente, il file in 'locale' prevale sull'altro.
L'ultimo percorso, come si è già visto, è quello del file user.config conseguente all'uso degli automatismi forniti da Visual Studio.Una possibile linea di condotta è quindi quella di approfittare della proprietà ApplicationSettings dei controlli usati per impostazioni formali (preferenze dell'utente per colori, caratteri e cose simili) - sempre che si stia sviluppando una Windows Application, ovviamente - ma di implementare una propria gestione per impostazioni di contenuto relative all'utente o all'applicazione.
Creare una propria classe di configurazione
La strada più semplice e immediata per gestire in proprio le impostazioni di programma è costituita dallo sviluppo di una propria classe di configurazione di applicazione.
Nella sua forma più semplice, deve constare di alcune proprietà per determinare il proprio ambito di azione (in qualità di configurazione per l'utente corrente o per tutti gli utenti) e di un paio di metodi per leggere o scrivere i file di impostazioni. In base a questa prima analisi, i requisiti minimi (finora) sono i riferimenti ai namespace System.IO e System.Xml.Il primo abbozzo della nuova classe Configurazione contiene queste due direttive e due proprietà di sola lettura, per una delle quali è conveniente implementare un tipo enumerato. Il costruttore della classe riceve un parametro per definire l'ambito di riferimento delle impostazioni e di conseguenza il percorso del file di configurazione:
Imports System.IO Imports System.Xml.Serialization Public Class Configurazione Private mScope As ConfigurationScope Public ReadOnly Property Scope() As ConfigurationScope Get Return mScope End Get End Property Private mConfigFileName As String Public ReadOnly Property ConfigFileName() As String Get Return mConfigFileName End Get End Property Public Sub New(ByVal ambito As ConfigurationScope) mScope = ambito If mScope = ConfigurationScope.ApplicationScope Then mConfigFileName = Application.CommonAppDataPath & "\" & Application.ProductName & ".config" Else mConfigFileName = Application.UserAppDataPath & "\" & Application.ProductName & ".config" End If End Sub End Class Public Enum ConfigurationScope ApplicationScope UserScope End EnumQuesta classe può essere la base da cui derivare per ereditarietà le specifiche classi che servono alle specifiche applicazioni: basterà aggiungere a ciascuna derivata le specifiche proprietà corrispondenti alle impostazioni che si vuole gestire.
Ad esempio, aggiungete una nuova classe, IniSettingsStudioConfig, che erediti da Configurazione (poiché la classe base espone un costruttore con parametro, bisogna implementarne uno analogo per la classe derivata):Public Class IniSettingsStudioConfig Inherits Configurazione Public Sub New(ByVal ambito As ConfigurationScope) MyBase.New(ambito) End Sub End ClassPotete verificare immediatamente il suo funzionamento aggiungendo un pulsante (Name = cmdNuovoConfig, Text = "Nuovo Config") alla FrmMain e implementando questo codice:
Private Sub cmdNuovoConfig_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles cmdNuovoConfig.Click Dim iniApp As New IniSettingsStudioConfig(ConfigurationScope.ApplicationScope) Dim cr As String = Environment.NewLine MessageBox.Show(String.Format("{0}{1}{2}{3}{4}", _ "iniApp.ConfigFileName: " & cr, iniApp.ConfigFileName, cr, _ "iniApp.Scope: ", iniApp.Scope.ToString), _ "Nuovo Config") End SubAdesso, potete perfezionare questo impianto con una classe IniSettingsStudioGlobals che esponga due oggetti IniSettingsStudioConfig statici per renderli visibili e utilizzabili da tutta l'applicazione, da inizializzare nell'evento Load della form di avvio, nel modulo della quale è comodo impostare una direttiva Imports:
Public Class IniSettingsStudioGlobals Public Shared IniApp As IniSettingsStudioConfig Public Shared IniUser As IniSettingsStudioConfig End ClassImports IniSettingsStudio.IniSettingsStudioGlobalsPrivate 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 IniApp = New IniSettingsStudioConfig(ConfigurationScope.ApplicationScope) IniUser = New IniSettingsStudioConfig(ConfigurationScope.UserScope) End SubI più attenti avranno già indovinato cosa non va: i due oggetti differiscono solo per l'ambito di riferimento, ma, se si aggiungesse una proprietà (ad esempio ConnectionString) alla classe IniSettingStudioConfig, questa impostazione si vedrebbe sia nella configurazione per un utente che in quella per l'applicazione.
Quindi è necessario creare due classi derivate e inserire esplicitamente il valore relativo all'ambito. Questo si potrebbe ottenere aggiungendo esplicitamente il costruttore privo di parametri.
Cioè, nella IniSettingStudioConfig:Public Sub New() MyBase.New(ConfigurationScope.ApplicationScope) End Sube in una nuova classe IniSettingStudioUserConfig:
Public Class IniSettingStudioUserConfig Inherits Configurazione Public Sub New(ByVal ambito As ConfigurationScope) MyBase.New(ambito) End Sub Public Sub New() MyBase.New(ConfigurationScope.UserScope) End Sub End ClassPerò in tal modo il costruttore parametrico diventa superfluo. Ma la programmazione a oggetti ci offre ben altro: non solo l'ereditarietà, appena provata, ma anche il polimorfismo, tramite le interfacce.
Modificate la classe Configurazione in interfaccia IConfigurazione, lasciando solo le firme delle due proprietà:Interface IConfigurazione ReadOnly Property Scope() As ConfigurationScope ReadOnly Property ConfigFileName() As String End InterfaceQuindi applicate l'interfaccia alle due classi derivate:
Public Class IniSettingsStudioConfig Implements IConfigurazione Public ReadOnly Property ConfigFileName() As String Implements IConfigurazione.ConfigFileName Get Return Application.CommonAppDataPath & "\" & Application.ProductName & ".config" End Get End Property Public ReadOnly Property Scope() As ConfigurationScope Implements IConfigurazione.Scope Get Return ConfigurationScope.ApplicationScope End Get End Property End ClassPublic Class IniSettingStudioUserConfig Implements IConfigurazione Public ReadOnly Property ConfigFileName() As String Implements IConfigurazione.ConfigFileName Get Return Application.UserAppDataPath & "\" & Application.ProductName & ".config" End Get End Property Public ReadOnly Property Scope() As ConfigurationScope Implements IConfigurazione.Scope Get Return ConfigurationScope.UserScope End Get End Property End ClassE modificate in FrmMain le righe che vi fanno riferimento:
Public Class IniSettingsStudioGlobals Public Shared IniApp As IniSettingsStudioConfig Public Shared IniUser As IniSettingStudioUserConfig End ClassPrivate 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 IniApp = New IniSettingsStudioConfig IniUser = New IniSettingStudioUserConfig End SubPrivate Sub cmdNuovoConfig_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles cmdNuovoConfig.Click Dim iniApp As New IniSettingsStudioConfig '...Potete così riprovare a fare clic sul pulsante cmdNuovoConfig e verificare che funziona.
Conclusioni
In questo articolo è stato illustrato un ragionamento sulla struttura di una classe per la configurazione delle nostre applicazioni, passando da una classe base da cui ereditare a una interfaccia da implementare.
In un successivo articolo, si implementeranno i metodi per completare lo sviluppo.
Il codice fin qui prodotto è scaricabile dall'area download.