Una citazione da un grandissimo film degli anni '80 diceva:
"Strano gioco. L'unica mossa vincente è non
giocare."
Chi indovina, vince un premio - non ho ancora
deciso cos'è!
Mi è venuto in mente anche un buon mio amico/nostro collega, che invece di
usare librerie pre-esistenti, preferiva cominciare da zero e farsele da sè. E'
esattamente quello che alla fine ho deciso io per il mio problema riguardante i
settings della mia applicazione HappySign. Ho creato una classe
Settings che espone tutta una serie di membri che sono poi i
settings che voglio usare nella mia applicazione. Ecco un piccolo estratto di
codice:
public class Settings
{
public static string ConfigDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\HappySign";
public static string ConfigFile = @"\Settings.config";
public static string TemporaryFile = @"\Remember.tmp";
#region "Privati"
private bool _TopMost;
private StartupPositionValues _StartupPosition;
private Color _BackColorStart;
private Color _BackColorEnd;
private Color _TitleForeColor;
private Color _TitleForeColorHot;
private Color _BackColorBar;
private Color _ForeColorItem;
private Color _ForeColorItemHot;
#endregion
Quelli elencati sopra sono i membri privati della classe: ovviamente
ho creato anche i membri pubblici. Poi ho aggiunto due metodi:
public void LoadSettings()
{ }
public void WriteSettings()
{ }
che si occupano effettivamente di leggere e scrivere sul mio file di
configurazione. Notare nel primo blocco di codice tre membri statici molto
utili:
ConfigDirectory, che ritorna il path "C:\Documents and
Settings\username\Application
Data\HappySign".
ConfigFile, cablato dentro il codice, e che
ritorna sempre "Settings.config".
TemporaryFile, cablato
dentro il codice, e che ritorna sempre "Remember.tmp".
Quando istanzio un oggetto dalla classe Settings e chiamo il
metodo LoadSettings, questo non fa altro che aprire il file
"C:\Documents and Settings\username\Application
Data\HappySign\Settings.config". La struttura del Settings.config è identica a
quella di qualsiasi app.config. Nel mio caso è qualcosa di simile a questo:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<!--
StartUpPosition = 1, centrato sullo schermo
StartUpPosition = 2, allineato in alto a sinistra
StartUpPosition = 3, allineato in alto a destra
-->
<add key="StartUpPosition" value="3" />
<add key="TopMost" value="False" />
<add key="BackColorStart" value="Moccasin" />
<add key="BackColorEnd" value="LightSkyBlue" />
<add key="TitleForeColor" value="White" />
<add key="TitleForeColorHot" value="Yellow" />
<add key="BackColorBar" value="LightSteelBlue" />
<add key="ForeColorItem" value="Blue" />
<add key="ForeColorItemHot" value="Red" />
</appSettings>
</configuration>
Questa sera predisporrò una piccola procedura di primo
avvio, che si scatena quando non viene trovato il file Settings.config
dentro la directory. In questo caso, ne propongo uno di default, che poi
l'utente ha la facoltà di modificare come vuole tramite UI.
Qualche dettaglio su LoadSettings
Questo metodo è basato
sul fatto che posso scandire tutti i ChildNodes del tag
AppSettings. Nel file XML sopra, i ChildNodes sono 11, tante
quante i settings che voglio leggere. Quindi, ho implementato un banale
for...next e uno switch (purtroppo!): in base al valore di
nodo.Attributes[0].Value, valorizzo il membro
della classe Settings corrispondente.
int howmany = nodoAppSettings.ChildNodes.Count;
// parto da 1 così salto il primo tag che è solo un commento
for(int i=1; i<howmany;i++)
{
nodo = nodoAppSettings.ChildNodes[i];
key = nodo.Attributes[0].Value;
value = nodo.Attributes[1].Value;
switch(key)
{
case "StartUpPosition":
_StartupPosition = (StartupPositionValues) Convert.ToInt32(value);
break;
case "TopMost":
_TopMost = value == "True";
break;
eccetera,
eccetera, eccetera...
E' un metodo che non mi piace, però adesso me lo tengo così. Avrei
potuto usare una HashTable, una Collection o una struttura di questo tipo, così
potevo caricare dinamicamente i valori contenuti in
Settings.config, però perdevo l'enorme vantaggio di 1)
Intellisense 2) Di conseguenza, leggibilità del codice 3) Strong-typed dei
membri 4) Avrei dovuto farcire il codice - ovunque - di casting per convertire
da object al tipo corretto in base al contesto. Avrei
potuto popolare una HashTable, per esempio, con tutti i valori provenienti da
XML, e che arrivano come string: il
popolamento sarebbe avvenuto con un ciclo, ed aggiungendo un elemento per ogni
tag <add> incontrato. Però - ripeto - alla fine mi sarei
trovato un HashTable pieno di stringhe che andavano "castate" di volta in volta
al tipo corretto. Chissà se mi sono spiegato?
Come al solito, per avere maggior controllo è sempre consigliabile farsi le
cose da sè. Ovvio che magari non è il massimo delle performance, non è
scalabile, non avrà un sacco di caratteristiche proprie delle library più
quotate, ma almeno fa quello che dico io (e solo quello) e a me per adesso
basta.