Loupe

Le SemanticZoom avec MVVM, exemple avec une application Windows 8.1

Lorsqu’on possède des données groupées au sein d’une application Windows 8.1 MVVM, il est intéressant d’utiliser une GridView (ou une ListView en fonction des besoins) pour les afficher. Rajoutez à cela un SemanticZoom et vous obtiendrez une application user-friendly.

Le SemanticZoom, un contrôle indispensable

Mais tout d’abord, qu’est-ce qu’un SemanticZoom ?

Le SemanticZoom permet de changer la structure ainsi que la présentation de l’information selon le zoom actuel de la vue.

Pour bien comprendre, nous allons imaginer une liste de produits, chacun appartenant à une catégorie. On va donc vouloir grouper les produits par catégories, et c’est là que le SemanticZoom intervient avec ses deux états :

La ZoomedInView qui correspond à l’état « normal » de la vue : tout simplement la liste de produit.

image

 

La ZoomedOutView qui, quant à elle, va représenter l’état « dézoomé » de votre liste qui fournit à l’utilisateur une vision plus globale, ici la liste des catégories.

image

 

La particularité de ces deux vues que sont la ZoomedInView et la ZoomedOutView est qu’on peut y insérer uniquement un objet qui implémente l’interface ISemanticZoomInformation. Typiquement, on parle ici des implémentations de ListViewBase, telles que les GridView et ListView, mais il est aussi possible d’y mettre un Hub.

Cependant, l’interface ISemanticZoomInformation ne fournit pas les informations permettant de synchroniser les deux vues du SemanticZoom. Or, il est essentiel que ces deux vues le soit, dans notre cas il serait intéressant, même indispensable, que lorsque l’utilisateur choisit un groupe, de le rediriger sur les produits du groupe en question.

Pour cela, il est possible d’utiliser une CollectionViewSource et de binder nos deux vues sur cette même CollectionViewSource, ou bien de s’abonner à l’évènement ViewChangeStarted du SemanticZoom et de synchroniser soi-même les items des deux listes.

Maintenant que l’on comprend mieux l’utilité que peut avoir un tel contrôle, passons à la réalisation from scratch.

 

Les données que nous allons utiliser

Tout d’abord, il nous faut quelque chose à binder, et donc un modèle. Nous allons garder l’exemple de tout à l’heure, la liste de produits et de catégories.

public class Category
{
    public string Title { get; set; }

    public List<Product> Products { get; set; }
}

 public class Product
{
    public string Name { get; set; }

    public double Price { get; set; }
}

 

Ici, rien de spécial, nous avons notre Category qui possède une liste de Product, et la classe Product elle-même.

Ensuite, on créé un jeu de données : une liste de catégories, chaque catégorie possédant une liste de produit. Et on set la propriété du ViewModel en question pour pouvoir la binder par la suite. Dans notre cas, on simule en dur une récupération de donnée depuis sa base, par exemple.

private List<Category> GetData()
{
    var categories = new List<Category>();
    var productList1 = new List<Product>() { new Product { Name = "Framboise", Price = 2.1d }, new Product { Name = "Fraise", Price = 3.4d }, new Product { Name = "Poire", Price = 4.2d }, new Product { Name = "Pomme", Price = 2.9d }, new Product { Name = "Cerise", Price = 4.2d }, new Product { Name = "Framboise 2", Price = 4.2d }, new Product { Name = "Fraise 2", Price = 4.2d } };
    var productList2 = new List<Product>() { new Product { Name = "Carotte", Price = 4d }, new Product { Name = "Concombre", Price = 1.2d }, new Product { Name = "Oignon", Price = 2.7d }, new Product { Name = "Carotte", Price = 4d }, new Product { Name = "Carotte", Price = 4d }, new Product { Name = "Carotte", Price = 4d } };
    var productList3 = new List<Product>() { new Product { Name = "Carotte", Price = 4d }, new Product { Name = "Concombre", Price = 1.2d }, new Product { Name = "Oignon", Price = 2.7d }, new Product { Name = "Carotte", Price = 4d }, new Product { Name = "Carotte", Price = 4d }, new Product { Name = "Carotte", Price = 4d } };

    categories.Add(new Category(){Title = "Fruits",Products = productList1});
    categories.Add(new Category() {Title = "Légumes", Products = productList2});
    categories.Add(new Category(){Title = "Légumes 2",Products = productList3});

    return categories;
}
public List<Category> Categories { get; set; }
public MainViewModel()
{
    Categories = GetData();
}

C’est tout pour le modèle, on peut maintenant passer à la deuxième et déjà dernière étape, le XAML !

 

Comment allons-nous afficher tout ça?

La première chose à faire est de créer le SemanticZoom, et c’est vraiment simple.

<SemanticZoom Background="#1E1E1E">
    <SemanticZoom.ZoomedInView></SemanticZoom.ZoomedInView>
    <SemanticZoom.ZoomedOutView></SemanticZoom.ZoomedOutView>
</SemanticZoom>

Maintenant il suffit de compléter les deux vues avec des implémentations de ISemanticZoomInformation, disons des GridView.

ZoomInView

Pour pouvoir alimenter la GridView, nous allons créer une CollectionViewSource dans les ressources de la page, que l’on va binder sur la propriéte ItemsSource de la GridView. Il est important de spécifier à « True » la propriété IsSourceGrouped, c’est cela qui va regrouper les produits par catégories au sein de la CollectionViewSource.

<CollectionViewSource x:Name="CategoriesViewSource"
IsSourceGrouped="True"
ItemsPath="Products"
Source="{Binding Categories}" />
<GridView Padding="24,0"
          ItemsPanel="{StaticResource ProductItemsPanelTemplate}"
          ItemTemplate="{StaticResource ProductItemTemplate}"
          ItemsSource="{Binding Source={StaticResource CategoriesViewSource}}">
    <GridView.GroupStyle>
        <GroupStyle HidesIfEmpty="True">
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <TextBlock Text='{Binding Title}'
                               Foreground="White"
                               Style="{StaticResource HeaderTextBlockStyle}"
                               Margin="30" />
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
        </GroupStyle>
    </GridView.GroupStyle>
</GridView>

Si on lance l’application, la liste apparait bien, on peut voir tous les produits avec leur nom, et on voit les catégories. Ce n’est pas très beau mais c’est fonctionnel ! Maintenant il faut afficher les catégories pour avoir une vision globale.

ZoomedOutView

Pour la ZoomedOutView, c’est le même principe, sauf qu’on va alimenter la GridView différemment, et pour cela nous allons utiliser la propriété CollectionGroups de la CollectionViewSource.

<GridView HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="24"
ItemTemplate="{StaticResource GroupItemTemplate}"
ItemsSource="{Binding Source={StaticResource CategoriesViewSource}, Path=CollectionGroups}"/>

Une fois cela fait, il suffit de créer son ItemTemplate et le tour est joué.

<DataTemplate x:Key="GroupItemTemplate">
    <Grid Width="400"
          Height="200"
          Background="Tomato">
        <TextBlock FontSize="32"
                   FontWeight="Bold"
                   Foreground="White"
                   Margin="12"
                   Text="{Binding Group.Title}"></TextBlock>
        <TextBlock FontSize="72"
                   Margin="0,0,12,0"
                   FontWeight="ExtraBold"
                   Foreground="White"
                   HorizontalAlignment="Right"
                   VerticalAlignment="Bottom"
                   Text="{Binding Group.Products.Count}"></TextBlock>
        <Border BorderBrush="White"
                BorderThickness="1"></Border>
    </Grid>
</DataTemplate>

Pour ma part, j’ai choisi d’afficher le titre de la catégorie, que l’on bind de la sorte : "{Binding Group.Title}, Vous aurez compris que la CollectionGroups possède donc une liste de Group, Group étant donc un objet de type Category dans notre cas. Cela me permet de rajouter en plus le Group.Products.Count dans mon template.

 

Conclusion

Vous l’aurez compris, il est très simple de combiner MVVM, Binding, GridView et Semantic Zoom. Avec tous ces ingrédients, on peut afficher nos données de la plus simple des manières tout en proposant à l’utilisateur de notre application une expérience agréable et surtout très « Windows 8 ». Au passage, je parle de Windows 8/8.1 mais le SemanticZoom peut aussi être utilisé sur Windows Phone !

N’hésitez pas, par exemple, à manipuler ces composants, rien ne vous empêche de créer une implémentation de l’ISemanticZoomInformation.

Ah, et surtout… Ne faites pas le même design que moi si vous voulez une application attrayante !

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus