Bonjour à tous !
Après un long silence, je refais surface en vous proposant de découvrir un contrôle du dernier Silverlight for Windows Phone Toolkit.
Si vous avez manqué cet évènement, vous pouvez aller de ce pas le télécharger sur codeplex, et jeter un coup d’oeil aux samples.
Côté nouveauté, on a notamment ce fameux LongListSelector, que vous connaissez certainement déjà. Il est vraiment propre à l’univers Windows Phone et vraiment appréciable côté utilisateur.
Pour mettre en place ce composant, nous aurons d’abord besoin d’une classe qui hérite de IEnumerable, qui nous permettra d’introduire une notion de groupe.
C’est effectivement de ça qu’il s’agit : associer des éléments à un groupe.
Celui qui m’intéressait était celui qu’on connait bien, le groupement par ordre alphabétique.
Nous allons d’abord créer cette classe, puis créer une méthode qui permet d’ajouter des objets à des groupes.
Dans mon application, j’ai centralisé la classe et la méthode dans un Helper, une classe statique.
public class Group<T> : IEnumerable<T>
{
public Group(string key, IEnumerable<T> items)
{
this.Key = key;
this.Items = new List<T>(items);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override bool Equals(object obj)
{
Group<T> that = obj as Group<T>;
return (that != null) && (this.Title.Equals(that.Title));
}
public string Key
{
get;
set;
}
public bool HasItems
{
get { if (Items.Count == 0) { return false; }else return true; }
}
public IList<T> Items
{
get;
set;
}
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return this.Items.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return this.Items.GetEnumerator();
}
#endregion
Si on regarde les paramètres du constructeur, on voit qu’on a une chaîne de caractère et une collection générique. La chaîne de caractère représente le nom du groupe, et la collection références tous les objets de ce groupe.
En clair, un groupe « P » et une liste de fruits « Pomme », « Poire », « Pêche » par exemple.
Ensuite il faut pouvoir faire des groupes par ordre alphabétique, c’est là qu’intervient la méthode.
Imaginons des objets Fruit avec des propriétés string Nom, string Variété, DateTime DebutSaison et DateTime FinSaison.
public static ObservableCollection<Group<Fruit>> CreateWidgetGroups(ObservableCollection<Fruit> fruits)
{
ObservableCollection<Group<Fruit>> groups = new ObservableCollection<Group<Fruit>>();
string chars = "#abcdefghijklmnopqrstuvwxyz";
var source = from f in fruits
orderby f.Nom
select f;
// On compare chaque caractère avec le premier de chaque objet de notre liste
// si ils correspondent, on ajoute l'objet au groupe du caractère précédement créé
foreach (char c in chars)
{
Group<Widget> group = new Group<Fruit>(c.ToString().ToLower(), new List<Fruit>());
foreach (Fruit fruit in source)
{
if (fruit.Nom.Substring(0, 1) == c.ToString() || fruit.om.Substring(0,1) == c.ToString().ToUpper())
group.Items.Add(widget);
// On centralise les chiffres dans le groupe #
else if (c.ToString() == "#")
{
try
{
if ((Convert.ToInt32(fruit.Nom.Substring(0, 1)) >= 0))
{
group.Items.Add(fruit);
}
}
catch (FormatException ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
}
groups.Add(group);
}
Voilà, nous avons une méthode capable de créer un groupe pour chaque lettre et d’y ajouter les objets concernés.
Maintenant, il reste encore à créer la vue !
Imaginons que dans notre base de données, nous ayons aussi une illustration de chaque fruit stockée sous forme de chaîne de caractère.
Dans un premier temps, nous allons créer nos DataTemplate, qui servent à décrire la façon dont les données seront affichées.
Rendons nous dans phone:PhoneApplicationPage.Resources pour les créer.
Le contrôle LongListSelector propose un large choix de DataTemplate, mais ici trois suffiront : l’ItemTemplate, le GroupHeaderTemplate et le GroupItemTemplate.
Le premier définit le bloc qui représente chaque fruit, ici une image et nos propriétés énumérées plus haut :
<DataTemplate x:Key="itemTemplate">
<Grid Margin="12,8,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Height="62" Width="62" Source="{Binding Path=Image, Converter={StaticResource ImageConverter}}"></Image>
<StackPanel VerticalAlignment="Top" Grid.Column="1">
<TextBlock Text="{Binding Nom}" Style="{StaticResource PhoneTextLargeStyle}" />
<TextBlock Text="{Binding Variete}" Style="{StaticResource PhoneTextSmallStyle}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Début de saison " Margin="12,3,5,0" Style="{StaticResource PhoneTextSmallStyle}"></TextBlock>
<TextBlock Text="{Binding DebutSaison}" Margin="0,3,0,0" Style="{StaticResource PhoneTextSmallStyle}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Finde saison " Margin="12,3,5,0" Style="{StaticResource PhoneTextSmallStyle}"></TextBlock>
<TextBlock Text="{Binding FinSaison}" Margin="0,3,0,0" Style="{StaticResource PhoneTextSmallStyle}"/>
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
Passons ensuite au GroupHeaderTemplate, qui définit le bloc qui affiche le nom du groupe en tête de chaque groupe :
<DataTemplate x:Key="groupHeaderTemplate">
<Border Background="Transparent" Margin="12,8,0,8">
<Border Background="{StaticResource PhoneAccentBrush}"
Padding="8,0,0,0" Width="62" Height="62"
HorizontalAlignment="Left">
<TextBlock Text="{Binding Key}"
Foreground="#FFFFFF"
FontSize="{StaticResource PhoneFontSizeExtraLarge}"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"/>
</Border>
</Border>
</DataTemplate>
Et enfin, passons au GroupItemTemplate, qui définit la manière dont sera affichée la liste des groupes quand on clique sur un « GroupHeader » [qu'on vient de définir plus haut !]
<DataTemplate x:Key="groupItemTemplate">
<Border Width="99" Height="99" Margin="6" toolkit:TiltEffect.IsTiltEnabled="True" Background="{Binding HasItems, Converter={StaticResource BackgroundConverter}}" IsHitTestVisible="{Binding HasItems}">
<TextBlock Text="{Binding Key}"
FontSize="{StaticResource PhoneFontSizeExtraLarge}"
FontFamily="{StaticResource PhoneFontFamilySemiLight}"
Margin="8,0,0,0"
VerticalAlignment="Bottom"
Foreground="{Binding HasItems, Converter={StaticResource ForegroundConverter}}"
/>
</Border>
</DataTemplate>
Je sais que vous avez l’oeil et que vous avez du coup remarqué la présence de Converters, et de cette propriété HasItems.
Vous vous en doutez, il faut que le groupe soit cliquable uniquement quand il contient au moins un item, sans quoi il faut en informer visuellement l’utilisateur et empêcher le click.
C’est à ça que sert la propriété IsHitTestVisible de Border.
HasItems renvoit true si la collection contient plus de zéro élément.
Si vous ne savez pas vraiment utiliser les converters, je vais vous expliquer comment procéder.
Les converters s’utilisent en général lors des bindings, quand vous voulez …convertir [évidemment
] la valeur passé par le binding vers une autre.
Ici, HasItems nous envoie un type Bool, et nous voulons selon sa valeur changer le Background ou Foreground, qui sont des types SolidColorBrush.
En faisant en binding sans converter de Background= »{Binding HasItems} », ça plantera à coup sûr, ça revient à écrire Background = true, ou false.
Du coup, nous allons passer par un converter, qui au moment du binding, va prendre notre valeur, et la …convertir [vous vous attendiez à quoi ?
] en un autre.
public class BackgroundConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool hasItems = (bool)value;
if (hasItems)
return Application.Current.Resources["PhoneAccentBrush"] as SolidColorBrush;
else return Application.Current.Resources["PhoneBackgroundBrush"] as SolidColorBrush;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Do something with exception
}
}
Voilà, notre HasItem nous retournera une SolidColorBrush qui nous permettra de donner un look « activé » ou « désactivé » à nos items
Ah aussi, pour ceux qui ne sont pas habitués aux ressources, il faut évidemment référencer ces converters, soit dans l’App.xaml, soit dans les ressources de la page (comme illustré ci-dessous)
xmlns:converters="clr-namespace:MonApplication.NamespaceDuConverter">
<phone:PhoneApplicationPage.Resources>
<converters:BackgroundConverter x:Key="BackgroundConverter"></converters:BackgroundConverter>
...
</phone:PhoneApplicationPage.Resources>
Sans quoi, la ressource n’existerait pas