Guest post: Template10, un nuovo approccio nello sviluppo di Universal Windows app – Le basi

Questo post è stato scritto da Matteo Pagani, Support Engineer in Microsoft per il programma AppConsult

Una delle novità che avrete sicuramente notato iniziando a sviluppare un progetto di tipo Universal Windows per Windows 10 è il ridotto numero di template disponibili in Visual Studio 2015. Se, per quanto riguarda Windows / Windows Phone 8.1, Visual Studio 2013 metteva a disposizione svariati template con i più diffusi layout grafici già impostati (Hub, Pivot, ecc.), in Visual Studio 2015 troviamo solo un template di nome "Blank app" o "Applicaziona vuota", che include semplicemente lo scheletro di una Universal Windows app.

La motivazione è che, spesso, i template predefiniti venivano usati dagli sviluppatori con meno esperienza nel campo della progettazione della User Experience per semplificare il loro lavoro e non dover creare da zero l'interfaccia grafica. Purtroppo però, alla lunga, questo approccio ha portato alla pubblicazione di molte applicazioni dall'interfaccia utente molto simile tra di loro. Di conseguenza, con Windows 10 si è deciso di lasciare molta più libertà creativa agli sviluppatori e di non costringerli o guidarli verso l'adozione di uno specifico design pattern. Attenzione, questo non significa che l'interfaccia utente si possa realizzare senza seguire alcun criterio: la qualità della user experience rimane sempre uno degli aspetti fondamentali di un'applicazione! A tal proposito, vi consiglio una lettura approfondita delle linee guida ufficiali per il design di Universal Windows app all'indirizzo https://dev.windows.com/en-us/design

Il nuovo template è molto semplice ed è il punto di partenza perfetto per realizzare qualsiasi applicazione. A volte, però, soprattutto quando si devono realizzare applicazioni complesse, può risultare fin troppo semplice: nel momento in cui abbiamo la necessità di gestisce scenari avanzati (l'uso del pattern MVVM, il salvataggio e il caricamento dello stato di una pagina, ecc.), dobbiamo farci carico di realizzare tutta l'infrastruttura necessaria.

A tale scopo, un team di persone in Microsoft, guidato da Jerry Nixon (Technical Evangelist noto per la sua serie di corsi su MVA, tenuti insieme ad Andy Wigley, dedicati allo sviluppo di applicazioni per Windows 10) ha iniziato lo sviluppo di un template più avanzato, chiamato Template10. Si tratta di un progetto open source, pubblicato su GitHub all'indirizzo https://github.com/Windows-XAML/Template10 e, di conseguenza, aperto ai contributi di qualsiasi sviluppatore voglia collaborare nella crescita di questo template.

Il progetto è in continua evoluzione e gli obiettivi sono molto ambiziosi: Template10 vuole diventare il punto di riferimento per gli sviluppatori di Universal Windows app, indipendentemente dall'approccio allo sviluppo che preferiscono (code behind o MVVM) e dalle librerie che già utilizzano. Se volete saperne di più, potete consultare la FAQ ufficiale all'indirizzo https://github.com/Windows-XAML/Template10/wiki/Questions. Nel prossimo futuro, Template10 sarà distribuito sia come pacchetto NuGet sia come estensione di Visual Studio, così da avere la possibilità di creare una nuova applicazione in grado di sfruttarlo direttamente dal menu New project di Visual Studio 2015. Per il momento, però, anche se il template è considerato stabile ed adatto all'utilizzo in progetti reali, è disponibile solamente su GitHub. Vediamo, perciò, i passi necessari, al momento, per iniziare lo sviluppo di una nuova applicazione basata su Template10.

Aggiornamento: Template10 è disponibile anche su NuGet http://www.nuget.org/packages/Template10/1.0.2.2-preview. Per poterlo trovare e installare da Visual Studio, però, è indispensabile abilitare l’opzione Include prerelease, dato che il pacchetto è stato pubblicato in modalità beta.

Nel corso di questo primo post vedremo la struttura di Template10 e il suo utilizzo in uno scenario base, ovvero lo sviluppo tradizionale in code behind. Nel prossimo post, invece, vedremo come Template10 dia il suo meglio quando viene usato in combinazione con il pattern MVVM.

Creare il primo progetto

Innanzitutto, dobbiamo clonare il progetto da GitHub. Possiamo farlo sfruttando direttamente gli strumenti messi a disposizione da Visual Studio 2015.

  1. Apriamo la finestra di Visual Studio 2015 denominata Team Explorer. Come impostazione predefinita, è accessibile dall'apposita voce in basso a destra, affiancata al Solution Explorer.
  2. Troveremo una sezione denominata Local Git Repositories, in cui vengono elencati tutti i repository Git che avete in locale. Premete il pulsante Clone.
  3. Nel primo campo (evidenziato dallo sfondo giallo) dovete inserire l'URL del repository GitHub, ovvero https://github.com/Windows-XAML/Template10
  4. Nel secondo campo, invece, dovete specificare il percorso locale sul vostro computer dove vorrete clonare il progetto.
  5. Ora premete il pulsante Clone: Visual Studio si farà carico di scaricare il progetto e di copiarlo sul vostro computer.

 

 

Da questo momento in poi, sempre tramite Team Explorer, avrete la possibilità di accedere al repository e di scegliere l'opzione Sync: tale funzionalità vi permetterà di scaricare i sorgenti più aggiornati e di avere sempre accesso, perciò, alla versione più recente della libreria.

Un buon punto di partenza per creare una nuova applicazione con Template10 è sfruttare il progetto già pronto denominato Blank e contenuto all'interno della cartella Templates (Project), il quale predispone l'infrastruttura di base. Noi, però, partiremo da un progetto vuoto e, man mano, configureremo manualmente le parti che ci servono, così da capire meglio come funziona il template e quali sono i vantaggi che apporta.

Per il nostro scopo, perciò, creiamo un nuovo progetto di tipo Universal Windows utilizzando il template Blank App. Dopodiché, facciamo clic con il tasto destro sulla soluzione e scegliamo Add existing project: a questo punto andiamo a cercare, sul nostro computer, la cartella in cui abbiamo clonato la libreria da GitHub. Nello specifico, dobbiamo andare ad aggiungere il progetto di nome Template10Library: lo troviamo all'interno dell'omonima cartella della libreria, rappresentato dal file Template10Library.csproj. A questo punto è sufficiente, tramite il menu Add reference di Visual Studio, aggiungere un riferimento a tale progetto all'interno della nostra applicazione. Ora siamo pronti per iniziare ad usare Template10.

Il bootstrapper

La classe App, come ben saprete, è il punto di partenza di tutte le Universal Windows app: si fa carico di inizializzare la finestra dell'applicazione con, all'interno, il frame principale, che si fa carico di gestire il rendering e la navigazione tra le varie pagine.

La struttura base della classe App (dichiarata nel file App.xaml.cs) può essere però, a volte, un po' confusionaria. Trovate, infatti, dichiarati numerosi parti di codice che fanno parte dell'inizializzazione standard e, di conseguenza, non devono essere modificati dallo sviluppatore. Anche la gestione del ciclo di vita dell'applicazione (descritta in un post precedente su questo stesso blog) può risultare difficile da comprendere: a seconda della modalità di avvio (lancio dalla tile, attivazione tramite tile secondaria, restore dalla terminazione, ecc.), ci sono diversi punti di accesso, che corrispondono ad altrettanti metodi.

Template10 si fa carico di semplificare il funzionamento di tale classe introducendo il concetto di bootstrapper. Tutto il codice "standard" per l'inizializzazione dell'applicazione è stato spostato all'interno di una classe specifica della libreria, dalla quale dovremo far ereditare la nostra classe App.

Ecco come appare, perciò, la definizione del file App.xaml in un progetto che fa uso di Template10:

<common:BootStrapper

x:Class="BasicSample.App"

xmlns:common="using:Template10.Common"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

</common:BootStrapper>

Come vedete, il nodo base App è stato sostituito dalla classe BootStrapper, contenuta all'interno del namespace Template10.Common. Di conseguenza, anche nel code-behind (quindi nel file App.xaml.cs) la classe App dovrà ereditare dalla classe BootStrapper, come nell'esempio seguente:

sealed partial class App : Template10.Common.BootStrapper

{

public App()

{

InitializeComponent();

}

}

Ecco come appare, invece, l'inizializzazione completa della classe App in un'applicazione basata su Template10:

sealed partial class App : Template10.Common.BootStrapper

{

public App()

{

InitializeComponent();

}

public override Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)

{

NavigationService.Navigate(typeof(MainPage));

return Task.FromResult<object>(null);

}

}

Come potete notare, rispetto all'inizializzazione standard, il codice è estremamente semplificato. Il cuore è il metodo OnStartAsync() , che rappresenta il punto di ingresso dell'applicazione, indipendentemente dallo scenario: sia che l'applicazione sia stata aperta tramite la tile principale, da una tile secondaria o da uno dei contratti disponibili nella Universal Windows Platform (come quello di sharing), passerete attraverso questo metodo. Nel caso più semplice, il vostro codice sarà come quello mostrato nell'esempio: una semplice navigazione verso la pagina principale. La seconda riga di codice serve solo come "workaround" per gestire il fatto che il metodo OnStartAsync() è dichiarato come asincrono (e quindi, come da best practice, restituisce Task), ma non è detto che al suo interno faccia uso di metodi basati sul pattern async / await (come nell'esempio precedente). Di conseguenza, in tal caso, si restituisce un Task vuoto.

Questo approccio semplifica di molto la gestione delle varie modalità di attivazione di un'applicazione perchè il bootstrapper si fa carico, dietro le quinte, di gestire la creazione della finestra e del frame in maniera corretta. Nell'approccio tradizionale, invece, il frame viene creato unicamente all'interno del metodo OnLaunched() , lasciandovi "scoperti" nel caso in cui invece l'applicazione sia stata aperta con una modalità differente dal semplice tap sulla tile principale.

A titolo di esempio, ecco come appare la classe App in caso l'applicazione supporti la creazione (e di conseguenza, l'attivazione) di tile secondarie:

sealed partial class App : Template10.Common.BootStrapper

{

public App()

{

InitializeComponent();

SplashFactory = e => new SplashScreenView(e);

}

public override Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)

{

AdditionalKinds cause = DetermineStartCause(args);

if (cause == AdditionalKinds.SecondaryTile)

{

LaunchActivatedEventArgs eventArgs = args as LaunchActivatedEventArgs;

NavigationService.Navigate(typeof (DetailPage), eventArgs.Arguments);

}

else

{

NavigationService.Navigate(typeof (MainPage));

}

 

return Task.FromResult<object>(null);

}

}

Come vedete, rispetto all'approccio tradizionale, il codice è estremamente semplificato. Tramite il metodo interno DetermineStartCause() siamo in grado di determinare la causa di attivazione dell'applicazione: in base ad essa, ci limitiamo a navigare verso una pagina piuttosto che l'altra dell'applicazione. Nell'esempio precedente, abbiamo ipotizzato che la tile secondaria dia la possibilità di aprire l'applicazione direttamente sul dettaglio di un elemento: ci limitiamo, perciò, a recuperare gli argomenti della tile (contenuti nella proprietà Arguments dei parametri di attivazione) e a portare l'utente verso la pagina predisposta della nostra applicazione, chiamata DetailPage. In caso, invece, di avvio dalla tile principale il flusso di navigazione rimane invariato e l'utente viene portato alla pagina principale.

Con lo stesso approccio, possiamo gestire tutti gli altri scenari di attivazione: da notifica toast, da Uri, da contratto di sharing, ecc. In questo caso, il metodo DetermineStartCause() vi restituirà il valore Other dell'enumeratore AdditionalKinds: dovremo perciò sfruttare la proprietà Kind del parametro di attivazione di tipo IActivatedEventArgs per determinare lo scenario di utilizzo.

All'interno del bootstrapper avete accesso a tre altri metodi di cui fare l'override, che possono risultare utili in alcuni scenari:

  1. OnInitializeAsync() viene chiamato durante l'inizializzazione dell'applicazione e prima del metodo OnStartAsync() . E' utile se dovete inizializzare qualche funzionalità in fase di startup (ad esempio, i servizi di analytics come Application Insights).
  2. OnResuming() equivale all'omonimo metodo disponibile nella classe App tradizionale e viene invocato quando l'applicazione viene riattivata dopo una sospensione. Normalmente, non è necessario gestire tale scenario: questo metodo, infatti, viene scatenato solamente quando l'applicazione è stata riattivata dallo stato di sospensione; il processo, perciò, non essendo stato terminato ma mantenuto in memoria, non ha bisogno di recuperare l'eventuale stato salvato in precedenza. Questo metodo può tornare utile quando, ad esempio, vogliamo forzare l'aggiornamento dei dati dell'applicazione, in caso l'utente l'abbia riattivata dopo lungo tempo dall'ultimo utilizzo.
  3. OnSuspending() viene invocato quando l'applicazione viene sospesa. Tipicamente, questo metodo viene utilizzato per salvare lo stato dell'applicazione, così da poterlo ripristinare in caso di terminazione. In realtà, con Template10 questa operazione non è necessaria: ci pensa, infatti, in automatico il bootstrapper a gestire questo scenario. Nei prossimi post vedremo come sfruttare tale funzionalità.

Gestione avanzata della splash screen

La splash screen è un'immagine statica che viene mostrata all'utente durante la fase di inizializzazione dell'applicazione. Nel momento in cui questa è terminata, allora la splash screen viene nascosta e viene lanciata la navigazione verso la pagina principale dell'applicazione.

Come regola generale, la fase di inizializzazione dell'applicazione deve essere il più breve possibile: se, entro 10 secondi dalla comparsa della splash screen, l'applicazione non è ancora pronta per l'utilizzo questa sarà terminata dal sistema operativo. Può capitare, però, che possa essere necessario più tempo: pensiamo, ad esempio, ad un'applicazione che deve scaricare dei dati da Internet. Per evitare la terminazione da parte del sistema, è buona prassi non effettuare il caricamento dei dati in fase di inizializzazione all'interno della classe App, ma direttamente nella prima pagina dell'applicazione. In questo modo, l'inizializzazione sarà molto veloce e il controllo sarà subito passato alla pagina principale; dato che, però, ormai l'applicazione è stata avviata, il sistema operativo non tenterà più di terminarla, anche se poi all'interno della pagina ci volessero più di 10 secondi per caricare tutti i dati.

Questo approccio ha però lo svantaggio, a volte, di non offrire una user experience eccezionale: l'utente viene portato alla pagina principale che, però, per forza di cose, sarà completamente vuota e conterrà, al massimo, un messaggio di caricamento fino a che tutti i dati non sono pronti. Di conseguenza, alcune applicazioni adottano il concetto di extended splash screen: terminata l'inizializzazione base dell'applicazione, l'utente viene portato in un'altra pagina che assomiglia, in tutto e per tutto, alla splash screen. La differenza, in questo caso, è che non trattandosi di una semplice immagine statica possiamo includere degli elementi visivi per segnalare all'utente che l'operazione di caricamento è in corso, come una ProgressBar o un testo. Se vi interessa approfondire l'argomento, su MSDN trovate una descrizione su come implementare questa procedura, all'indirizzo https://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh868191.aspx

Come vedete, c'è un po' di lavoro da fare, oltre a creare la extended splash screen. Template10 semplifica notevolmente questo scenario, mettendo a disposizione una classe che si fa carico di gestire la transizione splash screen -> extended splash screen -> prima pagina dell'applicazione per voi. Vediamo come fare.

Il primo passo è creare la splash screen estesa, tipicamente definita tramite uno User Control. Fate click con il tasto destro sul vostro progetto, scegliete Add new item e selezionate il template UserControl. All'interno di questo controllo potete mettere gli elementi grafici che volete, senza limitazioni di sorta. Dato, però, che l'obiettivo è quello di dare all'utente la parvenza di stare ancora visualizzando la splash screen, tipicamente viene incluso un controllo Image che punta all'immagine scelta come splash screen statica. Il resto, poi, è a vostro piacimento. Nell'esempio seguente, l'immagine viene inserita all'interno di un Canvas, con lo stesso colore di sfondo dell'immagine. In più, viene inserito un controllo ProgressRing, per notificare l'utente che c'è un caricamento in corso.

<UserControl

x:Class="BasicSample.Views.SplashScreenView"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

mc:Ignorable="d">

<Grid>

<Canvas x:Name="MyCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#0971C3">

<Image x:Name="MyImage" Stretch="None" Source="/Assets/SplashScreen.png" />

</Canvas>

<ProgressRing VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="50" Height="50" IsActive="True" Foreground="White" Margin="0, 0, 0, 100"/>

</Grid>

</UserControl>

 

Nel code-behind sfruttiamo l'accesso alla classe SplashScreen (che da alcune informazioni sull'immagine, come la dimensione e la posizione) per impostare la nostra immagine allo stesso modo: in questo modo, l'utente non noterà la transizione dalla splash screen statica a quella estesa.

public sealed partial class SplashScreenView : UserControl

{

public SplashScreenView(SplashScreen splashScreen)

{

this.InitializeComponent();

Action resize = () =>

{

MyImage.Height = splashScreen.ImageLocation.Height;

MyImage.Width = splashScreen.ImageLocation.Width;

MyImage.SetValue(Canvas.TopProperty, splashScreen.ImageLocation.Top);

MyImage.SetValue(Canvas.LeftProperty, splashScreen.ImageLocation.Left);

};

Window.Current.SizeChanged += (s, e) => resize();

resize();

}

}

Il ridimensionamento e il posizionamento dell'immagine viene incluso all'interno di una Action che, oltre ad essere eseguita in fase di inizializzazione del controllo, viene legata all'evento di ridimensionamento della finestra (SizeChanged). Questo perché, in ambito desktop, l'utente potrebbe ridimensionare la finestra durante l'avvio dell'applicazione e, di conseguenza, la splash screen deve riadattarsi di conseguenza.

Ora che abbiamo definito la nostra splash screen personalizzata, per utilizzarla è sufficiente aggiungere la seguente riga di codice nel boostrapper:

public App()

{

InitializeComponent();

SplashFactory = e => new SplashScreenView(e);

}

SplashScreenView rappresenta il nome dello UserControl appena creato, mentre e è l'istanza della classe SplashScreen che viene passata al costruttore del controllo (quella che abbiamo usato per posizionare e ridimensionare l'immagine).

Il gioco è fatto: a questo punto, nel momento in cui l'applicazione dovesse impiegare più di qualche secondo per avviarsi, l'utente sarà portato alla nostra splash screen personalizzata e, solo al termine del caricamento dei dati, alla pagina principale dell'applicazione. Per testare questo scenario, è sufficiente includere all'interno del metodo OnStartAsync() un delay di qualche secondo prima di effettuare la navigazione verso la pagina principale, come nell'esempio seguente:

sealed partial class App : Template10.Common.BootStrapper

{

public App()

{

InitializeComponent();

SplashFactory = e => new SplashScreenView(e);

}

public override async Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)

{

await Task.Delay(TimeSpan.FromSeconds(5));

NavigationService.Navigate(typeof(MainPage));

}

}

Una volta terminata l'inizializzazione, l'applicazione avvierà il metodo OnStartAsync() e, fintanto che non sarà scatenata la navigazione con il metodo Navigate del NavigationService, sarà caricata la splash screen personalizzata. Dato che, in questo caso, abbiamo inserito un delay di 5 secondi, tale splash screen rimarrà a video per 5 secondi, dopodiché l'utente verrà portato alla pagina principale.

La gestione della navigazione

Un'altra funzionalità offerta da Template10 è la gestione automatica dello stack delle pagine. Se avete sviluppato, in passato, applicazioni per Windows e Windows Phone 8.1, saprete sicuramente che la gestione della navigazione è una delle differenze principali tra le due piattaforme. Se, su Windows Phone, non è necessario includere alcun elemento visuale per portare l'utente alla pagina precedente, dato che gli smartphone sono già dotati di un pulsante Back hardware, lo stesso non si può dire di Windows.

Le cose con Windows 10 sono cambiate: anche la versione desktop / tablet, infatti, offre una gestione integrata del pulsante Back, sollevando gli sviluppatori (se lo ritengono opportuno) dal doverlo gestire manualmente. Nello specifico, quando questa funzionalità viene abilitata, gli utenti avranno la possibilità:

  • In caso di utilizzo dell'app in modalità desktop, il pulsante Back virtuale sarà incluso nella cornice della finestra in alto a destra.
  • In caso di utilizzo dell'app in modalità tablet, il pulsante Back virtuale sarà incluso nella barra di sistema, tra il pulsante Start e a Cortana.

 

 

 

 

 

 

 

Normalmente, in un'applicazione tradizionale, l'abilitazione e la gestione di questa funzionalità è a carico dello sviluppatore (allo stesso modo in cui, in Windows Phone 8.1, dovevamo prevedere un handler dell'evento BackPressed della classe HardwareButtons). Con Template10, invece, come comportamento predefinito, questa gestione è abilitata in automatico. Di conseguenza:

  • Quando l'applicazione viene eseguita su desktop e, nello stack delle pagine, sono presenti pagine precedenti a quella corrente, in alto a destra sarà abilitato il pulsante virtuale. Se, invece, lo stack è vuoto (ad esempio, perché ci si trova nella prima pagina), il pulsante sarà automaticamente nascosto.
  • Quando l'applicazione viene eseguita in ambito mobile, la pressione del pulsante Back hardware condurrà l'utente alla pagina precedente o, in alternativa, se lo stack è vuoto, alla Start screen del telefono.

Se vogliamo disabilitare questo comportamento perché vogliamo gestire la navigazione in maniera autonoma (ad esempio, includendo un pulsante direttamente nell'interfaccia grafica) possiamo impostare la proprietà ShowShellButton a false nel costruttore del bootstrapper, come nell'esempio seguente:

public App()

{

InitializeComponent();

ShowShellBackButton = false;

}

Con questa opzione vi accorgerete di come, quando l'applicazione viene utilizzata su desktop, il pulsante Back nella finestra non comparirà più, anche se vi trovate in una pagina "interna".

Nella prossima puntata J

Nel corso di questo post abbiamo visto alcuni concetti base di Template10 e quali sono le funzionalità che possiamo iniziare a sfruttare per rendere più semplice lo sviluppo di una Universal Windows app. Abbiamo, però solamente "grattato" la superficie e, nel corso dei prossimi post, scopriremo le vere potenzialità di Template10, soprattutto in ottica di utilizzo del pattern MVVM. Se, nel frattempo, volete iniziare a "giocare" con Template10, potete sfruttare gli esempi già pronti contenuti all'interno del repository su GitHub. Happy coding!