[5] Il ritorno dello HockeyPlayer (data-binding con WPF)
Proseguo la mia serie di articoli con una piccola riflessione che ho fatto fra me e me ieri sera mentre stavo lavorando. Per spiegarla, riprendo un blocco XAML del post [3] Il ritorno dello HockeyPlayer (data-binding con WPF), nel punto in cui definisco la ListBox che contiene i vari HockeyPlayer:
<ListBox Name="lstPlayers" ItemsSource="{Binding Source={StaticResource players}, Path=HockeyPlayer}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Margin="4" Orientation="Horizontal"> <TextBox Text="{Binding Path=Name}" Margin="4" Width="120" /> <TextBox Margin="4" Text="{Binding Path=Weight}" Background="LightCoral" Width="30" /> <TextBlock Margin="4" Text="{Binding ElementName=sldHeight, Path=Value}" Width="60" /> <Slider Name="sldHeight" Value="{Binding Path=Height}" Minimum="140" Maximum="230" Width="120" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
Osserviamo come la definizione del template col quale deve essere disegnato ogni Item della ListBox è fatto in corrispondenza della definizione della ListBox stessa. All’interno del tag <ListBox></ListBox> si definisce il DataTemplate, che è composto da uno StackPanel e via dicendo. Questo è quello che sono sempre stato abituato a vedere.
La mia domanda è: posso spostare la definizione del template in uno XAML dedicato? In altre parole, posso creare uno UserControl separato e staccato e poi referenziarlo e riutilizzarlo nel template? La risposta è sì. I motivi possono essere molteplici: se creo uno UserControl, posso utilizzare la stesso oggetto in più punti della mia applicazione, per favorire ad esempio una certa omogeinità nell’interfaccia. Se creo uno UserControl, questo è più facilmente debuggabile. Se creo uno UserControl, il codice XAML è meno prolisso, più leggibile e più facilmente modificabile.
Per farlo, è sufficiente aggiungere al progetto un nuovo UserControl (WPF), dargli il nome che vogliamo (ad esempio, HockeyPlayerTemplate) e copiare all’interno del blocco <UserControl></UserControl> lo XAML che definisce i controlli:
<UserControl x:Class="MyItemTemplate.HockeyPlayerTemplate" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:a="clr-namespace:MyItemTemplate"> <StackPanel Margin="4" Orientation="Horizontal"> <TextBox Margin="4" Width="120" Style="{StaticResource mandatory}" Text="{Binding Path=Name}" /> <TextBox Margin="4" Text="{Binding Path=Weight}" Background="LightCoral" Width="30" /> <TextBlock Margin="4" Text="{Binding ElementName=sldHeight, Path=Value, Converter={StaticResource conv}}" Width="60" /> <Slider Name="sldHeight" Minimum="50" Maximum="200" Width="120" Value="{Binding Path=Height}" /> </StackPanel> </UserControl>
Da notare che il resto è rimasto invariato. Continuo a fare uso del binding, anche se in questo contesto WPF non sa bene a cosa bindare. Non ha importanza, perchè poi a runtime ha tutte le informazioni che gli servono: in particolare, siamo noi che diciamo che ogni elemento della ListBox appartiene alla classe HockeyPlayer. Di conseguenza, il binding sulle proprietà Weight, Height e via dicendo funziona senza alcun problema.
Detto questo, la ListBox può essere definita come segue:
<ListBox Name="lstPlayers" ItemsSource="{Binding Source={StaticResource players}, Path=HockeyPlayer}"> <ListBox.ItemTemplate> <DataTemplate> <a:HockeyPlayerTemplate /> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
Il DataTemplate fa riferimento grazie al prefisso a alla classe HockeyPlayerTemplate, che è proprio lo UserControl che abbiamo appena creato. Questo UserControl, lo ripeto, non solo può essere utilizzato come template per la ListBox, ma in ogni altro punto della nostra applicazione, qualora ci servisse.
Una precisazione. Nello UserControl va aggiunta una risorsa, il converter che abbiamo creato l’ultima volta. Questo converter viene utilizzato nel TextBlock per permettere di leggere il valore dello Slider senza decimali. Non è un problema, basta aprire lo XAML dello UserControl ed aggiungere tra le sue risorse la classe che fa da converter:
<UserControl.Resources> <a:SliderValueConverter x:Key="conv"/> </UserControl.Resources>
Domanda: perchè non dobbiamo inserire anche la risorsa denominata mandatory? Perchè, lo abbiamo visto precedentemente, questa risorsa è stata inserita fra quelle a livello di applicazione, e quindi è globale.