Diamo una breve occhiata all’ultimo XAML che abbiamo scritto per ottenere una ListBox con associato un template, definito in uno UserControl separato.
<ListBox Name="lstPlayers"
ItemsSource="{Binding Source={StaticResource players},
Path=HockeyPlayer}">
<ListBox.ItemTemplate>
<DataTemplate>
<a:HockeyPlayerTemplate />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
A questa ListBox è stato associato, quindi, un solo template, HockeyPlayerTemplate, che fa in modo che a run-time la ListBox appaia in un certo modo. Fare riferimento all’ultimo post per maggiori dettagli.
La cosa interessante è che non è detto che il template debba per forza essere deciso in fase di compilazione, ma anche durante l’esecuzione del programma stesso. Ancora più interessante: non è detto che tutti gli Items della ListBox debbano per forza utilizzare e/o avere lo stesso template. Guardate la dichiarazione di questa ListBox:
<ListBox Name="lstPlayers" ItemsSource="{Binding Source={StaticResource players}, Path=HockeyPlayer}"
ItemTemplateSelector="{StaticResource selector}" />
E’ stata valorizzata la proprietà ItemTemplateSelector. Non fatevi ingannare dal nome: prima che la studiassi, pensavo che fosse una proprietà che serviva a definire un template da utilizzare come selettore, al posto del classico rettangolone blu che appare quando selezioniamo un oggetto sulla ListBox. Nulla di tutto questo. La proprietà ItemTemplateSelector specifica una classe particolare, che viene utilizzata dall’engine di WPF per determinare il template da utilizzare per ciascun item della ListBox stessa. Mi spiego meglio: in questo caso ItemTemplateSelector fa riferimento ad una risorsa statica con Key=”selector”. Tale risorsa è ovviamente definita all’interno dello stesso file XAML, e precisamente:
<Window.Resources>
<a:HockeyPlayers x:Key="players" />
<a:HockeyPlayerDataTemplateSelector x:Key="selector"/>
</Window.Resources>
La classe HockeyPlayerDataTemplateSelector è una classe definita nel namespace MyItemTemplate, che eredita da DataTemplateSelector. Tutto quello che dobbiamo fare, quindi, è implementare la classe, facendo l’override del metodo SelectTemplate e ritornando un’istanza di DataTemplate, che verrà utilizzata per disegnare l’item corrente. Ricordo infatti che durante l’esecuzione del codice si entra nel metodo SelectTemplate una volta per ciascun item che deve essere aggiunto alla ListBox. La mia implementazione, giusto per farvi vedere è la seguente:
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item != null && item is HockeyPlayer)
{
DataTemplate ret = null;
HockeyPlayer p = item as HockeyPlayer;
Window window = Application.Current.MainWindow;
switch (p.Sex)
{
case SexEnum.M:
{
ret = window.FindResource("templateM") as DataTemplate;
break;
}
case SexEnum.F:
{
ret = window.FindResource("templateF") as DataTemplate;
break;
}
}
return(ret);
}
return null;
}
La morale è: la classe HockeyPlayer espone una proprietà Sex, che è di tipo SexEnum, che può valere M o F. La classe HockeyPlayerDataTemplateSelector si occupa di ritornare un template piuttosto che un altro. La scelta può avvenire in base al codice che andiamo a scrivere nel metodo. Nel mio caso, ho buttato giù un semplice switch che in un caso ritorna il template denominato templateM; nell’altro ritorna templateF. I due template si differenziano l’uno dall’altro solo per il colore di sfondo: azzurro in un caso e rosa nell’altro. Un approccio di questo tipo è sprecato, perchè si potrebbe ottenere lo stesso risultato usando i triggers nativamente disponibili con WPF. Il fatto di avere una classe che si occupa di selezionare un template apre le porte ad indubbi vantaggi: estrarre la logica al di fuori della Window e, soprattutto, avere una logica mooolto più complessa rispetto a quella esprimibile attraverso i trigger. Per esempio: i trigger scattano solo quando una determinata proprietà assume un determinato valore. Ma cosa succede, per esempio, se volessimo usare un template nel caso in cui il peso sia < 90 Kg? Oppure, ancora, se un certo giocatore è alto più di 175cm? Inoltre, con i trigger possiamo cambiare valore alle proprietà, mentre con il metodo descritto in questo post possiamo anche prelevare ovviamente template molto diversi fra loro, a seconda delle necessità. Il metodo SelectTemplate può contenere tutto il codice che vogliamo, e quindi essere complesso a piacimento. Ricordiamoci solo che l’esecuzione del metodo avviene per ogni item, quindi tenere d’occhio le performance. Ad esempio, fare un window.FindResource ad ogni iterazione può essere controproducente: molto meglio cacheare da qualche parte i due template e nel metodo semplicemente ritornarli all’engine di WPF.
Tirando le somme, voglio ricordare che i due template sono stati definiti fra le risorse globali dell’applicazione. Vi riporto la definizione di uno solo dei due template, ovvero quello identificato dalla key templateM. E’ un po’ lungo, ma è lo stesso che abbiamo usato nei post precedenti:
<DataTemplate x:Key="templateM">
<DataTemplate.Resources>
<a:SliderValueConverter x:Key="conv"/>
</DataTemplate.Resources>
<StackPanel Margin="4" Orientation="Horizontal">
<StackPanel.Background>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="Blue" Offset="0.0" />
<GradientStop Color="White" Offset="0.80" />
<GradientStop Color="Blue" Offset="1.0" />
</LinearGradientBrush>
</StackPanel.Background>
<TextBox Margin="4" Width="120" Foreground="Red">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<a:NameHockeyPlayerRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<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">
<Slider.Value>
<Binding Path="Height" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<a:HeightHockeyPlayerRule />
</Binding.ValidationRules>
</Binding>
</Slider.Value>
</Slider>
</StackPanel>
</DataTemplate>
La differenza fra i due sta nel Background dello StackPanel: sono tutti e due gradienti, solo che in quello più sopra si usa il blue, nell’altro il rosa.
Technorati Tags: wpf programming .net