Guest post: Il nuovo binding di Windows 10
Questo post è stato scritto da Matteo Pagani, Support Engineer in Microsoft per il programma AppConsult
Una delle novità principali della Universal Windows Platform di Windows 10 è l’introduzione di una serie di nuove markup expression, che faranno la gioia di tutti gli sviluppatori che si affidano alle potenzialità dello XAML nello sviluppo delle loro applicazioni.
Tali markup expression permettono, infatti, di ottimizzare notevolmente le performance nel rendering dello XAML e di rendere l’interfaccia utente delle nostre applicazioni ancora più fluida e reattiva.
Vediamole in dettaglio.
x:Bind – Un nuovo modo per gestire il binding
Il binding è sicuramente una delle funzionalità più potenti dello XAML, la quale permette di creare un canale di comunicazione (anche bidirezionale) tra i controlli presenti nell’interfaccia utente e gli oggetti presenti nel nostro codice. In questo modo, grazie alle dependency property e all’interfaccia INotifyPropertyChanged, non dobbiamo preoccuparci di aggiornare manualmente l’interfaccia grafica: ogni qualvolta andremo a modificare gli oggetti nel nostro codice, i controlli ad essi collegati si aggiorneranno di conseguenza.
Ecco un esempio di binding utilizzando la sintassi standard:
<TextBlock Text="{Binding Path=Message}" />
Questo codice di esempio mostra la proprietà Text di un controllo TextBlock collegata ad una proprietà di nome Message definita nel nostro codice e, più precisamente, nella classe che è definita come DataContext della nostra pagina.
Questo tipo di binding, però, ha due svantaggi:
- Performance: il binding, dietro le quinte, sfrutta il meccanismo della reflection. Questo ha un costo a livello di performance, dato che ogni espressione viene valutata a runtime.
- Gestione degli errori:dato che il binding viene risolto a runtime, eventuali errori non vengono evidenziati in fase di compilazione. Nell’esempio precedente, se la proprietà di nome Message non esistesse, ce ne accorgeremmo solo eseguendo l’applicazione, in quanto il controllo TextBlock sarebbe vuoto.
Windows 10 introduce una nuova espressione di binding che risolve entrambi i problemi, dato che è in grado di generare codice fortemente tipizzato in fase di compilazione. In questo modo, si otterrà sia un guadagno di performance (dato che il binding non deve essere valutato a runtime), sia una migliore gestione degli errori (dato che eventuali problemi verranno evidenziati immediatamente in fase di compilazione).
La nuova markup expression si chiama x:Bind e va a sostituire la parola chiave Binding, come nell’esempio seguente:
<TextBlock Text="{x:Bind Path=Message}" />
C’è però una importante considerazione da fare: dato che l’espressione di binding viene a tutti gli effetti compilata, la proprietà non viene più cercata all’interno del DataContext, ma direttamente all’interno del code behind. Se volete approfondire l’argomento, vi consiglio la lettura di questo post a cura di Nick Randolph, MVP Microsoft, che tratta in maniera dettagliata il funzionamento, dietro le quinte, di questa nuova espressione.
Di conseguenza, lo XAML dell’esempio precedente, per funzionare correttamente, richiede la definizione di una proprietà di nome Message all’interno del code - behind della pagina, come nell’esempio seguente:
public sealed partial class StandardBinding : Page
{
public StandardBinding()
{
this.InitializeComponent();
Message = "Hello from the new x:Bind expression!";
}
publicstring Message { get; set; }
}
Un’altra importante differenza rispetto al binding tradizionale è che, come impostazione predefinita, viene applicata la modalità di aggiornamento OneTime: questo significa che, una volta effettuato il binding, eventuali modifiche alla proprietà non saranno più recepite dal controllo. Se abbiamo bisogno di mantenere aggiornata la proprietà nel corso dell’esecuzione dell’applicazione, dovremo perciò ricordarci di impostare manualmente la modalità OneWay o TwoWay (in caso di comunicazione bidirezionale), come nell’esempio seguente:
<TextBlock Text="{x:Bind Path=Message, Mode=OneWay }" />
x:Bind ed eventi
Una importante novità della nuova markup expression è che consente il binding non solo di proprietà, ma anche di eventi. Ciò significa che, ad esempio, possiamo gestire la pressione di un pulsante con il seguente codice XAML:
<Button Content="Show message" Click="{x:Bind OnShowMessageClicked}" />
Nel code behind, sarà sufficiente dichiarare un evento con il nome specificato nell’espressione di binding per gestire l’evento Click, come nell’esempio seguente :
public sealed partial class EventsBinding : Page
{
public EventsBinding()
{
this.InitializeComponent();
}
public async void OnShowMessageClicked()
{
MessageDialog dialog = new MessageDialog("Hello world!");
await dialog.ShowAsync();
}
}
Questa nuova funzionalità risulta particolarmente utile quando si lavora con il pattern Model-View-ViewModel (MVVM): siamo abituati, infatti, ad usare la proprietà Command per gestire gli eventi all’interno di un ViewModel, ovvero una classe separata da quella di code behind. Tale proprietà, però, è legata solamente all’interazione primaria offerta dal controllo (ad esempio, nel caso del controllo Button, la pressione del pulsante). Se abbiamo la necessità di gestire altre interazioni (ad esempio, il drag and drop o il doppio tap) dobbiamo ricorrere ad altre strategie (come l’invio di messaggi). Questa nuova funzionalità offerta dalla markup expression x:Bind ci permette di risolvere questo problema in maniera ottimale.
x:Bind e Model-View-ViewModel
Gli sviluppatori abituati a lavorare con il pattern MVVM saranno sicuramente trasaliti leggendo il paragrafo precedente: dato che lo scopo del pattern è quello di favorire la separazione tra logica e UI dell’applicazione, quando si utilizza questo approccio tutte le interazioni con l’interfaccia grafica vengono gestite in una classe separata (chiamata ViewModel) e non nel code-behind, come invece richiesto dall’espressione x:Bind.
Fortunatamente, esiste una semplice soluzione: esporre il ViewModel associato alla pagina corrente come proprietà nel code-behind. Ipotizziamo di avere una pagina, il cui DataContext è associato ad uno specifico ViewModel, come nell’esempio seguente:
<Page
x:Class="NewBindingSample.Views.MvvmBinding"
DataContext="{Binding Source={StaticResource MainViewModel}}">
</Page>
Come anticipato, tramite l’espressione x:Bind non sareste in grado di accedere alle proprietà esposte dalla classe di tipo MainViewModel, perché è una classe differente da quella di code behind. La soluzione è, nel code behind della pagina, trasformare il DataContext in una proprietà pubblica della classe, come nell’esempio seguente:
public sealed partial class MvvmBinding : Page
{
public MainViewModel ViewModel { get; set; }
public MvvmBinding()
{
this.InitializeComponent();
ViewModel = this.DataContext as MainViewModel;
}
}
A questo punto, avrete la possibilità di sfruttare la proprietà di nome ViewModel per gestire il binding con la nuova markup expression e di accedere a tutte le proprietà esposte dal ViewModel, come nell’esempio seguente:
<TextBlock Text="{x:Bind Path=ViewModel.Message}"/>
x:Bind e i DataTemplate
Anche se non fate uso del pattern MVVM, c’è uno scenario in cui l’uso del binding è praticamente indispensabile: i DataTemplate. Quando dovete mostrare, ad esempio, una collezione di elementi usando un controllo ListView o GridView, dovete associare un DataTemplate alla proprietà ItemTemplate: tale template rappresenta lo XAML che sarà utilizzato per definire, a livello visuale, ogni singolo elemento della lista.
Il meccanismo con cui possiamo collegare il template con le proprietà dell’oggetto che vogliamo rappresentare è proprio il binding. Anche in questo caso possiamo affidarci alla nuova markup expression x:Bind. L’unica differenza è che, in fase di definizione del template, dobbiamo specificare qual è la classe a cui si riferisce, usando l’attributo x:DataType.
Ipotizziamo di avere una classe, come la seguente, che rappresenti una persona:
public class Person
{
publicstring Name { get; set; }
publicstring Surname { get; set; }
}
Ecco come possiamo rappresentare, usando la markup expression x:Bind, una collezione di oggetti di tipo Person con un controllo ListView:
<Page
x:Class="NewBindingSample.MainPage"
xmlns:model="using:NewBindingSample.Model">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="PeopleList">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Person">
<StackPanel>
<TextBlock Text="{x:Bind Path=Name}"/>
<TextBlock Text="{x:Bind Path=Surname}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
Potete notare come, tra i namespace della pagina XAML, ne sia stato aggiunto uno nuovo di nome model, che punta al namespace della classe Person . Dopodichè, nella definizione del DataTemplate usato per la lista, abbiamo specificato l’attributo x:DataType, indicando che tale template sarà utilizzato per rappresentare un oggetto di tipo Person.
x:Bind e i Resource Dictionary
Quando si lavora con un’applicazione complessa, ricca di stili e template, risultano molto utili i Resource Dictionary, ovvero dei file XAML contenuti nel progetto in cui è possibile dichiarare le risorse che si vogliono utilizzare all’interno dell’applicazione. In questo modo, si evita di appesantire e di rendere poco leggibile il codice XAML delle pagine vere e proprie.
Se però, provaste ad usare le nuove markup expression all’interno di un normale Resource Dictionary (creato utilizzando l’omonimo template in Visual Studio) otterrete una serie di errori difficili da decifrare. Il motivo è che, come abbiamo imparato nei paragrafi precedenti, la markup expression x:Bind è legata al code behind, all’interno del quale il compilatore va a cercare le risorse per generare il codice necessario. Un Resource Dictionary, invece, è un file XAML sprovvisto di code behind, proprio perché non contiene alcuna logica.
La soluzione è quella di creare un Resource Dictionary che, analogamente ad una pagina, abbia una corrispettiva classe di code behind. Purtroppo, però, Visual Studio non offre, al momento, un template specifico per questo scenario. Il workaround è quello di creare una nuova pagina e poi modificarla per comportarsi come un Resource Dictionary. Vediamo come fare.
Il primo passo è quello di aggiungere un nuovo elemento al proprio progetto: nella lista dei template disponibili, scegliamo Blank page. A questo punto, dobbiamo cambiare il tipo base del file da Page a ResourceDictionary, come nell’esempio :
<ResourceDictionary
x:Class="NewBindingSample.Styles.Templates"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:local="using:NewBindingSample.Styles"
xmlns:d=http://schemas.microsoft.com/expression/blend/2008
xmlns:model="using:NewBindingSample.Model"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<DataTemplate x:DataType="model:Person" x:Key="PeopleTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind Path=Name}" Margin="0, 0, 5, 0" />
<TextBlock Text="{x:Bind Path=Surname}"/>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
Il secondo passaggio è quello di cambiare la classe base nel code behind: come impostazione predefinita, infatti, le pagine ereditano dalla classe Page, mentre nel nostro caso deve ereditare dalla classe ResourceDictionary, come nell’esempio seguente .
public sealed partial class Templates : ResourceDictionary
{
public Templates()
{
this.InitializeComponent();
}
}
Il terzo e ultimo passaggio è registrare il Resource Dictionary come risorsa globale nell’applicazione. Anche in questo caso si utilizza un approccio differente da un dictionary tradizionale: è necessario registrare la classe stessa, al posto di usare il tag ResourceDictionary, come nell’esempio seguente .
<Application
x:Class="NewBindingSample.App"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:templates="using:NewBindingSample.Styles"
RequestedTheme="Light">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<templates:Templates />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Si definisce un nuovo namespace nello XAML, che punta alla classe che funge da Resource Dictionary, e lo si utilizza per includere una nuova istanza della stessa all’interno della proprietà MergedDictionaries del controllo ResourceDictionary. In questo modo, diventa possibile accedere ai DataTemplate definiti all’interno del Resource Dictionary come se fossero dei normali stili, ovvero sfruttando la markup extension StaticResource, come nell’esempio seguente:
<ListView ItemTemplate="{StaticResource PeopleTemplate}" x:Name="PeopleList" />
Ottimizzare il caricamento: la markup expression x:Phase
Un’altra interessante novità introdotta nello XAML è la possibilità di definire l’ordine di caricamento dei controlli presenti nella pagina. In questo modo possiamo dare priorità a quelli più semplici e veloci da caricare, così da dare all’utente una migliore impressione di fluidità e reattività. Tale risultato si ottiene con una nuova markup expression di nome x:Phase, che è strettamente collegata a x:Bind: è possibile, infatti, utilizzarle solamente in combinazione; non è possibile utilizzare x:Phase se nessuna delle proprietà del controllo viene valorizzata tramite binding.
Implementarla è molto semplice, è sufficiente valorizzare la proprietà x:Phase del controllo con un numero ordinale, che rappresenta l’ordine di caricamento. Ecco un esempio:
<DataTemplate x:DataType="model:Person">
<Grid>
<Image Source="{x:Bind Path=Image}" x:Phase="2" VerticalAlignment="Bottom" Stretch="UniformToFill" />
<Border Background="Gray" Opacity="0.8" Height="100" VerticalAlignment="Bottom">
<TextBlock Text="{x:Bind Path=Name}" Foreground="White" FontSize="30" Margin="19, 0, 0, 19"
VerticalAlignment="Bottom" x:Phase="1"/>
</Border>
</Grid>
</DataTemplate>
Questo DataTemplate, il cui scopo è quello di mostrare il dettaglio di una persona, include due informazioni, un’immagine e il nome, entrambe valorizzate tramite l’espressione x:Bind . Sfruttando la nuova proprietà x:Phase, siamo in grado di specificare che il nome della persona debba essere caricato prima dell’immagine. In questo modo, anche se il caricamento delle immagini dovesse richiedere parecchio tempo (ad esempio, perché sono pubblicate su un server remoto), l’utente avrebbe comunque la possibilità di vedere la lista e i nomi delle persone presenti nella collezione.
In conclusione
Nel corso di questo post abbiamo visto alcune delle potenzialità delle nuove markup expression introdotte nella Universal Windows Platform di Windows 10. Potete trovare diversi esempi di queste nuove espressioni (insieme a molti altri) su GitHub all’indirizzo https://github.com/qmatteoq/Windows10-Samples
Comments
Anonymous
June 11, 2015
avrei inserito una proprietà nella page in modo da decidere se convertire il binding classico in x:bind, avrebbe aiutato nella migrazione delle vecchie app. Cmq articolo chiarissimo come sempre da parte di Matteo, peccato per gli spazi mancanti negli esempi di codice che possono portare fuori strada chi comincia.Anonymous
June 11, 2015
Ciao Tony, grazie per il commento, sistemiamo subito gli spazi. Per il suggerimento sulla proprietà nella pagina per sostituire il binding, è una bella idea ma purtroppo si scontra il fatto che non è solo questione di sintassi, ma soprattutto di contesto. Se il binding tradizionale si appoggia al DataContext, il nuovo binding si appoggia al code behind e quindi sarebbe comunque necessario apportare delle modifiche per poterlo sfruttare.Anonymous
June 12, 2015
aspetto con ansia una guida passo passo per la conversione di una app windows 8 a windows 10 sfruttando tutte le nuove caratteristiche e i nuovi stilemi. Magari a seguire una bella estensione del vs2015 che agevola l'adeguamento. Ho 40 app da convertire entro il 29 luglio...;-)Anonymous
June 24, 2015
Non vedo l'ora! I nuovi sistemi mostrano una promessa di un environment molto buonoAnonymous
October 14, 2016
Ciao Matteo, faccio i complimenti della ottima e molto chiara descrizione di x:Bind, ho provato l'esempio di cui hai messo il link, e qualcosa non riesco a capire. Dopo avere visionato il codice dell'esempio ho provato a replicarlo, risultato Eccezioni, cosi ho provato a fare un coppia e incolla dell'esempio e il risultato non cambia, nella sezione che descrive "List & Template" mi da la stessa "Eccezione di tipo 'System.BadImageFormatExceptio" nella classe "ListsAndTemplates,g,cs" alla proprietà "obj.ItemsSource = value;", non riesco a capire perché non funziona dato che ho fatto un copia e incolla e mi da la stessa Eccezione. Ho posto questo problema qui nel tuo post dato che hai messo il link dell'esempio, spero che tu possa aiutarmi. Grazie