Questo post è stato scritto da Matteo Pagani, Support Engineer in Microsoft per il programma AppConsult
Nel post precedente abbiamo iniziato a prendere famigliarità con Template10, il nuovo template open source realizzato da un team in Microsoft, che ha l'obiettivo di diventare il punto di partenza ideale per lo sviluppo di Universal Windows app.
Nel corso di questo post andremo a vedere alcune delle funzionalità che la libreria mette a disposizione sotto forma di nuovi controlli.
Il controllo PageHeader
Una delle novità principali, in ottica di interfaccia utente, nelle Universal Windows app di Windows 10 è la possibilità di posizionare la application bar non solo in basso, ma anche in cima. In Windows 8.1 si aveva la possibilità di includere una application bar superiore, ma solo per gestire la navigazione tra le varie sezioni dell'applicazione. I comandi dovevano essere necessariamente inclusi nella application bar inferiore, sfruttando un controllo chiamato CommandBar.
In Windows 10, invece, il controllo CommandBar può essere posizionato anche nella parte superiore dello schermo e includere dei pulsanti con cui l'utente può interagire. Il controllo PageHeader, incluso in Template10, nasce come estensione della CommandBar e permette di trasformarla in un vero e proprio header da usare come intestazione per le nostre pagine. Grazie a questo controllo, sarà più semplice ricreare il look & feel adottato da alcune applicazioni native come News o Money, che includono in tutte le pagine un header con il titolo della sezione e dei pulsanti per gestire la navigazione o l'interazione con i contenuti.
Il primo passo per utilizzare il controllo PageHeader è dichiarare nella pagina XAML il namespace a cui appartiene, ovvero Template10.Controls, come nell'esempio seguente:
<Page
x:Class="Controls.Views.MainPage"
xmlns:controls="using:Template10.Controls"
mc:Ignorable="d">
</Page>
Dopodiché, potete includere il controllo nella vostra pagina con il seguente codice:
<controls:PageHeader Frame="{x:Bind Frame}" />
Una delle proprietà chiave, che potete notare nell'esempio, è Frame. Come vedremo a breve, una delle principali caratteristiche del controllo è la sua capacità di gestire in automatico la navigazione tra le pagine dell'applicazione. Affinché questa feature funzioni correttamente, però, è indispensabile che il frame che contiene le pagine sia collegato al controllo: lo facciamo sfruttando la nuova markup extension x:Bind, che ci permette di accedere agli oggetti esposti nel code behind.
Personalizzare l'aspetto
La proprietà chiave per personalizzare l'aspetto del controllo PageHeader è Text, che permette di definire il testo da mostrare all'interno dell'header (tipicamente, il titolo della pagina). Eventualmente, è possibile inoltre cambiare il colore predefinito che vengono assegnati al testo e allo sfondo, tramite le proprietà HeaderForeground e HeaderBackground. Ecco un esempio di personalizzazione, in cui viene impostato il testo di colore rosso su uno sfondo arancione:
<controls:PageHeader Text="Main Page" Frame="{x:Bind Frame}" HeaderBackground="Orange" HeaderForeground="Red" />
Gestire la navigazione
Il controllo PageHeader include un pulsante per gestire la navigazione verso la pagina precedente, che è controllato da una proprietà di nome BackButtonVisibility. Quando questa proprietà viene impostata a Visible, il pulsante viene mostrato alla sinistra del testo che funge da header.
<controls:PageHeader Text="Detail" Frame="{x:Bind Frame}" BackButtonVisibility="Visible" />
È importante, però, sottolineare come la visibilità di tale pulsante non venga controllata solamente da tale proprietà, ma anche da altri fattori legati al sistema operativo e alla piattaforma su cui sta girando l'applicazione.
Nello specifico:
- Il controllo, tramite la proprietà Frame, è legato al frame di navigazione: di conseguenza, è in grado di determinare in automatico se ci sono pagine nello stack. Ciò significa che, ad esempio, il pulsante non sarà mai mostrato nella prima pagina dell'applicazione, perché lo stack sarà sempre vuoto.
- Le varie tipologie di device su cui gira Windows 10 hanno regole differenti per quanto riguarda la gestione del pulsante Back. Se, su desktop, abbiamo diverse possibilità (sfruttare il pulsante virtuale presente nella finestra, includerlo nella UI dell'applicazione, ecc.), su mobile invece le guideline prevedono l'uso del tasto Back fisico presente in tutti gli smartphone. Di conseguenza, ad esempio, se impostate la proprietà BackButtonVisibility a Visible ma la vostra applicazione è in esecuzione su un device mobile, il pulsante non sarà comunque mai visibile.
- Su desktop, come abbiamo visto nel post precedente, abbiamo la possibilità di sfruttare un pulsante Back virtuale, presente nella finestra dell'applicazione, per gestire la navigazione alle pagine precedenti. In tal caso, aggiungere troppi elementi nell'interfaccia utente per raggiungere lo stesso scopo potrebbe essere confusionario. Come comportamento predefinito, perciò, se la proprietà ShowShellBackButton è impostata a true, il pulsante sarà automaticamente disabilitato.
I comandi
Come anticipato all'inizio del post, PageHeader estende il controllo CommandBar, che viene utilizzato per aggiungere una application bar all'interno dell'applicazione, all'interno della quale poter aggiungere uno o più pulsanti che consentono all'utente di interagire con il contenuto corrente.
Di conseguenza, le stesse funzionalità offerte dal controllo CommandBar si possono riutilizzare anche all'interno di PageHeader, con lo stesso principio:
- All'interno della collezione PrimaryCommands è possibile aggiungere uno o più pulsanti, i quali saranno sempre visibili con la relativa icona e testo descrittivo.
- All'interno della collezione SecondaryCommands è possibile aggiungere uno o più pulsanti che, invece, saranno nascosti: l'utente potrà vederli premendo sui tre puntini mostrati al termine della barra. In tal caso, i pulsanti saranno solo testuali.
All'interno delle collezioni non si può includere qualsiasi controllo XAML, ma solo un sottoinsieme di elementi progettati ad hoc per l'application bar. Il controllo più utilizzato è AppBarButton, che rappresenta un pulsante che l'utente può premere; è caratterizzato da un'immagine (la proprietà Icon) e da una descrizione (la proprietà Label) e ci si può sottoscrivere all'evento Click per gestire l'interazione con l'utente.
Ecco un esempio di codice che definisce un comando primario e due secondari:
<controls:PageHeader Text="Main Page" Frame="{x:Bind Frame}">
<controls:PageHeader.PrimaryCommands>
<AppBarButton Icon="Forward" Label="Next" Click="OnNextClicked" />
</controls:PageHeader.PrimaryCommands>
<controls:PageHeader.SecondaryCommands>
<AppBarButton Label="Option 1" />
<AppBarButton Label="Option 2" />
</controls:PageHeader.SecondaryCommands>
</controls:PageHeader>
E' importante valutare bene l'ingombro dei pulsanti quando testate la vostra applicazione su differenti categorie di device. Ad esempio, l'utilizzo di numerosi comandi primari non costituisce un grosso problema su desktop: le maggiori dimensioni dello schermo e l'orientamento in landscape fanno sì che ci sia molto spazio a disposizione. Lo stesso, invece, non si può dire su uno smartphone: di conseguenza, in questo caso, è meglio ridurre al minimo i comandi primari e sfruttare maggiormente quelli secondari.
L'immagine seguente mostra come la stessa soluzione, che include tre comandi primari, si adatti perfettamente al desktop, ma crei problemi di layout in ambito mobile (a causa dello spazio ridotto, i pulsanti vanno a sovrapporsi al testo).
Il controllo HamburgerMenu
Uno delle novità dal punto di vista visuale di Windows 10 è il controllo SplitView, che permette di implementare il concetto di hamburger menu nelle vostre applicazioni. Tale approccio consiste in un menu laterale (tipicamente, viene posizionato a sinistra della pagina) che è possibile espandere premendo un pulsante in cima al pannello (solitamente, nell'angolo superiore sinistro). Il pannello, al suo interno, contiene diverse voci, che consentono di accedere alle varie sezioni dell'applicazione.
Diverse applicazioni native sfruttano questo approccio: News, che include nel pannello le varie sezioni di cui è composta l'applicazione; Mail, che sfrutta il menu per mostrare i vari account di posta con le relative cartelle, ecc.
Il controllo SplitView non nasce per "forzare" lo sviluppo esclusivamente di applicazioni basate sull'hamburger menu per gestire la navigazione, ma si affianca a quelli già disponibili e ancora utilizzabili, come Hub o Pivot. Di conseguenza, lo SplitView è estremamente flessibile e lascia la massima libertà allo sviluppatore di utilizzarlo come meglio preferisce. Si limita, infatti, a suddividere la pagina in due blocchi: un pannello a scomparsa e una sezione fissa, in cui inserire il contenuto. All'interno del pannello abbiamo la possibilità di inserire qualsiasi controllo XAML; di conseguenza, non deve necessariamente usato per la navigazione tra le varie sezioni.
Il rovescio della medaglia di questa flessibilità è che se vogliamo fornire all'utente un'esperienza basata sull'hamburger menu tradizionale (sulla falsa riga delle applicazioni native), abbiamo parecchio da lavoro da fare.
Template10 semplifica il lavoro dello sviluppatore in diversi modi:
- Offrendo un controllo di nome HamburgerMenu, che semplifica la definizione del pannello che andrà a contenere le sezioni dell'applicazione.
- Offrendo la possibilità di definire una shell, ovvero una pagina che funge da contenitore dell'applicazione. Sarà la shell a contenere il menu mentre, all'interno della sezione principale, saranno caricate le varie pagine dell'applicazione.
- Offrendo una serie di stili che permettono di ricreare un pannello con il look & feel delle applicazioni native (highlight della pagina correntemente caricata, supporto alle icone, ecc.)
In questo modo, saremo in grado di ottenere un risultato simile al seguente:
Vediamo i passi necessari per raggiungerlo.
Il controllo HamburgerMenu
Così come il controllo PageHeader, il controllo HamburgerMenu è contenuto all'interno del namespace Template10.Controls, che è quindi necessario aggiungere all'intestazione della pagina XAML.
A questo punto possiamo inserirlo all'interno della nostra pagina:
<controls:HamburgerMenu x:Name="Menu" />
La personalizzazione del controllo, dal punto di vista visuale, passa tramite diverse proprietà:
- HamburgerBackground e HamburgerForeground, che definiscono il colore di sfondo e il colore del testo utilizzati per il pulsante che nasconde / mostra il pannello..
- NavButtonBackground e NavButtonForeground, che definiscono il colore di sfondo e il colore del testo utilizzati per i pulsanti di navigazione.
- NavAreaBackground, che definisce il colore di sfondo del pannello.
Una peculiarità del controllo HamburgerMenu, rispetto allo SplitView standard, è il supporto a due tipologie di comandi:
- PrimaryButtons rappresentano i comandi principali utilizzati per la navigazione tra le varie sezioni e vengono posizionati in cima al pannello, subito sotto il pulsante per mostrarlo / nasconderlo.
- SecondaryButtons rappresentano i comandi che, per la nostra applicazione, sono secondari o che sono collegati a sezioni che l'utente probabilmente visiterà meno frequentemente (come le Impostazioni). Tali comandi saranno mostrati in fondo al pannello, con un separatore all'inizio.
All'interno di ogni collezione possiamo andare a specificare uno o più comandi sfruttando un altro controllo offerto da Template10, chiamato NavigationButtonInfo. Si tratta, fondamentalmente, di una variante del controllo RadioButton: è il più adatto per il nostro scenario, in quanto implementa in automatico le caratteristiche necessarie (highlight della sezione corrente, scelta esclusiva tra un gruppo di pulsanti, ecc.)
Ecco un esempio completo di definizione di un controllo HamburgerMenu:
<controls:HamburgerMenu x:Name="Menu"
HamburgerBackground="#FFD13438"
HamburgerForeground="White"
NavAreaBackground="#FF2B2B2B"
NavButtonBackground="#FFD13438"
NavButtonForeground="White">
<controls:HamburgerMenu.PrimaryButtons>
<controls:NavigationButtonInfo PageType="views:MainPage" ClearHistory="True">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Home" Width="48" Height="48" />
<TextBlock Text="Home" Margin="12, 0, 0, 0" />
</StackPanel>
</controls:NavigationButtonInfo>
<controls:NavigationButtonInfo PageType="views:DetailPage">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Calendar" Width="48" Height="48" />
<TextBlock Text="Calendar" Margin="12, 0, 0, 0" />
</StackPanel>
</controls:NavigationButtonInfo>
</controls:HamburgerMenu.PrimaryButtons>
<controls:HamburgerMenu.SecondaryButtons>
<controls:NavigationButtonInfo PageType="views:SettingsPage">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Setting" Width="48" Height="48" />
<TextBlock Text="Settings" Margin="12, 0, 0, 0" />
</StackPanel>
</controls:NavigationButtonInfo>
</controls:HamburgerMenu.SecondaryButtons>
</controls:HamburgerMenu>
Tale esempio include la definizione di due comandi primari e uno secondario. Ogni comando è rappresentato da un controllo NavigationButtonInfo, che ha una proprietà fondamentale chiamata PageType e serve a specificare la pagina a cui vogliamo portare l'utente quando ci farà tap sopra. Grazie a questa proprietà il controllo sarà in grado di:
- Lanciare in automatico la navigazione verso la pagina desiderata, senza necessità di sottoscrivere l'evento Click del pulsante e gestirla da codice.
- Applicare lo stile di highlight nel momento in cui la pagina correntemente visualizzata corrisponda a quella indicata dal pulsante.
Il controllo offre inoltre altre due proprietà per personalizzare la navigazione:
- ClearHistory che, quando viene impostata a true, forza la pulizia dello stack di navigazione quando si visita la pagina collegata. Tipicamente viene utilizzata in combinazione con la pagina principale, per evitare che si creino delle navigazioni di tipo circolare.
- PageParameter, per specificare eventuali parametri che devono essere passati alla pagina collegata (e, di conseguenza, recuperati tramite l'evento OnNavigatedTo()).
L'aspetto visuale del comando è a discrezione dello sviluppatore: è sufficiente definire, all'interno del controllo NavigationButtonInfo, lo XAML che vogliamo usare per rappresentarne il layout. Il codice che vedete nell'esempio permette di ricreare il look & feel delle applicazioni native: un'icona, definita con il controllo SymbolIcon (che viene visualizzata anche quando il menu viene mostrato in modalità minimale) e un testo descrittivo, definita con un semplice TextBlock.
Ecco come viene tradotto il codice precedente e declinato nei due stati possibili: pannello aperto e chiuso.
Affinché funzioni correttamente la gestione della navigazione, il controllo HamburgerMenu ha bisogno un riferimento al NavigationService, ovvero il servizio messo a disposizione da Template10 per gestire lo stack delle pagine. Tale riferimento viene passato, nel code-behind, alla proprietà NavigationService esposta dal controllo, come nell'esempio seguente:
public sealed partial class Shell : Page
{
public Shell(NavigationService navigationService)
{
this.InitializeComponent();
Menu.NavigationService = navigationService;
}
}
Come poter gestire il passaggio del NavigationService alla pagina che contiene il controllo HamburgerMenu? Lo vedremo nel prossimo paragrafo, dedicato alla shell.
Creare la shell
Abbiamo visto come, grazie a Template10, sia semplice definire una navigazione basata sull'hamburger menu in un'applicazione. Il problema, però, è che l'HamburgerMenu è un semplice controllo e, di conseguenza, va inserito in una pagina. Come gestire il fatto che, in realtà, tutte le pagine della nostra applicazione dovranno avere lo stesso menu?
Introduciamo perciò il concetto di shell, ovvero una pagina dell'applicazione che fungerà da contenitore per le varie pagine che compongono l'applicazione. La peculiarità è che il controllo HamburgerMenu sarà definito proprio all'interno della shell; di conseguenza, non dovremo ridefinirlo in tutte le pagine, ma potremo limitarci a definire nei vari file XAML il contenuto che vogliamo mostrare.
Il primo passo, perciò, è di creare una pagina vuota all'interno del nostro progetto e di riprendere i passaggi che abbiamo visto in precedenza, ovvero includere il controllo HamburgerMenu e collegare la proprietà NavigationService, come nell'esempio seguente:
<Page
x:Class="HamburgerSample.Views.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="using:HamburgerSample.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:Template10.Controls"
x:Name="ThisPage"
mc:Ignorable="d">
<controls:HamburgerMenu x:Name="Menu">
<controls:HamburgerMenu.PrimaryButtons>
<controls:NavigationButtonInfo PageType="views:MainPage" ClearHistory="True">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Home" Width="48" Height="48" />
<TextBlock Text="Home" Margin="12, 0, 0, 0" />
</StackPanel>
</controls:NavigationButtonInfo>
<controls:NavigationButtonInfo PageType="views:DetailPage" >
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Calendar" Width="48" Height="48" />
<TextBlock Text="Calendar" Margin="12, 0, 0, 0" />
</StackPanel>
</controls:NavigationButtonInfo>
</controls:HamburgerMenu.PrimaryButtons>
<controls:HamburgerMenu.SecondaryButtons>
<controls:NavigationButtonInfo PageType="views:SettingsPage">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Setting" Width="48" Height="48" />
<TextBlock Text="Settings" Margin="12, 0, 0, 0" />
</StackPanel>
</controls:NavigationButtonInfo>
</controls:HamburgerMenu.SecondaryButtons>
</controls:HamburgerMenu>
</Page>
public sealed partial class Shell : Page
{
public Shell(NavigationService navigationService)
{
this.InitializeComponent();
Menu.NavigationService = navigationService;
}
}
Ora abbiamo bisogno di:
- Definire la pagina appena creata come contenitore delle pagine della nostra applicazione, al posto del Frame tradizionale.
- Passare alla pagina un riferimento al NavigationService, così che possa essere passato al controllo HamburgerMenu
Entrambe le operazioni vengono effettuate nel bootstrapper; nello specifico, andremo a sfruttare il metodo InitializeAsync(), che viene chiamato prima della navigazione vera e propria verso la pagina principale.
sealed partial class App : BootStrapper
{
public App()
{
this.InitializeComponent();
}
public override Task OnInitializeAsync(IActivatedEventArgs args)
{
var nav = NavigationServiceFactory(BackButton.Attach, ExistingContent.Include);
Window.Current.Content = new Views.Shell(nav);
return Task.FromResult<object>(null);
}
public override Task OnStartAsync(BootStrapper.StartKind startKind, IActivatedEventArgs args)
{
NavigationService.Navigate(typeof(Views.MainPage));
return Task.FromResult<object>(null);
}
}
Innanzitutto utilizziamo il metodo offerto dal bootrapper NavigationServiceFactory per recuperare un riferimento al NavigationService. In questo modo, possiamo passarlo come parametro nel momento in cui creiamo una nuova istanza della pagina Shell. Dopodiché, associamo questa istanza alla proprietà Window.Current.Content, che specifica qual è il frame base dell'applicazione che fungerà da contenitore delle pagine. In un'applicazione tradizionale, tale proprietà viene valorizzata semplicemente con una nuova istanza della classe Frame.
Questa riga di codice fa sì che il contenitore delle nostre pagine non sia più un frame vuoto, ma la pagina che contiene il nostro HamburgerMenu. Il risultato sarà che tutte le pagine della nostra applicazione condivideranno il menu che abbiamo definito nella pagina denominata Shell.
HamburgerMenu e PageHeader: un'accoppiata vincente
I due controlli HamburgerMenu e PageHeader sono stati progettati proprio per essere utilizzati insieme. Il miglior risultato visivo, infatti, si ottiene quando le varie pagine della nostra applicazione che sfrutta l'hamburger menu utilizzano proprio il controllo PageHeader per definirne l'intestazione.
I due controlli, inoltre, hanno un comportamento pensato proprio per l'utilizzo combinato in ottica di adaptive layout. Come probabilmente saprete, una delle novità principali di Windows 10 è il fatto che le Universal Windows app sono costituite da un singolo binario che gira su tutte le piattaforme (al contrario di 8.1, dove si andavano a produrre due pacchetti differenti, da pubblicare su due store differenti). Qui entra in gioco il concetto di adaptive layout, ovvero l'interfaccia utente deve essere in grado di reagire ai cambiamenti nelle dimensioni della finestra per poter offrire sempre la migliore user experience possibile, indipendentemente che l'applicazione venga usata sullo schermo di un telefono o su una televisione.
A tale scopo, una delle principali novità introdotte dalla Universal Windows Platform sono i Visual State Trigger, ovvero dei trigger che consentono ad un controllo XAML o ad una pagina il passaggio in automatico da un visual state all'altro quando si verificano determinate condizioni. Nello specifico, la UWP mette a disposizione gli AdaptiveTrigger, che consentono il cambio di visual state in base alle dimensioni della finestra.
Sia il controllo HamburgerMenu che PageHeader sfruttano proprio questo meccanismo per ottimizzare l'esperienza d'uso in base alla dimensione dello schermo:
- In condizioni normali, il testo che funge da intestazione nel controllo PageHeader non ha alcun margine rispetto al bordo. Questo perché, quando la finestra è sufficientemente larga, il pannello del controllo HamburgerMenu sarà sempre visibile in modalità minimale (ovvero con le icone, ma senza la descrizione testuale). Di conseguenza, ci pensa già il pannello a creare il margine necessario per evitare che il pulsante di comparsa / scomparsa si sovrappongo all'intestazione.
- Quando le dimensioni della finestra sono inferiori (ad esempio, sullo schermo di uno smartphone) non c'è sufficiente spazio per mantenere visibile il pannello in modalità minimale. In tal caso, il pannello viene completamente nascosto e rimane visibile solamente il pulsante per mostrarlo / nasconderlo. In questo scenario il controllo PageHeader aggiunge un margine rispetto al bordo: dato che la colonna con le icone non è più presente, senza questo accorgimento il testo dell'intestazione andrebbe a sovrapporsi al pulsante.
Questo comportamento viene applicato in maniera predefinita tramite alcuni visual state trigger definiti nei due controlli. Avete, però, la possibilità di decidere a quale dimensione della finestra far scattare un comportamento piuttosto che l'altro, tramite le proprietà VisualStateNarrowMinWidth e VisualStateNormalMinWidth. Ecco un esempio:
<controls:PageHeader Text="Main page" Frame="{x:Bind Frame}"
VisualStateNarrowMinWidth="0"
VisualStateNormalMinWidth="700" />
Tale codice significa che:
- Quando la dimensione della finestra è compresa tra 0 e 700, viene applicato il visual state chiamato VisualStateNarrow, ovvero quello minimale in cui il pannello è nascosto (adatto, ad esempio, per gli smartphone) e, di conseguenza, il testo deve essere spostato.
- Quando la dimensione della finestra è superiore a 700, invece, viene applicato il visual state chiamato VisualStateNormal, che è quello tradizionale in cui il pannello è sempre visibile e non richiede uno spostamento del test.
Tali proprietà sono disponibili anche per il controllo HamburgerMenu: ovviamente, per ottenere il risultato migliore, è fondamentale che i valori di questa proprietà coincidano con quelli che sono stati utilizzati per i vari controlli PageHeader presenti nelle pagine.
Le due immagini seguenti vi aiuteranno meglio a capire la differenza: la prima mostra l'applicazione quando è attivo il visual state normal, il secondo invece quando è attivo quello narrow.
Se volete disabilitare questo comportamento del controllo PageHeader (ad esempio, perché la vostra applicazione non fa uso di un hambuger menu e, di conseguenza, lo spostamento del testo causerebbe un problema di layout) è sufficiente impostare la proprietà VisualStateNarrowMinWidth a -1.
In conclusione
Nel corso di questo post abbiamo esaminato i dettagli come alcuni dei controlli personalizzati offerti da Template10 possano semplificare notevolmente il nostro lavoro nella definizione del design della nostra applicazione. Nel prossimo post, invece, ci sposteremo sul versante architetturale e vedremo come Template10 ci aiuterà nell'implementazione del pattern Model-View-ViewModel.
Vi ricordo che il repository su GitHub, disponibile all'indirizzo http://github.com/Windows-XAML/Template10/, contiene anche degli esempi di codice con cui potete provare in prima persona quanto descritto nel corso di questo post.
read full article