Comment utiliser le SemanticZoom avec Windows Phone 8.1

windows_phone_8_logoAvec la nouvelle version du Windows Phone SDK (la version 8.1), certains contrôles dédiés au Windows Phone ont changé.

En effet le but de Microsoft est d’unifier les framework utilisés pour les applications Windows Store et les applications mobiles.

Un des contrôles qui a changé est le LongListSelector, de fait il n’existe plus dans la version 8.1, cependant il a été remplacé par le SemanticZoom qui était déjà existant pour les applications store. Nous allons voir comment utiliser ce contrôle pour la version mobile. J’utiliserai le pattern MVVM pour l’article.

Le DataModel

Dans un premier temps, nous allons définir un modèle de donnée. Par exemple, une classe qui contient 2 chaines de caractères:

public class ExempleClass
    {
        public string Name { get; set; }
        public string Address { get; set; }
    }

Nous devons aussi définir une nouvelle classe qui implémente IEnumerable, cette dernière sera utilisée pour trier les données.

public class GroupInfoList : List<object>
    {

        public object Key { get; set; }

        public new IEnumerator GetEnumerator()
        {
            return (IEnumerator)base.GetEnumerator();
        }
    }

Dans ce cas, c’est List qui porte l’implémentation de IEnumerable.

Le ViewModel

La première chose à faire dans le ViewModel est de récupérer la donnée de type ExempleClass. Une fois que cela est fait, nous devons utiliser GroupListInfo pour ordonner les données.

public async Task<List<GroupInfoList<ExempleClass>>> GroupData(List<ExempleClass> listExemple)
        {
            return await Task.Run(() => {
                var groups = new List<GroupInfoList<ExempleClass>>();
                var groupQuery = from element in listExemple
                                 orderby element.Name
                                 group element by element.Name[0] into g
                                 select new { GroupName = g.Key, Items = g };

                foreach(var g in groupQuery)
                {
                    GroupInfoList<ExempleClass> exemple = new GroupInfoList<ExempleClass>();
                    exemple.Key = g.GroupName;
                    foreach(var element in g.Items)
                    {
                        exemple.Add(element);
                    }
                    groups.Add(exemple);
                }
                return groups;

            });
        }

Nous avons enfin nos données rangées par groupe. La clef de chaque groupe sera la première lettre de la propriété Name de la classe ExempleClass.
Le résultat de l’appel de cette fonction sera stocké dans une propriété du ViewModel.

private List<GroupInfoList<ExempleClass>> groupedExemple;

        public List<GroupInfoList<ExempleClass>> GroupedExemple
        {
            get { return groupedExemple; }
            set { groupedExemple = value; RaisePropertyChanged(); }
        }

Car nous sommes dans un ViewModel, il ne faut pas oublier d’implémenter INotifyPropertyChanged.

protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            RaisePropertyChangedImpl(propertyName);
        }

        private void RaisePropertyChangedImpl(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }

Dans le but de ne pas avoir à écrire à chaque fois le nom de la propriété à chaque appel de RaisePropertyChanged, J’utilise l’attribut [CallerMemberName].

Nous avons fait le tour du ViewModel, maintenant passons à la View.

La View

Pour commencer je dois définir mon ViewModel comme le DataContext de ma View. Pour ce faire, j’utiliserai un ViewModelLocator, qui est une classe qui contient une propriété de type mon ViewModel:

public ExempleClassViewModel ExempleClassViewModel
        {
            get
            {
                return new ExempleClassViewModel();
            }
        }

Le XAML sera le suivant:

<Page
        ...
        DataContext="{Binding ExempleClassViewModel, Source={StaticResource ViewModelLocator}}">

Nous devons maintenant définir des ressources pour notre page:

<Page.Resources>
    <CollectionViewSource x:Name="CVS" 
                            Source="{Binding GroupedExemple}" 
                            IsSourceGrouped="True"/>
    <JumpListItemBackgroundConverter x:Key="ListItemBackgroundConverter"/>
    <JumpListItemForegroundConverter x:Key="ListItemForegroundConverter"/>
</Page.Resources>

La première ressource est une CollectionViewSource, elle sera utilisée comme source de données pour le contôle SemanticZoom. On remarque d’ailleurs que la Source de la CollectionViewSource est bindée sur la propriété de mon ViewModel qui contient ma liste de données groupées.

Les deux autres ressources sont des styles que j’utiliserai pour le SemanticZoom. Ils sont présents par défault, il n’y a rien à ajouter pour les avoir, il suffit de les mettre en ressources.

Le SemanticZoom est composé de deux parties: un ZoomIn et un ZoomOut, chacune de ces parties contiendra un UIElement.

<SemanticZoom>
    <SemanticZoom.ZoomedInView>
        <!-- A list-->
    </SemanticZoom.ZoomedInView>
    <SemanticZoom.ZoomedOutView>
        <!-- A grid-->
    </SemanticZoom.ZoomedOutView>
</SemanticZoom>

ZoomInView

<ListView IsHoldingEnabled="True" 
          ContinuumNavigationTransitionInfo.ExitElementContainer="True"  
          ItemsSource="{Binding Source={StaticResource CVS}}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <StackPanel VerticalAlignment="Top">
                <TextBlock FontWeight="Bold" Text="{Binding Name}"/>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>

    <ListView.GroupStyle>
        <GroupStyle HidesIfEmpty="True">
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <Border Background="{StaticResource PhoneAccentBrush}" 
                            BorderBrush="{StaticResource PhoneAccentBrush}" 
                            Width="80" 
                            Height="80">
                    <TextBlock Text='{Binding Key}' 
                               FontFamily="{StaticResource PhoneFontFamilySemiLight}" 
                               Foreground="{StaticResource PhoneForegroundBrush}" 
                               FontSize="48" 
                               Padding="6" 
                               HorizontalAlignment="Left" 
                               VerticalAlignment="Center"/>
                    </Border>
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
        </GroupStyle>
    </ListView.GroupStyle>
</ListView>

Nous pouvons voir que la propriété ItemsSource de la ListView est bindée sur la CollectionViewSource définie en ressource.

La ListView contient un ItemTemplate qui définit le rendu de chaque élément de ma liste. Dans ce cas une TextBox qui contient la propriété Name.

La ListView possède aussi une propriété de type GroupStyle qui permet de donner un style aux entêtes de chaque groupe, ce sera la propriété Key qui sera utilisée.

ZoomIn

ZoomOutView

<GridView x:Name="zoomOutGrid" 
          ItemsSource="{Binding Source={StaticResource CVS},ElementName=CVS,Path=CollectionGroups}" 
          Background="Black">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Border Background="{Binding Converter={StaticResource ListItemBackgroundConverter}}" 
                    Width="80" 
                    Height="80" 
                    Margin="6">
                <TextBlock Text="{Binding Group.Key}" 
                           Foreground="{Binding Converter={StaticResource ListItemForegroundConverter}}" 
                           HorizontalAlignment="Center" 
                           VerticalAlignment="Center"  
                           FontSize="48" 
                           Padding="6"/>
            </Border>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

On peut voir dans ce cas que l’ItemsSource de la GridView est différent de celui utilisé par la ListView! Bien que la base soit toujours la CollectionViewSource, on utilisera plutôt une propriété de cette dernière (CollectionGroups) comme source.
Ensuite l’ItemTemplate utilisé dans la GridView est similaire à celui utilisé dans la ListView.

ZoomOut

(les captures d’écrans sont ici à titre d’exemple, elles ne correspondent pas exactement au code de cet article)

Conclusion

Comme on peut le voir ce contrôle est très pratique pour présenter des données à l’utilisateur. Cependant sa mise en place peut être complexe la première fois.

Nombre de vue : 69

COMMENTAIRES 1 commentaire

  1. leon.zitrone dit :

    Article intéressant, néanmoins hériter de List c’est sale !

AJOUTER UN COMMENTAIRE