Un nuovo wrapper di SQLite per Windows Phone 8 e Windows 8

Print Content | More

Uno degli argomenti che ha raccolto maggiore interesse sul mio blog è SQLite: sviluppare un’applicazione che non abbia la necessità di memorizzare, in qualche modo, dati locali è praticamente impossibile, perciò gli sviluppatori sono sempre alla ricerca del miglior modo per soddisfare questo scenario. SQLite è una delle soluzioni più interessanti sul mercato, dato che è open source ed è disponibile per praticamente tutte le piattaforme sul mercato, sia mobile (iOS, Android, Windows Phone, ecc.) che non.

In precedenza vi ho già parlato di sqlite-net, una libreria disponibile per Windows Phone 8 e Windows 8 che consente di effettuare operazioni su un database SQLite utilizzando delle API di alto livello basate su LINQ. Si tratta di una libreria molto semplice da utilizzare, ma ha diverse limitazioni: la princpale è che non supporta le relazioni, se non con qualche workaround.

Peter Torr (uno dei membri del team di sviluppo di Windows Phone), con il supporto di Andy Wigley (un ex “collega” MVP nella categoria Windows Phone Development che, da qualche tempo, è stato assunto in Microsoft), ha rilasciato su Codeplex un nuovo wrapper per SQLite, che utilizza un approccio totalmente differente da quello proposto da sqlite-net, ovvero offrendo pieno controllo sulle operazioni. Infatti, questo wrapper non supporta LINQ, ma solo l’esecuzione manuale di query SQL: il vantaggio principale è che sarete in grado di gestire praticamente ogni scenario, incluse le relazioni. Lo svantaggio principale è che, non supportando LINQ, dovrete affidarvi al caro vecchi SQL anche per effettuare le operazioni piiù semplici, come creare una tabella o inserire dei dati.

Vediamo ora, passo per passo, come utilizzarlo e quali sono le principale differenze con sqlite-net. In questo primo post vedremo le basi, nel prossimo parleremo in dettagio di come gestire le relazioni.

Configurare la libreria

Per il momento, dato che è scritta in codice nativo, la libreria non è disponibile su NuGet: dovrete scaricare il codice sorgente dalla pagina del progetto su Codeplex. La soluzione scaricata contiene due progetti, uno chiamato SQLiteWinRT e uno chiamato SQLiteWinRTPhone. Il primo è dedicato alle Windows Store apps di Windows 8, il secondo alle applicazioni Windows Phone 8 (Windows Phone 7 non è supportato, dato che manca il supporto al codice nativo): dovrete aggiungere alla vostra soluzione il progetto che più si adatta al vostro scopo (facendo clic con il tasto destro sulla soluzione e selezionando il file con estensione .vcxproj).

In questo tutorial andrò a creare un’applicazione per Windows Phone 8, perciò utilizzerò il secondo progetto. Inoltre, così come avevamo fatto con sqlite-net, è necessario installare l’engine ufficiale di SQLite, che è disponibile sotto forma di estensione di Visual Studio: questo è il link per la versione per Windows 8, mentre questo è quello per le applicazioni Windows Phone 8.

Ora siamo pronti per utilizzarlo! Sfrutteremo lo stesso esempio che abbiamo utilizzato nel post su sqlite-net: una tabella per memorizzare dati inerenti a persone.

Creiamo il database

Vediamo come fare:

private async void OnCreateDatabaseClicked(object sender, RoutedEventArgs e) 
{ 
    Database database = new Database(ApplicationData.Current.LocalFolder, "people.db"); 
  
    await database.OpenAsync(); 
  
    string query = "CREATE TABLE PEOPLE " + 
                   "(Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + 
                   "Name varchar(100), " + 
                   "Surname varchar(100))"; 
  
    await database.ExecuteStatementAsync(query); 
}

Prima creiamo un nuovo oggetto di tipo Database, che utilizzeremo per effettuare le operazioni sui dati. Ci sono diversi modo per istanziarlo: in questo esempio lo creeremo specificando la cartella nell’isolated storage dove salvarlo e il nome del file. La cartella viene passata al costruttore come oggetto di tipo StorageFolder (ovvero la classe base del Windows Runtime che identifica un cartella nell’Isolated Storage). In questo esempio, utilizziamo il percorso root dello storage: se il database non esiste, sarà creato; in caso contrario, sarà semplicemente aperto.

Dopodichè possiamo aprire la connessione chiamando il metodo OpenAsync(), il quale accetta, opzionalmente, un parametro per specificare la modalità di apertura (ad esempio, se vogliamo un accesso di sola lettura). Se non lo specifichiamo (come nell’esempio), sarà aperto con permessi di lettura / scrittura.

Dopodichè, si inizia a scrivere il caro vecchio codice SQL Smile Come già anticipato, questa libreria non supporta operazioni con LINQ, perciò dovrete scrivere le query SQL necessarie per eseguire le operazioni. Do per scontato che conosciate già le basi di SQL necessarie, perciò non descriverò in dettaglio le varie query; in questo esempio creiamo una tabella di nome People con 3 colonne: Id (ovvero la chiave primaria, è un valore auto incrementante), Name e Surname (che contengono delle semplici stringhe).

Per eseguire le query dobbiamo utilizzare il metodo asincrono ExecuteStatementAsync() sull’oggetto di tipo Database, passando come parametro la stringa che contiene la query. ExecuteStatementAsync() è il metodo che deve essere utilizzato quando la query non restituisce alcun valore e quando non abbiamo la necessità di passare alcun parametro (vedremo a breve come utilizzarli).

Come potete vedere, c’è una grande differenza rispetto all’approccio di sqlite-net: non c’è un mapping tra le tabelle e le classi del nostro progetto, tutto viene realizzato utilizzando query SQL.

Effettuare operazioni sul database

L’approccio per eseguire operazioni (inserimento, aggiornamento, selezione, ecc.) è lo stesso che abbiamo appena visto: apriamo la connessione al database, definiamo la query e la eseguiamo. L’unica differenza è che, quando eseguite una insert, ad esempio, avete la necessità di impostare uno o più parametri, dato che alcuni elementi della query non sono fissi ma dinamici. Ad esempio, se vogliamo aggiungere una nuova riga alla tabella People che abbiamo creato in precedenza, abbiamo la necessità di passare due valori dinamici: Name e Surname.

Ecco il codice per eseguire questa operazione:

private async void OnAddDataClicked(object sender, RoutedEventArgs e) 
{ 
    Database database = new Database(ApplicationData.Current.LocalFolder, "people.db"); 
  
    await database.OpenAsync(); 
  
    string query = "INSERT INTO PEOPLE (Name, Surname) VALUES (@name, @surname)"; 
    Statement statement = await database.PrepareStatementAsync(query); 
    statement.BindTextParameterWithName("@name", "Matteo"); 
    statement.BindTextParameterWithName("@surname", "Pagani"); 
  
    await statement.StepAsync(); 
  
    statement = await database.PrepareStatementAsync(query); 
    statement.BindTextParameterWithName("@name", "John"); 
    statement.BindTextParameterWithName("@surname", "Doe"); 
  
    await statement.StepAsync(); 
}

Diamo il benvenuto alla classe Statement, che può essere utilizzata per eseguire operazioni aggiuntive sulla query, come l’aggiunta di parametri e l’iterazione dei risultati ottenuti da una query. Pere preparare l’oggetto di tipo Statement dovete chiamare il metodo PrepareStatementAsync(), passando come parametro la query da eseguire. Come gestire i parametri? Il modo più semplice è utilizzare quelli che vengono definiti named parameter, che possono essere aggiunti ad una query semplicemente aggiungendo la chiocciola (@) come prefisso del nome del parametro. Nel codice di esempio, la query di inserimento accetta due parametri: @name e @surname.

Come definire il valore di questi parametri? Chiamando il metodo BindTextParameterWithName() dell’oggetto Statement con due parametri: il primo è il nome, il secondo il valore da assegnare. In questo esempio, aggiungiamo due utenti alla tabella: uno di nome Matteo Pagani e uno di nome John Doe. Per eseguire la query dobbiamo chiamare il metodo StepAsync() sull’oggetto di tipo Statement. Abbiamo a disposizione anche altre varianti del metodo BindParameterWithName(), a seconda della tipologia di parametro da passare: ad esempio, se fosse stato un numero intero avremmo utilizzato il metodo BindIntParameterWithName().

Il metodo StepAsync(), in realtà, ha anche un altro scopo: può essere utilizzato per scorrere tutti i risultati di una query, in caso stiamo eseguendo un’operazione che può restituire uno o più risultati. Vediamo, ad esempio, come effettuare una selezione per recuperare i dati che abbiamo appena inserito:

private async void OnGetDataClicked(object sender, RoutedEventArgs e) 
{ 
    Database database = new Database(ApplicationData.Current.LocalFolder, "people.db"); 
  
    await database.OpenAsync(); 
  
    string query = "SELECT * FROM PEOPLE"; 
    Statement statement = await database.PrepareStatementAsync(query); 
  
    while (await statement.StepAsync()) 
    { 
        MessageBox.Show(statement.GetTextAt(0) + " " + statement.GetTextAt(1)); 
    } 
}

La prima parte del codice è la stessa che abbiamo visto in precedenza: definiamo la query (in questo caso, recuperiamo tutte le righe della tabella People), definiamo l’oggetto di tipo Statement ed eseguiamo il metodo StepAsync(). La differenza è che, in questo caso, StepAsync() viene eseguito all’interno di un ciclo while: in questo caso il metodo ciclerà attraverso tutti le righe restituite dalla query in modo che, ad ogni esecuzione del ciclo, sarà restituita la riga successiva. In questo esempio, ci aspettiamo di veder comparire la MessageBox due volte: una per l’utente Matteo Pagani e una per l’utente John Doe. In questo esempio potete vedere, inoltre, come recuperare le informazioni dai risultati della query: l’oggetto Statement offre diversi metodi che iniziano con il prefisso Get, che accettano come parametro l’indice della colonna per la quale vogliamo recuperare il valore. Esistono diversi metodi per i tipi di dato più comuni: nell’esempio, dato che Name e Surname sono stringhe, utilizziamo il metodo GetTextAt(), passando 0 come indice per recuperare il nome e 1, invece, per recuperare il cognome.

Ovviamente, possiamo combinare quanto abbiamo appreso dagli ultimi due esempi e, ad esempio, effettuare una query di selezione con alcuni parametri:

private async void OnGetSomeDataClicked(object sender, RoutedEventArgs e) 
{ 
    Database database = new Database(ApplicationData.Current.LocalFolder, "people.db"); 
  
    await database.OpenAsync(); 
  
    string query = "SELECT * FROM PEOPLE WHERE Name=@name"; 
    Statement statement = await database.PrepareStatementAsync(query); 
    statement.BindTextParameterWithName("@name", "Matteo"); 
  
    while (await statement.StepAsync()) 
    { 
        MessageBox.Show(statement.GetTextAt(0) + " " + statement.GetTextAt(1)); 
    } 
}

In questo esempio recuperiamo dalla tabella People solamente le righe dove la colonna Nome contiene il valore Matteo.

Abbiamo anche un’altra opzione per accedere alle colonne dei risultati di una query, ma è disabilitata di default dato che ha un’impatto maggiore sulle performance; in più, i valori vengono sempre restituiti come stringhe, anzichè con il loro tipo specifico. Per abilitarla, dovete chiamare il metodo EnableColumnsProperty() dell’oggetto Statement: una volta fatto, potrete accedere ai valori utilizzando la proprietà Columns dell’oggetto Statement; si tratta di una collezione che permette di accedere ai valori delle singole colonne con il relativo nome anzichè l’indice. Ecco un esempio:

private async void OnGetSomeDataWithColumnsPropertyClicked(object sender, RoutedEventArgs e) 
{ 
    Database database = new Database(ApplicationData.Current.LocalFolder, "people.db"); 
  
    await database.OpenAsync(); 
  
    string query = "SELECT * FROM PEOPLE"; 
    Statement statement = await database.PrepareStatementAsync(query); 
  
    statement.EnableColumnsProperty(); 
  
    while (await statement.StepAsync()) 
    { 
        MessageBox.Show(statement.Columns["Name"] + " " + statement.Columns["Surname"]); 
    } 
}

Nella prossima puntata

In questo post abbiamo imparato i concetti base per utilizzare questo nuovo wrapper per effettuare operazioni con un database SQLite e quali sono le differenze con un’altra popolare libreria, sqlite-net. Nel prossimo post vedremo come gestire le relazioni, uno degli aspetti più complessi da gestire attualmente. Nel frattempo, potete scaricare il progetto di esempio e fare qualche esperimento.


Windows Phone , Windows 8 , SQLite

2 comments

Backup e restore su Skydrive in Windows Phone 7

Print Content | More

Ho recentemente pubblicato un grosso update per Speaker Timer, un’applicazione da me sviluppata diverso tempo fa dedicata agli speaker o, più in generale, a chiunque si trovi nella situazione di dover parlare in pubblico e voglia tenere traccia del tempo a disposizione.

Una delle caratteristiche che ho implementato è il supporto a Skydrive per effettuare il backup dei dati, da ripristinare successivamente in caso di acquisto di un nuovo telefono o di ripristino di quello che si possiede. In rete ci sono diversi tutorial che spiegano come implementare questo scenario, ma chi più chi meno contengono tutti qualche lacuna: alcuni tutorial spiegano come gestire l’operazione di upload, ma non come scaricarlo successivamente per il ripristino; la documentazione ufficiale spiega come gestire il download, ma dando alcune informazioni per scontate (ad esempio, che si sappia che cos’è il “file id” e come recuperarlo).

Per questo motivo ho deciso di scrivere questo post, nella speranza che lo troviate utile nel caso vogliate implementare questo scenario nelle vostre applicazioni. Tenete presente che il tutorial si riferisce a Windows Phone 7: in Windows Phone 8 l’approccio è molto simile, ma la Live SDK supporta metodi asincroni basati sulle keyword async e await al posto di sfruttare le callback.

Iniziamo!

La prima cosa da fare è aggiungere la liberia Live SDK al vostro progetto: si tratta dell’SDK fornita da Microsoft per interagire con i servizi che una volta erano riuniti sotto il marchio Live, come Skydrive, rubrica, calendari, ecc. Alcuni esempi di funzionalità offerte da questa SDK sono l’identificazione dell’utente, l’accesso ai suoi dati, alle sue foto e così via. Una delle caratteristiche supportate è l’accesso a Skydrive, così da permettervi di leggere e scrivere file nello spazio di storage sul cloud di Microsoft. Potete scaricare la Live SDK per Windows Phone dal sito ufficiale o, semplicemente, utilizzare NuGet.

Il secondo passaggio è quello di registrare la vostra applicazione sul Live Developer Portal: si tratta di un requisito necessario per ottenere il client id, che è un identificatore univoco utilizzato dai servizi di Live per autorizzare l’accesso da parte della vostra applicazione.

Una volta che avete effettuato il login con il vostro Microsoft Account sul portale, cliccate su My apps e selezionate l’opzione Create application.

image

Nel primo step della procedura dovete specificare un identificatore univoco (nel campo Application Name) e il linguaggio principale della vostra applicazione. Una volta che siete pronti per proseguire, cliccate sul pulsante I accept. Nel secondo e ultimo step l’unica opzione a vostra disposizione vi permetterà di specificare se si tratta o meno di un’applicazione mobile, selezionando l’opzione Yes alla voce Mobile client app. Ricordatevi di premere il pulsante Save per confermare.

In questa pagina potete vedere anche il Client ID, che useremo a breve per configurare la nostra applicazione.

Login con il Microsoft Account

La prima operazione da effettuare è aggiungere il supporto ai servizi di Live: l’utente dovrà effettuare il login con il suo Microsoft Account prima di effettuare qualsiasi attività con Skydrive. La procedura è molto semplice, dato che la Live SDK include un controllo integrato che s occupa di gestire tutto il processo di login. La prima cosa da fare è aggiungere, nella dichiarazione della classe PhoneApplicationPage nello XAML, il namespace che contiene il controllo, ovvero:

xmlns:my=”clr-namespace:Microsoft.Live.Controls;assembly=Microsoft.Live.Controls”

Ora siete in grado di aggiungere il controllo alla pagina, come nell’esempio seguente:

<my:SignInButton ClientId="your_client_id" 
    Scopes="wl.signin wl.skydrive_update" 
    Branding="Skydrive" 
    TextType="SignIn" 
    SessionChanged="SignInButton_OnSessionChanged" 
    VerticalAlignment="Top"
/>

Il controllo è semplicemente un pulsante, che si farà carico di eseguire tutte le operazioni necessarie per effettuare il login: quando l’utente lo preme viene aperta una web view, nella quale saranno richieste le credenziali del Microsoft Account dell’utente. Dopodichè, in base alle funzioni che avete necessità di utilizzare (e che vedremo a breve come specificare), sarà mostrata una schermata di conferma all’utente.

Il controllo si chiama SignInButton e offre diverse proprietà per personalizarlo. La più importante è ClientId, che contiene l’identificatore univoco dell’applicazione che avete ricevuto una volta completata la procedura di registrazione sul portale Live Developer. Un’altra proprietà importante è Scopes, che può essere utilizzata per specificare quali caratteristiche della piattaforma Live vogliamo utilizzare (potete vedere l’elenco completo in questa pagina): in questo caso ci serve solamente specificare i valori wl.signin (che è la funzionalità base, necessaria per supportare l’autenticazione) e wl.skydrive_update (necessaria per avere accesso in lettura e scrittura su Skydrive).

Se volete personalizzare l’aspetto visuale del pulsante potete utilizzare le proprietà Branding e TextType: la prima serve per scegliere quale logo mostrare (nel nostro caso, utilizziamo l’opzione Skydrive), la seconda per specificare il testo che sarà utilizzato come etichetta del pulsante nei vari stati di login e logout (potete anche personalizzarmi a piacimento tramite le proprietà SignInText e SignOutText).

Ora è tempo di scrivere un po’ di codice: il controllo offre un evento chiamato SessionChanged, che viene invocato quando l’utente interagisce con il controllo e sta effettuando il login. Ecco come gestire l’evento nel codice:

private void SignInButton_OnSessionChanged(object sender, Microsoft.Live.Controls.LiveConnectSessionChangedEventArgs e) 
{ 
    if (e.Status == LiveConnectSessionStatus.Connected) 
    { 
        client = new LiveConnectClient(e.Session); 
        MessageBox.Show("Connected!"); 
    } 
}

Il parametro ricevuto nel metodo contiene lo stato dell’operazione: con la proprietà Status (il cui tipo è LiveConnectSessionStatus) siamo in grado di scoprire se l’operazione di login sia andata a buon fine (in tal caso, il valore restituito è LiveConnectionStatus.Connected). A questo punto siamo in grado di creare una nuova istanza della classe LiveConnectClient, che è la classe base necessaria per eseguire tutte le operazioni con i servizi Live. Come parametro del costruttore occorre passare l’identificatore della sessione corrente, che è memorizzato all’interno della proprietà Session del parametro restituito dal metodo.

Effettuare il backup del file

Ora che l’utente si è collegato e che avete un oggetto di tipo LiveConnectClient valido, siete pronti per effettuare il backup dei vostri file su Skydrive. In questo esempio, vediamo come salvare un singolo file nella root dell’account Skydrive:

private void OnBackupClicked(object sender, RoutedEventArgs e) 
{ 
    client.UploadCompleted += client_UploadCompleted; 
    using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication()) 
    { 
        IsolatedStorageFileStream stream = storage.OpenFile("Sessions.xml", FileMode.Open); 
        client.UploadAsync("me/skydrive", "SpeakerTimer.bak", stream, OverwriteOption.Overwrite); 
    } 
} 
  
void client_UploadCompleted(object sender, LiveOperationCompletedEventArgs e) 
{ 
    if (e.Error == null) 
    { 
        MessageBox.Show("Upload successfull!"); 
    } 
}

La prima cosa da fare è sottoscrivere l’evento UploadCompleted esposto dalla classe LiveConnectClient, che viene invocato nel momento in cui l’operazione di upload è terminata: si tratta di un passaggio necessario, dato che l’operazione è asincrona. Dopodichè, utilizzando le API per accedere allo storage locale, recuperiamo il file da caricare su Skydrive sotto forma di stream: nel mio esempio, il file si chiama Sessions.xml ed è memorizzato nella root dell’isolated storage. Infine, avviamo l’operazione di upload vera e propria chiamando il metodo UploadAsync() del client, che richiede come parametri:

  • Il percorso dove salvare il file nello storage di Skydrive. In questo caso stiamo usando la sintassi me/skydrive, che rappresenta la root dello storage;
  • Il nome del file da salvare su Skydrive;
  • Lo stream del file da salvare: è quello che abbiamo recuperato tramite il metodo OpenFile() della classe IsolatedStorageFile;
  • Il comportamento da tenere nel caso in cui esista già un file su Skydrive con lo stesso nome: nell’esempio, ci limitiamo a sovrascriverlo tramite il valore OverwriteOption.Overwrite.

Nel momento in cui l’evento UploadCompleted viene scatenato, verifichiamo semplicemente se l’operazione è andata a buon fine tramite la proprietà Error del parametro di ritorno: nel caso in cui sia null (ovvero, non ci sono stati errori), mostriamo semplicemente un messaggio sullo schermo. Se abbiamo fatto tutto correttamente, troveremo il file nel nostro account Skydrive.

Ripristinare il file

Effettuare il backup del file è stato piuttosto semplice; l’operazione di ripristino, invece, è un po’ più complessa, in quanto ogni file e cartella memorizzata su Skydrive è identificata tramite un id univoco e non tramite il suo nome, che abbiamo utilizzato in fase di backup.

Un modo per recuperarlo potrebbe essere quello di sfruttare le informazioni restituite dal metodo UploadCompleted, che contiene, nella proprietà Result, un dictionary con tutte le proprietà del file. Utilizzando il seguente codice saremmo perciò in grado di recuperarlo:

void client_UploadCompleted(object sender, LiveOperationCompletedEventArgs e) 
{ 
    if (e.Error == null) 
    { 
        string fileId = e.Result["id"].ToString(); 
        MessageBox.Show("Upload successfull!"); 
    } 
}

Il limite di questo approccio è che, prima di ottenere l’id del file, dobbiamo farne l’upload. Gli scenari più frequenti, invece, in cui si introducono funzionalità di backup e ripristino sono che l’utente ha acquistato un nuovo telefono o ha ripristinato quello già in suo possesso e vuole ripristinare i dati che aveva in precedenza: in questo caso, l’applicazione appena installata non ha mai effettuato un backup, perciò dobbiamo trovare un’altra strada per recuperare l’id del file.

La soluzione è quella di utilizzare il metodo GetAsync() del client, che permette di recuperare tutti i file e le cartelle presentii sull’account Skydrive e, per ognuno di essi, recuperarne le relative proprietà. Andremo perciò a recuperare i file e le cartelle dello storage Skydrive dell’utente e, tramite le proprietà esposte, andremo a cercare l’identificativo del file che si chiama SpeakerTimer.bak (che è il nome che abbiamo utilizzato durante la procedura di upload).

private void OnRestoreClicked(object sender, RoutedEventArgs e) 
{ 
    string id = string.Empty; 
    client.GetCompleted += (obj, args) => 
    { 
        List<object> items = args.Result["data"] as List<object>; 
        foreach (object item in items) 
        { 
            Dictionary<string, object> file = item as Dictionary<string, object>; 
            if (file["name"].ToString() == "SpeakerTimer.bak") 
            { 
                id = file["id"].ToString(); 
            } 
        } 
    }; 
  
    client.GetAsync("me/skydrive/files"); 
}

Il metodo GetAsync() accetta come parametro il percorso che vogliamo esplorare: utilizzando la parola chiave me/skydrive/files siamo in grado di recuperare tutti i file e le cartelle presenti nella root. Dato che il metodo è asincrono, dobbiamo sottoscrivere l’evento GetCompleted, che viene scatenato quando l’operazione è terminata. La proprietà Result dei parametri di ritorno contiene una collezione di tutti i file e le cartelle disponibili, all’interno della collezione Data (si tratta di un Dictionary). Dato che è una collezione, è necessario effettuare un cast del risultato alla tipologia List<object>. Ogni oggetto all’interno di questa lista è, a sua volta, un’altra collezione di tipo Dictionary<string, object>, la quale contiene (finalmente!) le proprietà che stiamo cercando.

A questo punto siamo in grado, tramite un ciclo foreach, di analizzare tutti i file e le cartelle e, per ognuno di essi, verificare il valore della proprietà name: se il valore è quello che ci aspettiamo (nel nostro caso, SpeakerTimer.bak), recuperiamo la proprietà id e lo memorizziamo (sarà qualosa del tipo file.8c8ce076ca27823f.8C8CE076CA27823F!129), ci servirà successivamente per il download vero e proprio).

Ora siamo pronti per effettuare l’operazione di download vera e propria:

client.DownloadCompleted += (o, a) => 
                                { 
                                    Stream stream = a.Result; 
                                    using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication()) 
                                    { 
                                        using ( 
                                            IsolatedStorageFileStream fileToSave = storage.OpenFile("Sessions.xml", FileMode.Create, 
                                                                                                    FileAccess.ReadWrite)) 
                                        { 
                                            stream.CopyTo(fileToSave); 
                                            stream.Flush(); 
                                            stream.Close(); 
                                        } 
                                    } 
                                }; 
  
client.DownloadAsync(string.Format("{0}/content", id));

Utilizziamo il metodo DownloadAsync() offerto dal client, passando come parametro l’identificativo del file che abbiamo appena recuperato. E’ molto importante aggiungere il suffisso /content all’id: molti tutorial omettono questa informazione, ma è indispensabile altrimenti quello che otterrete in ritorno sarà il JSON che rappresenta le proprietà del file anzichè il contenuto vero e proprio.

Dato che, come per gli altri casi, l’operazione è asincrona, ci sottoscriviamo all’evento DownloadCompleted, che viene invocato nel momento in cui il download è stato completato e abbiamo accesso al contenuto del file, che viene reso disponibile come oggetto di tipo Stream all’interno della proprietà Result dei parametri di ritorno. Utilizzando nuovamente le API dello storage salviamo lo stream appena scaricato nell’isolated storage, creando un nuovo file (utilizzando sempre il metodo OpenFile() ma passando il parametro FileMode.Create come valore) e copiando lo stream scaricato all’interno dello stream del file locale.

Ecco come appare il metodo di ripristino completo, dove le due operazioni (recupero dell’identificativo del file su Skydrive e download dello stesso) vengono eseguite in una volta sola:

private void OnRestoreClicked(object sender, RoutedEventArgs e) 
{ 
    string id = string.Empty; 
    client.GetCompleted += (obj, args) => 
    { 
        List<object> items = args.Result["data"] as List<object>; 
        foreach (object item in items) 
        { 
            Dictionary<string, object> file = item as Dictionary<string, object>; 
            if (file["name"].ToString() == "SpeakerTimer.bak") 
            { 
                id = file["id"].ToString(); 
            } 
        } 
  
        client.DownloadCompleted += (o, a) => 
                                        { 
                                            Stream stream = a.Result; 
                                            using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication()) 
                                            { 
                                                using ( 
                                                    IsolatedStorageFileStream fileToSave = storage.OpenFile("Sessions.xml", FileMode.Create, 
                                                                                                            FileAccess.ReadWrite)) 
                                                { 
                                                    stream.CopyTo(fileToSave); 
                                                    stream.Flush(); 
                                                    stream.Close(); 
                                                } 
                                            } 
                                        }; 
  
        client.DownloadAsync(string.Format("{0}/content", id)); 
    }; 
  
    client.GetAsync("me/skydrive/files"); 
}

In conclusione

In questo post abbiamo visto come implementare una semplice soluzione per effettare il backup e il ripristino dei dati della vostra applicazione: c’è spazio per alcuni miglioramenti (ad esempio, si potrebbe criptare il file prima di effettuarne l’upload), ma questo tutorial copre più o meno tutti gli aspetti base della procedura. Buon lavoro!


Windows Phone , SkyDrive

3 comments

Caliburn Micro e Windows Phone – Lazy Loading con il controllo Pivot

Print Content | More

Dopo che, nel post precedente, abbiamo parlato di come utilizzare il controllo Pivvot in un’applicazione sviluppata utilizzando Caliburn Micro, siamo pronti per affrontare il tema del lazy loading. Di cosa si tratta? E’ un approccio che, solitamente, viene utilizzato quando si lavora con i database e, semplificando, si traduce nel fatto che i dati vengono caricati solo nel momento in cui sono realmente necessari. Più o meno tutte le tecnologie ORM (come Entity Framework o NHibernate) supportano questo approccio; pensate ad un database, dove sono coinvolte numerose tabelle e relazioni. Solitamente, quando effettuate una query per recuperare dei dati che sono disponibili solo tramite una join tra due o più tabelle, l’operazione viene eseguita immediatamente e tutti i dati richiesti vengono estratti dal database.

Con il lazy loading vengono caricati in memoria, invece, solo i dati che servono in quel preciso momento. Ipotizzate di avere un’applicazione per la gestione degli ordini e dovete eseguire una query per recuperare tutti gli ordini, con le informazioni sui relativi clienti che lo hanno effettuato (e che sono memorizzate in un’altra tabella). Grazie al lazy loading siete in grado di estrarre tutti gli ordini e di andare a recuperare i dati del cliente che lo ha effettuato solo nel momento in cui la persona che sta usando il gestionale li richiede esplicitamente. In caso contrario, la query non viene eseguita, facendo risparmiare tempo e memoria.

Come applicare questo concetto al controllo Pivot? Nel post precedente abbiamo visto che, con Caliburn Micro, siamo stati in grado di suddividere gli elementi di un Pivot in View e ViewModel differenti ma, alla fine, facevano tutti parte della stessa pagina: quella definita conductor, che contiene il controllo Pivot vero e proprio. Questo significa che, se utilizziamo l’approccio abituale di caricare i dati nel costruttore del ViewModel, nel momento in cui la pagina con il controllo Pivot viene caricata, vengono automaticamente istanziati tutti i ViewModel dei singoli item e, di conseguenza, vengono caricati tutti i dati in memoria, anche quelli che non sono immediatamente visibili.

Pensate, ad esempio, ad un’applicazione per leggere le notizie, che mostra differenti categorie di news sfruttando un controllo Pivot: quando la pa g ina viene caricata, tutte le notizie vengono immediatamente caricate, anche quelle che non sono visibili all’inizio. Questo è uno scenario in cui il lazy loading può essere utile per migliorare le performance e la responsività dell’applicazione: possiamo caricare solamente i dati che fanno parte della pagina attualmente mostrata e caricare quelli delle altre pagine solo nel momento in cui l’utente, con uno swipe, si sposta all’interno del Pivot.

Vediamo come implemetarlo: innanzitutto dobbiamo partire dall’applicazione che abbiamo sviluppato nel post precedente, che contiene un controllo Pivot con due elementi.

IMPORTANTE! Anche se, da un punto di vista architetturale, Panorama e Pivot utilizzano lo stesso approccio, al momento siamo in grado di supportare il lazy loading solo con il controllo Pivot, a causa di un bug presente nel controllo Panorama di Windows Phone 8 (mentre funziona correttamente su Windows Phone 7). Cosa succede? Che se i vari PanoramaItem vengono aggiunti al controllo Panorama utilizzando il binding e la proprietà ItemSource, invece che essere inseriti manualmente nello XAML o nel code behind, la proprietà SelectedIndex (che è fondamentale per tenere traccia della view correntemente visibile) non funziona correttamente, restituendo solamente i valori 0 e 1, indipendentemente dalla pagina selezionata. Di conseguenza, la proprietà SelectedItem, necessaria per supportare il lazy loading, non funziona correttamente.

Per il nostro esempio andremo a sviluppare un semplice lettore di feed RSS: per questo motivo avremo bisogno di alcune classi che ci aiuteranno a raggiungere il nostro scopo.

La prima si chiama FeedItem e rappresenta una singola notizia recuperata dal feed RSS:

public class FeedItem
{
    public string Title { get; set; }
    public string Description { get; set; }
}

E’ una semplice classe utilizzata per memorizzare il titolo e la descrizione di una notizia. Dopodichè ci serve un parser che, tramite LINQ to XML, sia in grado di trasformare le informazioni contenute nell’RSS in oggetti di tipo FeedItem:

public static class RssParser
{
    public static IEnumerable<FeedItem> ParseXml(string content)
    {
        XDocument doc = XDocument.Parse(content);
        var result = doc.Descendants("rss").Descendants("channel").Elements("item").Select(x => new FeedItem
                                                                {
                                                                    Title = x.Element("title").Value,
                                                                    Description = x.Element("description").Value
                                                                });
 
        return result;
    }
}

Ora siamo pronti per aggiungere il codice necessario a caricare i dati: prima proviamo l’approccio standard, così da capire le differenze. Il nostro obiettivo è quello di mostrare na lista di notizie nelle due viste che compongono il Pivot, di conseguenza, in ogni View, andremo a definire una ListBox il cui ItemTemplate è in grado di mostrare il valore della proprietà Title della notizia.

<ListBox x:Name="FeedItems">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding Path=Title}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Abbiamo dato al controllo ListBox il nome FeedItems: in questo modo, grazie alle convenzioni di Caliburn, ci aspettiamo di avere nel ViewModel corrispondente una proprietà con lo stesso nome, che rappresenta la collezione che sarà visualizzata:

public class PivotItem1ViewModel: Screen
{
    public PivotItem1ViewModel()
    {
        DisplayName = "Pivot 1";
        LoadData();
    }
 
    public async void LoadData()
    {
        WebClient client = new WebClient();
        string result = await client.DownloadStringTaskAsync("http://feeds.feedburner.com/qmatteoq_eng");
        IEnumerable<FeedItem> feedItems = RssParser.ParseXml(result);
 
        FeedItems = feedItems.ToList();
    }
 
    private List<FeedItem> _feedItems;
 
    public List<FeedItem> FeedItems
    {
        get { return _feedItems; }
        set
        {
            _feedItems = value;
            NotifyOfPropertyChange(() => FeedItems);
        }
    } 
}

Nel momento in cui il ViewModel viene inizializzato chiamiamo il metodo LoadData() che, utilizzando la classe WebClient in combinazione con la libreria Async Targeting Pack (così da avere a disposizione il metodo asincrono DownloadStringTaskAsync()), scarica il feed RSS. Dopidichè elaboriamo il risultato utilizzando la classe RssParser che abbiamo definito in precedenza e, in conclusione, convertiamo il risultato in una collezione di tipo List<FeedItem> e lo usiamo per valorizzare la proprietà FeedItems del ViewModel. Grazie alla convenzione, i valori contenuti nella collezione saranno automaticamente mostrati nella ListBox definita nella View.

Ora ripetiamo gli stessi passaggi per la seconda pagina del Pivot, aggiungendo lo stesso codice alla pagina PivotItem2View e alla classe PivotItem2ViewModel: ricordatevi solamente di cambiare l’URL del feed RSS che viene scaricato, così da utilizzarne uno differente.

Ora lanciate l’applicazione: noterete che, quando la pagina con il Pivot viene mostrata, entrambe le viste vengono caricate e sia le notizie presenti nella prima vista che nella seconda vengono rese disponibili. Se il vostro Pivot fosse composto da molte più viste e i dati da caricare fossero molto più pesanti (ad esempio, delle immagini), l’operazione potrebbe impiegare del tempo e l’utente dovrebbe aspettare che tutto sia pronto prima di utilizzare l’applicazione. Per ovviare a questo inconveniente, è giunto il momento di implementare il lazy loading! L’operazione è molto semplice e, se avete letto i miei post precedenti su Caliburn Micro, dovreste avere già una vaga idea di come fare: tramite gli eventi di navigazione.

In questo post vi ho parlato della classe Screen e di come, facendo si che i ViewModel ereditino da essa, siamo in grado di agganciarci a diversi eventi di navigazione: nello specifico, quello che ci serve si chiama OnActivate(), che viene invocato quando la vista viene visualizzata. Nel contesto di un controllo Pivot, questo evento viene scatenato nel momento in cui l’utente, tramite lo swipe, si porta nella view corrente: in questo modo, siamo in grado di utilizzarlo per caricare i dati solamente nel momento in cui l’utente si sposta nella specifica View del Pivot. E’ sufficiente, perciò, all’interno del ViewModel spostare il codice del metodo LoadData() all’interno dell’evento OnActivate() per raggiungere il nostro risultato:

public class PivotItem1ViewModel: Screen
{
    public PivotItem1ViewModel()
    {
        DisplayName = "Pivot 1";
    }
 
    protected override async void OnActivate()
    {
        base.OnActivate();
        WebClient client = new WebClient();
        string result = await client.DownloadStringTaskAsync("http://feeds.feedburner.com/qmatteoq_eng");
        IEnumerable<FeedItem> feedItems = RssParser.ParseXml(result);
 
        FeedItems = feedItems.ToList();
    }
 
    private List<FeedItem> _feedItems;
 
    public List<FeedItem> FeedItems
    {
        get { return _feedItems; }
        set
        {
            _feedItems = value;
            NotifyOfPropertyChange(() => FeedItems);
        }
    } 
}

Se ora lanciate nuovamente l’applicazione noterete che, quando il controllo Pivot viene caricato, solo le notizie nella prima pagina vengono mostrate: la seconda pagina non contiene alcun dato. Nel momento in cui, tramite swipe, vi spostate nella seconda ecco che l’operazione di caricamento viene eseguita e la nuova lista di notizie viene mostrata. Obiettivo raggiunto Smile


Windows Phone , Caliburn , MVVM

0 comments

Caliburn Micro e Windows Phone – Il controllo Pivot

Print Content | More

Panorama e pivot sono due concetti chiave in Windows Phone: sono, probabilmente, due dei controlli più utilizzati per definire l’interfaccia delle applicazioni, dato che consentono di creare interfacce originali e molto differenti da quelle tipiche delle applicazioni per iOS e Android.

Il controllo Panorama viene utilizzato, solitamente, come punto di partenza di un’applicazione: ogni pagina, che può essere sfogliata con uno swipe verso destra o verso sinistra, mostra un’anteprima del contenuto della pagina successiva, così che l’utente possa immediatamente capire che ci sono altri elementi da visualizzare. Il controllo Panorama deve essere utilizzato come punto di partenza di un’applicazione, non come contenitore di dati: un panorama deve essere utilizzato per dare una breve anteprima di quello che l’applicazione ha da offrire e un modo semplice per accedere alle varie sezioni e opzioni. Per esempio, se state sviluppando un’applicazione per leggere le ultime notizie, non è corretto mostrare tutte le notizie in una pagina del panorama; è corretto, invece, mostrare solo le ultime 10 notizie e aggiungere un pulsante per aprire una pagina di dettaglio con l’elenco completo delle notizie.

Il controllo Pivot, invece, viene utilizzato per mostrare informazioni differenti legate allo stesso contesto, oppure la stessa informazione ma collegata a elementi diversi. Un esempio del primo scenario ci viene dato dall’hub People, nello specifico dalla pagina di dettaglio di un contatto: in questo caso, il controllo Pivot viene utilizzato per mostrare informazioni differenti (dettaglio, foto, post sui social network, ecc.) legate allo stesso contesto (il contatto). Un’applicazione meteo, invece, è un buon esempio del secondo scenario: il controllo Pivot può essere utilizzato per mostrare la stessa informazione (le previsioni del tempo) ma legata a elementi differenti (le città).

Gestire il controllo Panorama o Pivot in un’applicazione sviluppata con il pattern MVVM non è complesso: le pagine che compongono un Panorama o un Pivot sono “finte” (non sono effettivamente pagine XAML diverse, ma controlli differenti, di tipo PanoramaItem o PivotItem, inseriti all’interno di un “grande contenitore” presente nella pagina, ovvero il controllo Panorama o Pivot). Di conseguenza, è sufficiente collegare tra di loro una View e un ViewModel (con i relativi dati esposti) usando le convenzioni di Caliburn Micro che abbiamo imparato ad utilizzare nei post precedenti.

Esiste però un altro approccio, che consente di suppportare caratteristiche come il lazy loading e di organizzare meglio il proprio codice. Vediamo quale.

IMPORTANTE! Anche se, dal punto di vista dell’architettura, Panorama e Pivot sfruttano il medesimo approccio, saremo in grado di sfruttare il meccanismo che vedremo a breve solo con il controllo Pivot, a causa di un bug nel Panorama introdotto in Windows Phone 8. Qual è il problema? Che se gli elementi che compongono un Panorama vengono aggiunti sfruttando il binding con la proprietà ItemsSource (invece che essere direttamente dichiarati nello XAML o manualmente nel code behind), la proprietà SelectedIndex (che è importante per tenere traccia della vista corrente) non funziona correttamente e restituisce solamente i valori 0 e 1, indipendentemente da quale sia la vista effettivamente in uso.

La classe Conductor

Calibun Micro supporta il concetto di Conductor: una serie di pagine che sono collegate tra di loro e che sono gestite da un singolo punto di accesso. Con questo approccio, siamo in grado di avere una pagina principale, che funge da “conductor”, e pagine differenti con il relativo ViewModel, che costituscono le varie viste del Pivot.

Il primo vantaggio di questo approccio è che che ci consente di mantenere il codice più pulito e semplice da mantenere: invece di avere un unico ViewModel che deve gestire tutte le viste differenti, avete View e ViewModel separati.

Facciamo qualche esperimento utilizzando il controllo Pivot. Innanzitutto dobbiamo creare la pagina che fungerà da conduttore e che conterrà il Piot: aggiungete una nuova pagina nella cartella Views del progetto (ad esempio, di nome PivotView) e il relativo ViewModel nella cartella ViewModels (seguendo la convenzione standard, il nome da assegnare alla classe è PivotViewModel). Ricordatevi di registrare il ViewModel nel metodo Configure() del boostrapper!

Vediamo ora la definizione del ViewModel:

public class PivotViewModel: Conductor<IScreen>.Collection.OneActive 
{ 
    private readonly PivotItem1ViewModel item1; 
    private readonly PivotItem2ViewModel item2; 
  
    public PivotViewModel(PivotItem1ViewModel item1, PivotItem2ViewModel item2) 
    { 
        this.item1 = item1; 
        this.item2 = item2; 
    } 
  
    protected override void OnInitialize() 
    { 
        base.OnInitialize(); 
  
        Items.Add(item1); 
        Items.Add(item2); 
  
        ActivateItem(item1); 
    } 
}

Innanzitutto, il ViewModel deve ereditare dalla classe Conductor<T>.Collection.OneActive: in questo modo andiamo a definire che il ViewModel conterrà una collezione di viste (useremo come valore di T l’interfaccia IScreen, che viene implementata dalla classe Screen da cui ereditano tutti i ViewModel) e, utilizzando la proprietà OneActive, definiamo che potrà essere solamente mostrata una vista alla volta. E’ l’unica opzione che può essere utilizzata per gestire il controllo Pivot: ne sono disponibili altre, ma sono semplicemente un retaggio del fatto che Caliburn Micro supporta anche tecnologie come WPF e Silverlight, dove possono essere attive più viste allo stesso tempo.

Potete notare come, nel costruttore, abbiamo aggiunto due parametri, il cui tipo è PivotItem1ViewModel e PivotItem2ViewModel: sono i ViewModel collegati alle viste che andremo a mostrare all’interno del controllo Pivot.

Dopodichè implementiamo il metodo OnInitialize(), che viene chiamato quando la pagina viene inizializzata per la prima volta: dato che il ViewModel eredita dalla classe Conductor<T> abbiamo accesso alla proprietà Items e al metodo ActivateItem(). La prima è la collezione (il cui tipo T coincide con il tipo con cui è stata definita la classe Conductor<T>) che contiene le viste che compongono il controllo Pivot e la utilizziamo semplicemente per aggiungere tutti i ViewModel che abbiamo inizializzato nel costruttore. Dopodichè chiamiamo il metodo ActivateItem(), che permette di impostare il focus del Pivot su una specifica vista: in questo caso, impostiamo il primo ViewModel (ovvero la prima vista), ma potremmo aver specificato qualsiasi altra vista. Ad esempio, potremmo aver ricevuto l’informazione sulla pagina da mostrare da un parametro in query string, in seguito alla navigazione da un’altra pagina o da una tile secondaria.

Ora possiamo creare le vere e proprie pagine che andranno a comporre il nostro Pivot: aggiungete due nuove pagine nella cartella View del vostro progetto (chiamati PivotItem1View e PivotItem2View) e i relativi ViewModel nella cartella ViewModels (chiamati PivotItem1ViewModel e PivotItem2ViewModel).  Sono delle semplici pagine, niente di speciale da segnalare a riguardo. Possono anche essere degli UserControl, è indifferente: nel caso optiate per creare delle vere e proprie pagine, ricordatevi di rimuovere gli elementi nello XAML non necessari (dato che sono già contenute all’interno di una pagina, non è necessario mantenere l’header incluso nel template standard con il nome dell’applicazione e il titolo della pagina).

Ecco un esempio di definizione dello XAML:

<phone:PhoneApplicationPage
    x:Class="CaliburnMicro.Views.PivotItem1View"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d"
    shell:SystemTray.IsVisible="True"> 
  
    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent"> 
        <Grid.RowDefinitions> 
            <RowDefinition Height="Auto"/> 
            <RowDefinition Height="*"/> 
        </Grid.RowDefinitions> 
  
      <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> 
  
        </Grid> 
    </Grid> 
  
</phone:PhoneApplicationPage>

Ed ecco il relativo ViewModel:

public class PivotItem1ViewModel: Screen 
{ 
    public PivotItem1ViewModel() 
    { 
        DisplayName = "First pivot"; 
    } 
}

Niente di speciale neanche qui: è il classico ViewModel di un’applicazione che fa uso di Caliburn Micro e che eredita dalla classe Screen. Potete notare solamente che, nel costruttore, diamo un valore alla proprietà DisplayName, che fa parte della classe base Screen: è il titolo della vista che sarà utilizzata come header del Pivot.

Ora vediamo lo XAML della pagina PivotView, ovvero quella che funge da Conductor:

<Grid x:Name="LayoutRoot" Background="Transparent"> 
    <!--Pivot Control-->
    <phone:Pivot Title="MY APPLICATION" x:Name="Items" SelectedItem="{Binding ActiveItem, Mode=TwoWay}"> 
        <phone:Pivot.HeaderTemplate> 
            <DataTemplate> 
                <TextBlock Text="{Binding DisplayName}" /> 
            </DataTemplate> 
        </phone:Pivot.HeaderTemplate> 
    </phone:Pivot> 
</Grid>

Potete notare alcune delle “magie” di Caliburn in azione: la prima è che abbiamo dato al controllo Pivot il nome Items; in questo modo la proprietà ItemsSource viene in automatico collegata alla proprietà Items definita nel ViewModel, che contiene la collezione di viste da mostrare. La seconda “magia” è che la proprietà SelectedItem è collegata (in modalità two way) ad una proprità chiamata ActiveItem, la quale è definita nella classe base Conductor<T> da cui eredita il ViewModel e viene utilizzata per mantenere un riferimento alla vista correntemente selezionata. Grazie a questo binding ci assicuriamo che il metodo ActivateItem() che abbiamo definito nella classe PivotViewModel funzioni correttamente.

L’ultima cosa da sottolineare è che definiamo un template per la proprietà HeaderTemplate del controllo: semplicemente, utilizziamo una TextBlock, in binding con la proprietà DisplayName. In questo modo, il titolo della pagina viene preso automaticamente dalla proprietà DisplayName che abbiamo definito nel ViewModel.

Siamo pronti per testare l’applicazione? Non ancora! Prima, dobbiamo registrare, nel metodo Configure() del bootstrapper, tutti i ViewModel che abbiamo appena definito (quello della pagina che funge da conductor più tutti i singoli ViewModel).

Ora possiamo finalmente provare l’applicazione: se abbiamo fatto tutto correttamente, vedrete il controllo Pivot con due pagine, uno per ognuno delle View e dei ViewModel che avete definito. Ora potete giocare con il progetto: potete provare ad aggiungere alcuni dati in una pagina, oppure altre viste al contollo Pivot.

Nel prossimo post vedremo come sfruttare quanto abbiamo appreso per supportare il lazy loading. Nel frattempo, potete fare esperimenti con il progetto di esempio.


Windows Phone , Caliburn , MVVM

0 comments

Sviluppare applicazioni per Windows Phone 8 ora in vendita!

Print Content | More
WP_20130509_002

Aggiornamento: il libro è ora disponibile anche in versione digitale, sempre sul sito della casa editrice, al prezzo di 29,90 € (contrariamente ai 34,90 € precedentemente dichiarati). Prossimamente in vendita anche nei principali store online.

In tanti mi avete contattato per chiedere informazioni sulla disponibilità via mail o tramite social network e, finalmente, posso segnalarvi che il mio libro “Sviluppare applicazioni per Windows Phone 8” è ora in vendita!

Al momento, il libro è acquistabile direttamente dal sito della casa editrice al prezzo di 49,90 €. A partire da settimana prossima sarà disponibile anche nelle principali librerie della Lombardia mentre, entro due settimane, sarà distribuito in tutta Italia. Per fine Maggio, infine, sarà disponibile anche la versione e-book, al prezzo di 34,90 €.

Il volume, interamente a colori, è composto da 656 pagine e contiene un’introduzione a cura di Marco Argenti, Senior Vice President della Developer Division di Nokia.

Per l’occasione, ho creato anche una sezione dedicata su questo blog: oltre alla descrizione del libro, trovate il download degli esempi di codice e, a breve, una pagina in cui segnalerò eventuali aggiornamenti sugli argomenti trattati emersi dopo la pubblicazione.

Se siete tra le persone che lo hanno acquistato o che hanno intenzione di acquistarlo, vi ricordo che, per qualsiasi feedback, segnalazione di refuso o richiesta di chiarimento, potete contattarmi usando l’apposito form di contatto.


Windows Phone

0 comments

Caliburn Micro e Windows Phone – Gestire l’Application Bar

Print Content | More

L’Application Bar è la croce e la delizia di tutti gli sviluppatori Windows Phone che utilizzano il pattern MVVM: tale controllo, infatti, vive “in un mondo a sè” e non fa parte del contesto della pagina. Per questo motivo, non eredita dalla classe base FrameworkElement (dalla quale derivano tutti gli altri controlli) e, di conseguenza, non supporta il binding. Non è possibile utilizzare i command, per collegare delle azioni agli eventi scatenati dalla pressione dei pulsanti, e non è possibile collegare delle proprietà del ViewModel alle proprietà Text e IconUri per gestire dinamicamente testi e icone.

Dato che, spesso e volentieri, questi limiti portano lo sviluppatore a “rompere” il pattern MVVM e a gestire l’Application Bar direttamente dal code behind, alcuni sviluppatori hanno realizzato delle implementazioni alternative della Application Bar, in grado di supportare il binding. Sono diverse le soluzioni disponibili: due delle migliori che ho trovato sono il Cimbalino Toolkit di Pedro Lamas e Caliburn Bindable App Bar by Kamran Ayub. Il primo toolkit utilizza un approccio basato sui behavior, che vengono applicati direttamente alla Application Bar nativa. Non lo approfondiremo nel corso di questo articolo, non perchè non sia una soluzione valida ma perchè non si sposa molto bene con Caliburn Micro: è perfetto, invece, se utilizzate MVVM Light come toolkit per le vostre applicazioni.

Tratteremo, invece, la seconda implementazione, che nasce proprio per supportare Caliburn e le sue convenzioni e che rimpiazza comletamente l’Application Bar standard.

Vediamo come usarla!

Aggiungere l’Application Bar al proprio progetto

La Caliburn Bindable App Bar è disponibile come pacchetto NuGet: semplicemente fate click con il tasto destro sul vostro progetto, scegliete Manage NuGet packages e cercate il pacchetto chiamato Caliburn.Micro.BindableAppBar. Una volta installato, dovete aggiungere il namespace seguente nello XAML per poterlo utilizzare:

xmlns:bab=”clr-namespace:Caliburn.Micro.BindableAppBar;assembly=Caliburn.Micro.BindableAppBar”

A questo punto potete aggiungere il controllo vero e proprio, che si chiama BindableAppBar. Fate attenzione però! Contrariamente alla Application Bar standard (che viene posizionata al di fuori della Grid principale dato che non fa parte della pagina), questo controllo deve essere inserito, invece, all’interno della Grid principale (mi riferisco al controllo Grid che, nel template standard, è chiamato LayoutRoot). Ecco un esempio:

<Grid x:Name="LayoutRoot" Background="Transparent"> 
    <Grid.RowDefinitions> 
        <RowDefinition Height="Auto"/> 
        <RowDefinition Height="*"/> 
    </Grid.RowDefinitions> 
  
    <!--TitlePanel contains the name of the application and page title-->
    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> 
        <TextBlock Text="Caliburn Micro" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/> 
        <TextBlock Text="Sample" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/> 
    </StackPanel> 
  
    <!--ContentPanel - place additional content here-->
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> 
  
    </Grid> 
  
    <bab:BindableAppBar x:Name="AppBar"> 
        <bab:BindableAppBarButton x:Name="AddItem"
                                  Text="{Binding AddItemText}"
                                  IconUri="{Binding Icon}" 
                                  Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
                                  /> 
  
        <bab:BindableAppBarMenuItem x:Name="RemoveItem"
                                  Text="Remove"
                                  /> 
    </bab:BindableAppBar> 
</Grid>

Il controllo è molto semplice da utilizzare. All’interno del tag BindableAppBar potete aggiungere due tipologie di controlli: BindableAppBarButton, che è il pulsante con l’icona (ne potete aggiungere fino a 4) e BindableAppBarMenuItem, che invece rappresentano gli elementi testuali che vengono mostrati sotto le icone.

Entrambi condividono le stesse proprietà, dato che rappresentano un pulsante che può essere premuto per eseguire un’azione: l’unica differenza è che il controllo BindableAppBarButton ha una proprietà IconUri, che contiene il percorso dell’immagine che viene utilizzata come icona.

Entrambi i controlli condidono la stessa convenzione di Caliburn che viene utilizzata per le azioni: il valore della proprietà x:Name del controllo coincide con il nome del metodo, dichiarato nel ViewModel, che viene eseguito quando il pulsante viene premuto. In più, tutte le altre proprietà supportano il binding, inclusa Visibility, che permette di mostrare o nascondere un pulsante.

Prima di iniziare ad interagire con la Application Bar dal ViewModel c’è un’ulteriore passaggio da fare: aggiungere una convenzione personalizzata. Caliburn Micro, infatti, da la possibilità dagli sviluppatori di definire le proprie convenzioni, che vengono aggiunte a quelle già esistenti. Tali convenzioni vengono aggiunte nel bootstrapper, all’interno del metodo AddCustomConventions() che viene chiamato quando il boostrapper viene registrato.

Ecco il codice da inserire:

static void AddCustomConventions()
{
    ConventionManager.AddElementConvention<BindableAppBarButton>(
    Control.IsEnabledProperty, "DataContext", "Click");
    ConventionManager.AddElementConvention<BindableAppBarMenuItem>(
    Control.IsEnabledProperty, "DataContext", "Click");
}

Tramite questo codice stiamo praticamente aggiungendo una nuova convenzione per gestire l’evento Click del pulsante direttamente dal ViewModel.

Ora è il momento di passare al codice del ViewModel, per gestire le varie proprietà della BindableAppBar:

public class MainPageViewModel: Screen
{
 
    private string addItemText;
 
    public string AddItemText
    {
        get { return addItemText; }
        set
        {
            addItemText = value;
            NotifyOfPropertyChange(() => AddItemText);
        }
    }
 
    private Uri icon;
 
    public Uri Icon
    {
        get { return icon; }
        set
        {
            icon = value;
            NotifyOfPropertyChange(() => Icon);
        }
    }
 
    private bool isVisible;
 
    public bool IsVisible
    {
        get { return isVisible; }
        set
        {
            isVisible = value;
            NotifyOfPropertyChange(() => IsVisible);
        }
    }
 
    public MainPageViewModel()
    {
        AddItemText = "Add";
        Icon = new Uri("/Assets/AppBar/appbar.add.rest.png", UriKind.Relative);
        IsVisible = false;  
    }
 
    public void AddItem()
    {
        MessageBox.Show("Item added");
    }
 
    public void RemoveItem()
    {
        MessageBox.Show("Item removed");
    }
}

Se avete seguito gli altri post dedicati a Caliburn Micro la struttura di questo ViewModel dovrebbe esservi famigliare: abbiamo definito alcune proprietà e metodi, che sono collegati alla BindableAppBar tramite delle convenzioni. Quando il ViewModel viene inizializzato, definiamo il testo, l’icona e la visibilità di uno dei pulsanti nell’Application Bar. Si tratta di un approccio molto utile quando lo stato visivo dei pulsanti è dinamico e non “fisso” per tutto il ciclo di vita dell’applicazione. Prendiamo come esempio un’applicazione per leggere le ultime notizie: una delle funzionalità proposte potrebbe essere la possibilità di salvare la notizia in una lista di preferiti, tramite un pulsante ad hoc nella Application Bar. In questo caso, l’aspetto del pulsante deve cambiare in baso allo stato della notizia: se questa è già stata salvata tra i preferiti, probabilmente il testo del pulsante sarà “Rimuovi” e l’icona mostrerà il segno meno; se invece la news non fa parte dei preferiti, il pulsante avrà l’etichetta “Aggiungi” e l’icona mostrerà il segno più.

Con il ViewModel che abbiamo appena visto sarebbe semplice cambiare l’aspetto visivo del pulsante in base allo stato della notizia: sarebbe sufficiente agire sulle proprietà AddItemText e Icon per cambiare testo e icona del pulsante.

Anche la proprietà Visibility può essere comoda in molte situazioni: sempre riprendendo l’esempio precedente, ipotizziamo che l’applicazione consenta anche, sempre tramite un pulsante nella Application Bar, di creare una tile secondaria nella schermata Start che punti alla notizia. In tal caso, solo nel momento in cui l’applicazione viene aperta da una tile secondaria, vogliamo aggiungere un pulsante “Home” nella Application Bar per portare l’utente alla pagina principale dell’applicazione, dato che lo stack delle pagine è vuoto e quindi la pressione del pulsante Back la chiuderebbe direttamente. Nel nostro caso, invece, sarebbe sufficiente definire una proprietà nel ViewModel che rappresenti questa informazione e collegarla alla proprietà Visibility del pulsante, così da mostrarlo solo in caso di effettivo bisogno.


Windows Phone , Caliburn , MVVM

0 comments

Caliburn Micro e Windows Phone – Utilizzare i propri servizi

Print Content | More

In questa serie di post sull’utilizzo di Caliburn Micro in Windows Phone abbiamo imparato che il toolkit include molti helper e servizi, che vengono automaticamente registrati nel momento in cui, nel boostrapper, chiamiamo il metodo RegisterPhoneServices() della classe PhoneContainer. Abbiamo visto molti esempi di questi servizi, come il NavigationService e l’EventAggregator. Siamo stati in grado di ottenere un riferimento a tali classi semplicemente aggiungendo un parametro nel costruttore del ViewModel: Caliburn Micro, tramite il suo sistema integrato di dependency injection, è in grado di risolvere da solo le dipendenze.

Ma come comportarsi nel caso in cui abbiamo la necessità di registrare le nostre classi e i nostri servizi? Ipotizziamo che stiate sviluppando un’applicazione per leggere un feed RSS, che è in grado di fare il parsing dell’XML e di mostrare le notizie in una lista. In questo caso, potreste avere un servizio (ovvero una classe separata dal ViewModel) che si occupa di scaricare l’RSS e di convertirlo da una stringa piatta (l’XML) in una serie di oggetti che possiamo manipolare. Un approccio potrebbe essere quello di creare un’istanza del servizio direttamente nel ViewModel, come nell’esempio:

public MainPageViewModel() 
{
    IFeedService feedService = new FeedService();
     // do something with the feed service
}

Questo approccio, però, ha uno svantaggio. Ipotizziamo che vogliate fare dei test con dei dati fittizzi e, di conseguenza, vogliata utilizzare, al posto della classe FeedService, un’altra classe chiamata FakeFeedService che, invece di prelevare i dati dal feed RSS reale, li prenda da un file XML locale. In uno scenario reale, la vostra classe FeedService probabilmente sarebbe utilizzata da diversi ViewModel: di conseguenza, dovreste andare nel costruttore di ogni ViewModel e cambiare l’inizializzazione della classe nel modo seguente:

public MainPageViewModel() 
{
    IFeedService feedService = new FakeFeedService();
     // do something with the fake feed service
}

Un approccio migliore sarebbe di usare lo stesso adottato da Caliburn Micro: i servizi vengono registrati all’avvio, in questo modo è sufficiente aggiungere un parametro al costruttore del ViewModel affinchè il servizio venga automaticamente risolto e “iniettato” nel ViewModel. In questo modo, se dovete cambiare l’implementazione concreta del vostro servizio, dovrete farlo solo nel bootstrapper, quando il servizio viene registrato: automaticamente tutti i ViewModel inizieranno ad utilizzare la nuova classe.

Di conseguenza, ecco come cambia la definizione del ViewModel con questo nuovo approccio:

public MainPageViewModel(IFeedService feedService)
{
    //do something with the feed service
}

Registrare i propri servizi

Vediamo ora come implementare un vostro servizio e registrarlo, così da poterlo utilizzare con l’approccio sopra descritto.

Innanzitutto dovete creare un’interfaccia per il servizio, che descriva i metodi che la classe deve esporre. Si tratta di un passaggio molto importante, in quanto consente di cambiare, in maniera semplice, l’implementazione concreta di un servizio semplicemente registrandolo nel bootstrapper. Riprendendo l’esempio di prima, FeedService e FakeFeedService sono due implementazioni concrete differenti, ma si rifanno alla stessa interfaccia, dato che espongono gli stessi metodi: quelli per elaborare un feed RSS. Andremo perciò a creare l’interfaccia IFeedService e a registrarla nel costruttore del ViewModel.

public interface IFeedService
{
    Task<List<FeedItem>> GetNews(string url);
}

L’interfaccia descrive solamente un metodo, che utilizzeremo per recuperare l’elenco delle notizie: accetta in ingresso l’url del feed RSS e restituisce una collezione di oggetti di tipo FeedItem, che è una semplice classe che descrive una singola notizia:

public class FeedItem
{
    public string Title { get; set; }
    public string Description { get; set; }
    public Uri Url { get; set; }
}

Infine, ecco l’implementazione concreta della classe FeedService:

public class FeedService: IFeedService
{
    public async Task<List<FeedItem>> GetNews(string url)
    {
        WebClient client = new WebClient();
        string content = await client.DownloadStringTaskAsync(url);
 
        XDocument doc = XDocument.Parse(content);
        var result = doc.Descendants("rss").Descendants("channel").Elements("item").Select(x => new FeedItem
        {
            Title = x.Element("title").Value,
            Description = x.Element("description").Value
        }).ToList();
 
        return result;
    }
}

Questa classe contiene l’implementazione concreta dell’interfaccia IFeedService e, di conseguenza, del metodo GetNews(). All’interno di tale metodo andiamo ad utilizzare la classe WebClient per scaricare il feed: nello specifico, per comodità ho installato l’Async Pack di cui vi ho parlato in un post precedente, grazie al quale possiamo utilizzare il metodo asincrono DownloadStringTaskAsync(). Una volta ottenuto l’XML del feed RSS, utilizziamo LINQ TO XML per attraversare i nodi che lo compongono e creare una serie di oggetti di tipo FeedItem con le principali informazioni (titolo e descrizione).

Ora è tempo di registrare il nostro servizio, così da utilizzarlo nel ViewModel. La registrazione viene fatta nel bootrapper e dovrebbe esservi famigliare, dato che è lo stesso approccio utilizzato per la registrazione dei ViewModel: lo facciamo nel metodo Configure() della classe boostrapper.

protected override void Configure()
{
    container = new PhoneContainer(RootFrame);
 
    container.RegisterPhoneServices();
    container.PerRequest<MainPageViewModel>();
    container.PerRequest<IFeedService, FeedService>();
    AddCustomConventions();
}

Potete notare una differenza rispetto a quanto facciamo con i ViewModel: dato che, in questo caso, il nostro servizio utilizza un’interfaccia, dobbiamo dire al container che ogni qualvolta un ViewModel richiederà un oggetto di tipo IFeedService, sarà suo compito restituirgli un’istanza concreta della classe FeedService. Lo facciamo grazie al metodo PerRequest<T, Y>(), dove T è l’interfaccia e Y l’implementazione reale. Ora siamo in grado di usare il servizio nel nostro ViewModel per mostrare le notizie.

Ecco un esempio di XAML, all’interno della View, in grado di mostrare la collezione di oggetti di tipo FeedItem:

<StackPanel>
    <Button Content="Load website" x:Name="LoadWebsite"></Button>
    <ListBox x:Name="News">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock Text="{Binding Path=Title}"></TextBlock>
                    <TextBlock Text="{Binding Path=Description}"></TextBlock>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</StackPanel>

Abbiamo un pulsante, che utilizzeremo per caricare il feed RSS, e una lista che, tramite la definizione dell’ItemTemplate, mostrerà le proprietà Title e Description (ovvero titolo e descrizione) della notizia.

Ed ecco la definizione del ViewModel:

public class MainPageViewModel: Screen
{
    private readonly IFeedService feedService;
 
    private List<FeedItem> news;
 
    public List<FeedItem> News
    {
        get { return news; }
        set
        {
            news = value;
            NotifyOfPropertyChange(() => News);
        }
    }
 
    public MainPageViewModel(IFeedService feedService)
    {
        this.feedService = feedService;
    }
 
    public async void LoadWebsite()
    {
        News = await feedService.GetNews("http://feeds.feedburner.com/qmatteoq_eng");
    }
}

Nulla di speciale da evidenziare se non che, nel costruttore del ViewModel, abbiamo aggiunto un parametro di tipo IFeedService: questo fa si che, grazie alla registrazione nel boostrapper fatta in precedenza, il ViewModel contenga in automatico un riferimento al servizio, che siamo in grado perciò di utilizzare nel momento in cui l’utente preme il pulsante e il metodo LoadWebsite() viene invocato. Nell’esempio, tramite il metodo GetNews() recuperiamo l’elenco di notizie pubblicate sul mio blog in inglese.

Passare dati da una pagina all’altra

Nel momento in cui abbiamo parlato di navigazione abbiamo evidenziato la possibilità di passare parametri nell’URL della pagina, così da poter scambiare informazioni tra le pagine. Il limite di questo approccio è che, trattandosi di parametri in query string, siamo in grado solamente di scambiare dati testuali, come stringhe o numeri. Nella maggior parte dei casi, invece, abbiamo la necessità di passare oggetti complessi. Ipotizziamo di voler espandere la nostra applicazione di esempio ed implementare una pagina per leggere il dettaglio della notizia: in tal caso, quello che vogliamo fare è passare l’intero oggetto FeedItem che è stato selezionato, così da poterne accedere a tutte le proprietà.

Un approccio che possiamo utilizzare è quello che abbiamo descritto in questo post: utilizzare un nostro servizio ad hoc, che passeremo ad ogni ViewModel.

public class DataService
{
    public FeedItem SelectedItem { get; set; }
}

La classe è molto semplice, dato che contiene solamente una proprietà di tipo FeedItem, nella quale andremo a salvare la notizia selezionata dall’utente.

Ora dobbiamo registrare questa nuova classe nel bootstrapper:

protected override void Configure()
{
    container = new PhoneContainer(RootFrame);
 
    container.RegisterPhoneServices();
    container.PerRequest<MainPageViewModel>();
    container.PerRequest<DetailPageViewModel>();
    container.PerRequest<IFeedService, FeedService>();
    container.Singleton<DataService>();
    AddCustomConventions();
}

In questa occasione possiamo vedere un nuovo tipo di registrazione, tramite il metodo Singleton<T>(). Qual è la differenza? Nel momento in cui registriamo una classe con il metodo PerRequest<T>(), ogni volta che all’interno dell’applicazione tentiamo di utilizzarla ci sarà restituita una nuova istanza. E’ l’approccio ideale per i ViewModel: nel nostro caso, ogni volta che la pagina di dettaglio viene caricata il suo contenuto deve essere cambiato, perchè deve essere mostrata la news selezionata; ha senso perciò che, ad ogni richiesta, ci venga restituita una nuova pagina con un nuovo ViewModel. Ecco perciò che la classe DetailPageViewModel viene registrata con il metodo PerRequest<T>(). Non è però la stessa situazione della classe DataService: in questo caso abbiamo la necessità di mantenere la stessa istanza della classe, perchè vogliamo che ogni ViewModel possa accedere al dato contenuto nella proprietà SelectedItem. Se ci facessimo restituire una nuova istanza ogni volta, il valore della proprietà andrebbe perso ad ogni richiesta. La soluzione è proprio quella di utilizzare il metodo Singleton<T>(), che registra l’istanza della classe come univoca, facendo si che venga restituita sempre la stessa ad ogni richiesta.

Ora dobbiamo aggiungere semplicemente un parametro di tipo DataService sia nel costruttore della classe MainPageViewModel, che in quello della classe DetailPageViewModel:

public class MainPageViewModel: Screen
{
    private readonly IFeedService feedService;
    private readonly INavigationService navigationService;
    private readonly DataService dataService;
 
    private List<FeedItem> news;
 
    public List<FeedItem> News
    {
        get { return news; }
        set
        {
            news = value;
            NotifyOfPropertyChange(() => News);
        }
    }
 
    private FeedItem selectedNew;
 
    public FeedItem SelectedNew
    {
        get { return selectedNew; }
        set
        {
            selectedNew = value;
            dataService.SelectedItem = value;
            navigationService.UriFor<DetailPageViewModel>().Navigate();
            NotifyOfPropertyChange(() => SelectedNew);
        }
    }
 
    public MainPageViewModel(IFeedService feedService, INavigationService navigationService, DataService dataService)
    {
        this.feedService = feedService;
        this.navigationService = navigationService;
        this.dataService = dataService;
    }
 
    public async void LoadWebsite()
    {
        News = await feedService.GetNews("http://feeds.feedburner.com/qmatteoq_eng");
    }
}

Nel ViewModel della pagina principale abbiamo aggiunto anche una proprietà di nome SelectedNew: se vi ricordate quanto spiegato nel post dedicato alle collezioni, in questo modo facciamo sì che questa venga automaticamente collegata all’elemento selezionato della lista. Dobbiamo però fare due modifiche al set della proprietà (che viene invocato ogni qualvolta il suo valore viene modificato, nel nostro caso quando viene selezionato un elemento dalla lista): la prima è quella di salvare, all’interno della classe DataService, l’elemento selezionato; la seconda è quella di scatenare la navigazione verso la pagina di dettaglio.

E per quanto riguarda la pagina di dettaglio? La View, semplicemente, si limiterà a mostrare le informazioni della news selezionata:

<StackPanel>     
    <TextBlock x:Name="Title" />     
    <TextBlock x:Name="Description" />
</StackPanel>

Il ViewModel conterrà semplicemente due proprietà, Title e Description, che saranno valorizzate con le informazioni recuperate dal DataService:

public class DetailPageViewModel: Screen
{
    private string title;
 
    public string Title
    {
        get { return title; }
        set
        {
            title = value;
            NotifyOfPropertyChange(() => Title);
        }
    }
 
    private string description;
 
    public string Description
    {
        get { return description; }
        set
        {
            description = value;
            NotifyOfPropertyChange(() => Description);
        }
    }
 
    public DetailPageViewModel(DataService dataService)
    {
        Title = dataService.SelectedItem.Title;
        Description = dataService.SelectedItem.Description;
    }
}

Anche in questo caso abbiamo aggiunto un parametro di tipo DataService nel costruttore: dato che, nel boostrapper, lo abbiamo registrato come singleton, il risultato è che l’istanza che ci sarà passata è la stessa che è stata definita nel MainPageViewModel: di conseguenza, la proprietà SelectedItem della classe DataService sarà valorizzata con la notizia scelta dall’utente nella pagina precedente.

Come al solito, vi lascio a disposizione un progetto di esempio con cui sperimentare le funzionalità descritte in questo post. Buon lavoro!


Windows Phone , Caliburn , MVVM

0 comments

Prossimi appuntamenti: Community Days, DotNetCampus, Intel MeetUp e altro ancora

Print Content | More

Periodo ricco di appuntamenti per gli sviluppatori e per le community d’Italia! Vi segnalo alcuni degli eventi più interessanti nel prossimo periodo.

Community Days

Dopo la tappa di Milano, tornano i Community Days e questa volta si spostano a Catania, per offrire anche agli sviluppatori del Sud Italia due giorni di sessioni e lab su Windows 8, Windows Phone, Azure e Web. Avrò il piacere di essere presente anche io a rappresentare DotNetLombardia, a dare una mano ai laboratori e a tenere una sessione sulla distribuzione di applicazioni Windows Phone: lo Store, gli scenari Enterprise e l’in app-purchase. L’appuntamento è per il 20 e 21 Maggio presso la Cittadella Universataria di Catania. Trovate l’agenda completa, le informazioni logistiche e i link per iscrivervi sul sito ufficiale. Attenzione che i laboratori su Windows 8, Windows Phone e Azure richiedono una registrazione separata rispetto a quella tradizionale per le giornate.

DotNetCampus

Anche il DotNetCampus si ripete e, dopo la tappa di Roma, propone altri due appuntamenti a Cosenza (il 15 Maggio presso la sede dell’Università della Calabria) e a Cesena (il 14 Giugno, presso il TechnoGym Village). Sarò presente anche in questa occasione, proponendo una sessione dedicata all’accesso ai dati locali in Windows Phone: database, isolated storage e file sharing. A rappresentare DotNetLombardia, con me, ci sarà anche Roberto, che terrà due sessioni dedicate a Windows Phone e ad Azure.

Trovate tutte le indicazioni, l’agenda e i link per iscrivervi ad entrambe le tappe sul sito ufficiale.

Windows Azure Conference

DotNetLombardia e Microsoft propongono insieme una giornata di approfondimento dedicata alle novità recentemente annunciate su Azure, suddivisa in due momenti: uno più formativo, al mattino, con sessioni su tematiche come i Mobile Service o lo sviluppo e il deploy di applicazioni web, e uno più pratico, con laboratori e ampio spazio alle domande. L’appuntamento è per il 6 Maggio presso il Microsoft Innovation Campus di Peschiera Borromeo (MI). Iscrizioni e maggiori dettagli sulla pagina ufficiale.

Intel MeetUp – The Natural User Interface Episode

Anche gli amici di Intel propongono, Sabato 4 Maggio a Bologna, un evento molto interessante dedicato alle Natural User Interface, in collaborazione con la community emiliana DotDotNet. Si parlerà di Perceptual Computing con le tecnologie di Intel, di Kinect e del nuovo Leap Motion. Se volete scoprire le ultime novità sulle nuove modalità di interazione con le più recenti tecnologie, non potete mancare! Agenda e iscrizioni sul sito ufficiale.

Tutto quello che avreste voluto sapere sull'accesso ai dati (e non avete mai osato chiedere)

Gli amici di DotNetToscana propongono, Sabato 18 Maggio a Pisa, una giornata completamente dedicata all’accesso ai dati in Windows Phone e Windows 8: si parlerà di SQLite, WebAPI, Mobile Service e SkyDrive. Se siete di quelle parti non perdetevelo perchè l’agenda è decisamente interessante. Informazioni e iscrizioni sul sito ufficiale.

Spring Event by Visual Basic Tips & Tricks

Visual Basic Tips & Tricks, una delle community italiane storiche, propone Sabato 8 Giugno a Vicenza una mattinata di approfondimento sulle nuove tecnologie web e cloud di Microsoft. Si parlerà di Azure, SignalR e LightSwitch, l’ottimo strumento di Microsoft per realizzare in maniera semplice e veloce applicazioni business. Iscrizioni e agenda sul sito ufficiale.

Web e ASP.NET Live

Gli eventi community sono da sempre il modo migliore per avvicinarsi alle nuove tecnologie e, allo stesso tempo, espandere la propria rete di contatti e conoscere nuove persone. Non sempre però si ha la possibilità di partecipare fisicamente. Vi farà piacere sapere, perciò, che ASPItalia ha organizzato un pomeriggio dedicato alle ultime novità Microsoft per il Web e che sarà tramesso in streaming online: tra gli argomenti trattati le Single Page Application, SignalR, WebAPI e tanto altro ancora, a cura dei membri di ASPItalia. Pur essendo un evento esclusivamente online, è comunque necessario iscriversi sul sito ufficiale, dove trovate anche l’agenda completa. L’appuntamento è per il 16 Maggio.


Microsoft , DotnetCampus , Community Days , Intel

0 comments

Caliburn Micro e Windows Phone – Utilizzare i launcher e i chooser

Print Content | More

Nel post precedente della serie abbiamo visto come funzioni l’infrastrattura dei messaggi integrata in Caliburn Micro. In questo post vedremo come utilizzare i launcher e i chooser in un ViewModel: i due argomenti sono strettamente collegati, dato che per eseguire questo compito Caliburn Micro utilizzato un tipo speciale di messaggio. Infatti, per interagire con launcher e chooser andremo ad utilizzare la classe IEventAggregator che abbiamo imparato ad utilizzare in precedenza. La differenza, in questo caso, sarà che i messaggi saranno “nascosti” e già inclusi all’interno del toolkit. Iniziamo!

Do per scontato che, se state leggendo questo blog, dato che MVVM è un argomento “avanzato”, sappiate già cosa sono i launcher e i chooser. In caso contrario, vi basti sapere per ora che sono un meccanismo che il sistema operativo mette a disposizione per interagire con le applicazioni native: ad esempio, per mostrare una posizione nell’applicazione Mappe o per importare un contatto dall’hub People; o ancora, inviare un SMS tramite l’applicazione Messaggi.

I launcher

Vediamo come interagire con i launcher, ovvero i task che sono utilizzati per demandare un’operazione ad un’applicazione nativa ma non si aspettano dati di ritorno: il nostro esempio sarà basato sulla classe MapsTask, che consente di mostrare una posizione nell’applicazione Mappe. Do per scontato che abbiate già creato il progetto base utilizzando Caliburn Micro, perciò abbiate già a disposizione una View collegata al relativo ViewModel tramite l’apposita convenzione che abbiamo imparato ad usare nei primi post.

Nella View aggiungiamo un pulsante, che useremo per eseguire il launcher:

<StackPanel>
    <Button Content="Launch map" x:Name="LaunchMap" />
</StackPanel>

Come sempre, grazie alle convenzioni di Caliburn, questa dichiarazione fa si che, al click sul pulsante, venga eseguito un metodo dal nome LaunchMap dichiarato nel ViewModel. Diamo uno sguardo al codice:

public class MainPageViewModel: Screen
{
    public MainPageViewModel(IEventAggregator eventAggregator)
    {
        this.eventAggregator = eventAggregator;
        eventAggregator.Subscribe(this);
    }
 
    public void LaunchMap()
    {
        eventAggregator.RequestTask<MapsTask>(task =>
                                                  {
                                                      task.SearchTerm = "Milan";
                                                  });
    }
}

Come abbiamo potuto vedere nel post dedicato ai messaggi, è sufficiente aggiungere un parametro nel costruttore del ViewModel di tipo IEventAggregator per ottenere un riferimento all’oggetto da utilizzare per interagire con i messaggi. Il primo passaggio è sottoscriversi alla ricezione, utilizzando il metodo Subscribe(), passando come parametro la parola chiave this (dato che vogliamo abilitare alla ricezione il ViewModel stesso).

Nel metodo LaunchMap() utilizziamo la classe EventAggregator, chiamando uno degli extension method aggiunti alla versione di Caliburn Micro per Windows Phone: RequestTask<T>(), dove T è la classe che rappresenta il launcher che vogliamo eseguire (sono quelle contenute all’interno del namespace Microsoft.Phone.Tasks). Come parametro possiamo passare un delegate (nell’esempio, ne utilizziamo uno anonimo): si tratta di un’operazione necessaria per tutti quei launcher o chooser che hanno bisogno di alcune informazioni per funzionare correttamente. Nel nostro esempio, impostiamo la proprietà SearchTerm del task, per specificare la posizione che vogliamo cercare nella mappa. Se dobbiamo utilizzare un launcher che non richiede alcuna informazione in input (come il MarketplaceDetailTask), possiamo semplicemente non passare alcun parametro al metodo.

Se provate questa semplice applicazione sul telefono, vedrete l’applicazione Mappe aprirsi e localizzare la città di Milano nel momento in cui premete il pulsante.

I chooser

I chooser funzionano in un modo leggermente differente: il modo in cui andremo ad eseguirli è lo stesso deie launcher ma, in questo caso, ci aspettiamo anche un risultato. Come già detto all’inizio del post, l’utilizzo di launcher e chooser con Caliburn è basato sui messaggio: ecco perciò che il modo in cui gestiamo il valore di ritorno è lo stesso che abbiamo visto in precedenza per ricevere un messaggio. Facciamo una prova utilizzando il chooser PhoneNumberChooserTask, che può essere utilizzato per importare il numero di telefono di un contatto dalla rubrica.

Prima aggiungiamo il solito pulsante nella View:

<StackPanel>
    <Button Content="Open contact" x:Name="OpenContact" />
</StackPanel>

Ora diamo uno sguardo al ViewModel:

public class MainPageViewModel: Screen, IHandle<TaskCompleted<PhoneNumberResult>>
{
    private readonly IEventAggregator eventAggregator;
 
    public MainPageViewModel(IEventAggregator eventAggregator, INavigationService navigationService)
    {
        this.eventAggregator = eventAggregator;
 
        eventAggregator.Subscribe(this);
    }
 
    public void OpenContact()
    {
        eventAggregator.RequestTask<PhoneNumberChooserTask>();
    }
 
    public void Handle(TaskCompleted<PhoneNumberResult> message)
    {
        MessageBox.Show(message.Result.DisplayName);
    }
}

Il modo in cui utilizziamo un chooser è lo stesso che abbiamo visto per il launcher: utilizziamo il metodo RequestTask<T>() dell’oggetto EventAggregator, passando come valore di T il tipo di chooser che vogliamo utilizzare. In questo caso, rispetto a prima, non passiamo alcun parametro, dato che questo chooser non richiede nessuna informazione in ingresso per funzionare. Per gestire il valore di ritorno (quindi il numero della rubrica scelto dall’utente) utilizziamo lo stesso approccio visto nei messaggi: facciamo ereditare il ViewModel dalla classe IHandle<T>, dove T è di tipo TaskCompleted<T>. Questo secondo T, a sua volta, è il tipo restituito dal chooser (nel nostro caso, un oggetto di tipo PhoneNumberResult).

Ereditando da questa interfaccia, saremo costretti ad implementare il metodo Handle(), che riceve come parametro il risultato restituito dal chooser. Sta a noi decidere cosa farne: nell’esempio, ci limitiamo a mostrare il valore della proprietà DisplayName (che contiene nome e cognome del contatto).

Come gestire correttamente il ciclo di vita dei launcher e chooser

C’è un problema nell’usare l’architettura dei messaggi per gestire launcher e chooser. Quando abilitate un ViewModel alla ricezione di messaggi utilizzando il metodo Subscribe(), la sottoscrizione viene mantenuta attiva fintanto che l’istanza del ViewModel viene tenuta in memoria. Nel caso dei launcher e dei chooser questo può causare qualche problema: se ricordate il post precedente, eravamo in grado di sfruttare i messaggi per scambiare informazioni tra due ViewModel; in questo caso, invece, utilizziamo i messaggi per scambiare informazioni all’interno del ViewModel stesso (dato che si occupa sia di inviarlo, che di riceverlo). Se, ad esempio, spostaste il metodo OpenContact() che abbiamo appena visto in un altro ViewModel (ad esempio, una seconda pagina), ma manteneste il MainPageViewModel abilitato alla ricezione di messaggi di tipo TaskCompleted<PhoneNumberResult>, notereste che il MainPageViewModel continuerebbe a rispondere all’attivazione del chooser, anche se venisse scatenata da un altro ViewModel.

Questo scenario si applica soprattutto quando parliamo del MainPageViewModel, ovvero il ViewModel che è collegato alla pagina principale dell’applicazione: per questo motivo, la sua istanza viene sempre mantenuta attiva, fino al momento in cui l’applicazione non viene chiusa, al contrario di quanto avviene per le altre pagine (se siete nella seconda pagina della vostra applicazione e tornate alla pagina principale, questa, assieme al relativo ViewModel, viene dismessa).

Il modo migliore per gestire questa casistica è quello di usare gli eventi di navigazione che abbiamo imparato ad utilizzare in un post precedente ed ereditare il nostro ViewModel dalla classe Screen: in questo modo possiamo chiamare il metodo Subscribe() della classe EventAggregator nel momento in cui l’utente naviga verso la pagina (sfruttando il metodo OnActivate()) e, viceversa, utilizzare il metodo Unsubscribe() nel momento in cui l’utente lascia la pagina (sfruttando il metodo OnDeactivate()). Con questo approccio, nel momento in cui l’utente si sposta dalla pagina corrente verso un’altra pagina, questa non è più abilitata alla ricezione dei messaggi e, di conseguenza, ignorerà qualsiasi interazione con i launcher e i chooser.

Ecco un esempio di come appare il ViewModel aggiornato:

public class MainPageViewModel: Screen, IHandle<TaskCompleted<PhoneNumberResult>>
{
    private readonly IEventAggregator eventAggregator;
 
    public MainPageViewModel(IEventAggregator eventAggregator)
    {
        this.eventAggregator = eventAggregator;
    }
    protected override void OnActivate()
    {
        eventAggregator.Subscribe(this);
        base.OnActivate();
    }
 
    protected override void OnDeactivate(bool close)
    {
        eventAggregator.Unsubscribe(this);
        base.OnDeactivate(close);
    }
 
    public void OpenContact()
    {
        eventAggregator.RequestTask<PhoneNumberChooserTask>();
    }
 
    public void Handle(TaskCompleted<PhoneNumberResult> message)
    {
        MessageBox.Show(message.Result.DisplayName);
    }
}

Come potete notare, non chiamiamo più il metodo Subscribe() della classe EventAggregator nel costruttore, ma lo abbiamoi spostato nel metodo OnActivate().

Se volete fare qualche esperimento con quanto appreso in questo post, di seguito trovate il link per scaricare un progetto di esempio.


Windows Phone , MVVM , Caliburn

0 comments

Slide e demo del DotNetCampus

Print Content | More

Sabato si è svolto a Roma, nella cornice dell’Università di Roma Tre, il DotNetCampus, un evento organizzato da DevLeap in collaborazione con diversi sponsor che mette insieme tante realtà che ruotano attorno al mondo Microsoft: community, MVP, studenti, aziende e quant’altro. Anche noi di DotNetLombardia eravamo là, con un nostro stand e con la presenza mia e di Roberto, con il quale abbiamo proposto sessioni riguardanti Windows Phone e Windows Azure.

Nello specifico, ho tenuto una sessione dedicata all’accesso ai dati locali in Windows Phone 8: le nuove API per lo storage, i database disponibili e la nuova funzionalità di data sharing, per condividere dati con altre applicazioni.

La giornata è stata magnifica: tantissima gente, molto interesse verso gli argomenti proposti e tante occasioni, sia per noi speaker e membri delle community che per i partecipanti, di conoscere e incontrare altre persone.

Di seguito, vi propongo le slide e le demo utilizzate durante la mia sessione. Potete fare riferimento a questo post per tutti i dettagli su come installare e utilizzare SQLite nella vostra applicazioni Windows Phone 8.

Per qualsiasi dubbio, potete contattarmi tramite il form apposito!


Windows Phone , DotnetCampus

0 comments