Isolated Storage: memorizziamo i settings della nostra applicazione – Parte 2: una soluzione più elegante

Print Content | More

Nel post precendete abbiamo famigliarizzato con la classe IsolatedStorageSettings, che ci permette di gestire in maniera semplice le impostazioni della nostra applicazione: il metodo che però abbiamo visto non era dei più efficienti, dato che tutto il codice per gestire le impostazioni era memorizzato nel code behind, il che rende l’applicazione difficilmente testabile e più difficile da mantenere.

In questo post vedremo una soluzione più elegante ed efficiente, che prevede l’implementazione della gestione delle impostazioni in una classe separata. Sfruttando questa classe, andremo ad implementare due soluzioni diverse:

  • Una che sfrutta il binding di Silverlight e che ci permette di memorizzare instantanemente qualsiasi modifica ad una delle impostazioni. Sfruttando questa soluzione, non scriveremo neanche una riga di codice nel code behind della nostra pagina contenete il form.
  • Una che richiede comunque la scrittura di codice nel code behind, ma che ci permette di implementare un pulsante di Conferma per evitare che le modifiche vengano salvate instantaneamente. Il risultato sarà analogo a quello ottenuto nell’applicazione realizzata nel post precedente.

Per realizzare tale soluzione, sfrutteremo i consigli che Microsoft stessa fornisce in questa guida.

Definiamo la classe AppSettings

La prima cosa da fare è realizzare la classe AppSettings, ovvero la classe “esterna” al form che utilizzeremo per leggere e scrivere le nostre impostazioni. In questa classe, a differenza di quanto fatto nell’esempio precedente, andremo a creare un elemento nel dictionary settings per ognuna delle impostazioni disponibili, anche se sono a scelta mutuamente esclusiva. Questo significa che se nell’esempio precedente avevamo una sola chiave Sex in cui memorizzavamo la scelta Male or Female, adesso avremo una chiave specifica per il radio button Male e una per il radio button Female.

Per gestire tutto ciò, per ogni settings, andremo a creare, a livello di classe, due costanti:

  • Una che contiene il nome della chiave.
  • Una che contiene il valore di default.

Ecco un esempio relativo alle impostazioni Name e Sex:

private const string NameSettingKeyName = "Name";
private const string MaleSettingKeyName = "Male";
private const string FemaleSettingKeyName = "Female";

private const string NameSettingDefault = "";
private const bool MaleSettingsDefault = false;
private const bool FemaleSettingDefault = false;

Avremmo potuto definire queste informazioni direttamente nelle proprietà che andremo a creare per gestire le impostazioni: in questo modo però, se dobbiamo cambiare il nome di una chiave, possiamo farlo in maniera più semplice e veloce.

Avremo bisogno inoltre di 3 metodi per poter lavorare con le nostre impostazioni:

  • Uno per memorizzare un’impostazione nel dictionary.
  • Uno per recuperare un’impostazione dal dictionary.
  • Uno per salvare le modifiche al dictionary.

Vediamoli uno per uno.

Il salvataggio

public bool AddOrUpdateValue(string key, object value)
{
    bool valueChanged = false;

    try
    {
        // if new value is different, set the new value.
        if (settings[key] != value)
        {
            settings[key] = value;
            valueChanged = true;
        }
    }
    catch (KeyNotFoundException)
    {
        settings.Add(key, value);
        valueChanged = true;
    }
    catch (ArgumentException)
    {
        settings.Add(key, value);
        valueChanged = true;
    }
    catch (Exception e)
    {
        Debug.WriteLine("Exception while using IsolatedStorageSettings: " + e.ToString());
    }

    return valueChanged;
}

Il metodo accetta come parametri di input una chiave e il relativo valore: la prima operazione che andiamo a fare è, nel caso in cui esista già quella chiave e il valore sia diverso da quello già memorizzato, sovrascrivere il valore corrente.

Tale operazione potrebbe scatenare un’eccezione, dato che la chiave potrebbe non esistere se è la prima volta che la memorizziamo: in tal caso, intercettando le tue tipologie di eccezioni che si possono scatenare in un caso come questo, aggiungiamo il nuovo elemento al dictionary tramite il metodo Add.  Se invece si verifica un’eccezione non prevista, scriviamo un messaggio nella Output Window di Visual Studio e ritorniamo false come risultato dell’operazione.

Il caricamento

public valueType GetValueOrDefault<valueType>(string key, valueType defaultValue)
{
    valueType value;

    try
    {
        value = (valueType)settings[key];
    }
    catch (KeyNotFoundException)
    {
        value = defaultValue;
    }
    catch (ArgumentException)
    {
        value = defaultValue;
    }
    catch (InvalidCastException)
    {
        value = defaultValue;
    }

    return value;
}

Questo metodo, sfruttando il tipo di ritorno valueType, ci permette di gestire settings che manipolano dati di tipo diverso. Il form che abbiamo realizzato ne è un chiaro esempio: la proprietà Name viene gestita con un Textbox (quindi di tipo string), mentre la proprietà Sex con due RadioButton (quindi di tipo bool).

Per il resto, il codice è molto semplice: recuperiamo dal dictionary settings l’elemento con la chiave specificata (key). Se dovesse scatenarsi un’eccezione, vuol dire che l’elemento non esiste, perciò ne recuperiamo il valore di default dalla costante.

Memorizziamo le impostazioni

Per comodità, definiamo anche un metodo che non fa altro che richiamare la funzione Save dell’oggetto settings:

private void Save()
{
    settings.Save();
}

Il vantaggio di utilizzare questa architettura è che, nel caso in cui dovessimo cambiare il nome del nostro oggetto di tipo IsolatedStorageSettings, ci basterebbe intervenire su questo metodo: se avessimo chiamato la funzione settings.Save() direttamente nelle singole proprietà che andremo a creare, avremmo dovuto modificarle tutte.

Definiamo i singoli settings

Ora siamo pronti per definire ogni singola impostazione sotto forma di proprietà: abbiamo infatti a disposizione i metodi che ci servono per scrivere, leggere e salvare le impostazioni. Vediamo il codice di esempio dell’impostazione Name.

public string NameSetting
{
    get { return GetValueOrDefault<string>(NameSettingKeyName, NameSettingDefault); }
    set
    {
        AddOrUpdateValue(NameSettingKeyName, value);
        Save();
    }
}

Il metodo get (invocato quando leggiamo la proprietà) chiama il metodo da noi definito GetValueOrDefault<>, specificando il tipo di ritorno come value type (in questo caso string) e come parametri il nome della chiave e il valore di default in caso non esista (entrambi i quali vengono letti dalle costanti che abbiamo creato all’inizio).

Il metodo set (invocato quando scriviamo la proprietà) chiama il metodo da noi definito AddOrUpdateValue, passandogli il nome della chiave (sempre tramite la costante da noi creata) e l’attributo value, che corrisponde al valore che vogliamo scrivere (se scriviamo ad esempio NameSetting = “Matteo”, “Matteo” rappresenta il value). Dopodichè, richiamiamo il metodo Save per salvare permanentemente le impostazioni nell’Isolated Storage.

Ad ogni singola impostazione corrisponde una proprietà, le quali sono definite tutte in questo modo: gli unici parametri che cambieranno saranno ovviamente il tipo di ritorno e i nomi delle costanti che contengono la chiave e il valore di default.

E ora… binding!

Ora abbiamo tutto quello che ci serve per collegare direttamente i nostri controlli XAML che gestiscono le varie impostazioni con le proprietà che abbiamo creato sfruttando il binding di Silverlight. Per prima cosa, dobbiamo definire la classe che abbiamo creato come risorsa della pagina. Dobbiamo perciò dire all’applicazione dove si troverà la nostra risorsa: per fare questo, dobbiamo aggiungere il namespace come attributo xmlns del nodo PhoneApplicationPage, che è  quello principale che contiene tutto il markup della nostra pagina, e specificare il tag con cui andremo a identificare questo controllo.

Ecco un esempio dell’attributo che dobbiamo aggiungere:

xmlns:local="clr-namespace:SettingsBinding.Classes

Nel nostro caso, SettingsBinding.Classes è il namespace che contiene la nostra classe AppSettings. Se apriamo il progetto di esempio allegato a questo post, capiremo facilmente il perchè di questo namespace: il nome dell'applicazione è SettingsBinding e la classe AppSettings è contenuta all’interno della cartella Classes. In linea di massima, per capire cosa inserire qui dentro, ci basta aprire il file cs della nostra classe e copiare il namespace.

Ora che abbiamo aggiunto questo attributo, possiamo sfruttare le risorse contenute nel namespace tramite il tag <local:xxx></local:xxx>, dove xxx è il nome della classe che vogliamo utilizzare. Ecco perciò che per usare la classe AppSettings dobbiamo inserire il seguente XAML:

<phone:PhoneApplicationPage.Resources>
    <local:AppSettings x:Key="appSettings"></local:AppSettings>
</phone:PhoneApplicationPage.Resources>

A questo punto possiamo utilizzare le proprietà della classe AppSettings (che corrispondono alle varie impostazioni) direttamente nello XAML. Ora vedremo qualche esempio: il fulcro però sarà sempre lo stesso, ovvero andremo a fare il binding con l’attributo del controllo XAML il cui valore cambia nel momento in cui interagiamo con esso. Se si tratta di una Textbox, ad esempio, faremo il binding con la proprietà Text; se si tratta di un RadioButton o di un CheckBox, faremo il binding con la proprietà IsChecked e così via.

<TextBox x:Name="txtName" Text="{Binding Source={StaticResource appSettings}, Path=NameSetting, Mode=TwoWay}"></TextBox>
<StackPanel Orientation="Horizontal">
    <RadioButton x:Name="rMale" GroupName="Sex" Content="Male" IsChecked="{Binding Source={StaticResource appSettings}, Path=MaleSetting, Mode=TwoWay}"></RadioButton>
    <RadioButton x:Name="rFemale" GroupName="Sex" Content="Female" IsChecked="{Binding Source={StaticResource appSettings}, Path=FemaleSetting, Mode=TwoWay}"></RadioButton>
</StackPanel>

Come potete vedere, la sintassi è sempre la stessa; in tutti e tre gli esempi andiamo a impostare tre attributi del binding:

  • Source: la sorgente, ovvero la risorsa appSettings che abbiamo definito sopra, che è di tipo StaticResource.
  • Path: il percorso, ovvero nel nostro caso la proprietà della classe appSettings collegata al nostro controllo.
  • Mode: la modalità di binding, nel nostro caso sarà sempre TwoWay (ovvero le due proprietà si influenzano a vicenda, una modifica all’una si ripercuote sull’altra e viceversa).

Il gioco è fatto

Il gioco è fatto: ora proviamo a lanciare la nostra applicazione nell’emulatore, modifichiamo le varie impostazioni e usciamo con il tasto Back (non la sospendiamo perciò, ma la chiudiamo definitivamente). Ora lanciamo nuovamente il debug (o rilanciamo l’applicazione direttamente dall’emulatore, non è necessario riattacare il debugger di Visual Studio per i nostri scopi) e potremo notare che le impostazioni saranno esattamente come le abbiamo lasciate.

E’ stato un lavoro duro, ma ce l’abbiamo fatta. Il tutorial può apparire abbastanza complesso, ma una volta capito il meccanismo è piuttosto semplice. Tra l’altro, come abbiamo visto, i metodi che abbiamo scritto nella classe AppSettings non contengono riferimenti a controlli specifici: i metodi AddOrUpdateValue e GetValueOrDefault sono generici e perciò possiamo riutilizzare la classe in qualsiasi applicazione che andremo a realizzare. Dobbiamo semplicemente ricordarci di definire due costanti (una con la chiave e una con il valore di default) per ognuno dei nostri settings e definirne la proprietà da mettere in binding con il controllo.

Nel prossimo post concluderemo il discorso sulla classe IsolatedStorageSettings mostrando come possiamo utilizzare questa classe anche per implementare lo scenario visto nel primo post, ovvero la possibilità di aggungere un pulsante di conferma prima di salvare le nostre impostazioni.


Windows Phone , Microsoft , Isolated Storage

4 comments

Related Post

  1. #1 da Giancarlo Monday May 2012 alle 08:55

    Ciao
    il codice

    -local:AppSettings x:Key="appSettings"

    mi da errore: IMPOSSIBILE CREARE UN'ISTANZA PER APP SETTINGS

    cosa devo fare?
    GIANCARLO

  2. #2 da Matteo Pagani Wednesday May 2012 alle 11:50

    Ciao, hai creato una classe di nome AppSettings all'interno del tuo progetto?

    Il namespace "local" deve puntare al namespace di quella classe. Fammi sapere!

  3. #3 da Giancarlo Wednesday May 2012 alle 02:04

    Si certo, l'ho creata la classe APPSETTINGS. ho seguito passo passo il tuo post.
    GIANCARLO


(will not be published)
(es: http://www.mysite.com)