Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Un menu gratuit à Tuile pour WPF ou comment se donner un air Windows 8 en bureau classique…

[new:31/12/2012]Un menu de type Tuile ça peut changer beaucoup de choses dans une application WPF (ou Silverlight), cela rend le logiciel plus facile à utiliser même sous Windows 7 en tactile, et puis c’est un petit vent de fraicheur qui permet de se passer des menus traditionnels. Gratuit ? oui. Et avec le source. C’est un cadeau Dot.Blog…

Un menu à tuile

image

Le menu c’est tout ce qui est dans la zone ayant l’image vectorielle en fond. Le bandeau supérieur fait partie de la mise en page du logiciel.

Comme on le voit : il y a des tuiles de couleurs différentes, des groupes qui portent un nom, et une image de fond. La zone des tuiles est légèrement plus foncée ce qui dessine deux bandes en haut et en bas de cette zone qui délimitent le menu de façon subtile tout en laissant circuler les couleurs.

Sur cette capture on ne le voit pas, mais les groupes peuvent contenir plusieurs lignes de tuiles bien entendu. De même le nombre de groupes n’est pas limité, et si le menu dépasse vers la droite, comme sous Windows 8 on peut facilement scroller avec la molette de la souris. Un parti pris : ne pas afficher l’ascenseur horizontal, question de goût (mais avec le code gratuit de ce contrôle il est facile de faire réapparaitre l’ascenseur si vous le désirez).

Paramétrable et personnalisable

Le but du jeu était que ce menu soit paramétrable facilement. Il l’est.

Paramétrer c’est bien, personnalisé c’est encore mieux… Par exemple on pourrait travailler un peu plus le template de la classe Button utilisé pour faire les tuiles afin de donner un petit mouvement de pivotation, ou un effet de lumière sur le mouse over ou plein d’autres idées.

Ici je ne suis pas allé très loin niveau design, ce menu est une juste une base assez sophistiquée pour être utilisable tel quel, mais assez ouverte pour laisser la porte ouverte à plein d’ajouts personnels (des icônes pour les tuiles, des tuiles vivantes pourquoi pas ! – ce ne serait pas très difficile à ajouter en fait).

La construction du Menu

De l’extérieur c’est simple : C’est un UserControl agrémenté de propriétés de dépendance.

Il suffit de le poser sur n’importe quelle surface (Window, Page, autre UserControl, etc) et de lui fournir la liste des tuiles à afficher, il se charge du reste. On n’oublie pas de capturer l’évènement de click sur les tuiles (un event global bien entendu qui passe toutes les infos nécessaires).

La mise en page de base du control est simple aussi : une grille séparée en 10% – 80% –10% pour créer l’effet de bandeaux haut et bas, une image en fond, un scrollviewer dans la partie 80% avec un stackpanel dedans…

Tout se joue dans le code qui fabrique le menu à la volée.

Chaque groupe est ainsi une grille contenant deux zones, celle du titre du groupe et une zone plus étendue avec un wrapPanel dans lequel sont ajoutées les tuiles, de simples boutons templatés donc.

Les difficultés

En fait il y en a une seule. Lorsque le control reçoit la liste des entrées de menu, par exemple par un binding depuis un ViewModel, les vues ne sont pas encore affichées. Toutes les mesures de type ActualHeight, Width etc, sont à zéro ou à NaN.

Pourtant les groupes utilisant un wrapPanel, il faut bien les contraindres en hauteur et en largeur sinon rien ne marche.

Il y a donc une astuce pour l’initialisation un peu trop précoce : le Loaded du control est géré pour marquer un flag. La séquence de construction du menu vérifie que ce flag est à true. Dans les situations décrites plus haut il ne l’est pas… Que faire ? On marque alors un second flag de type “à redessiner au plus vite!”, et on attend… Quand Loaded se déclenche on peut positionner à vrai le premier flag et on vérifie qu’il n’y a pas un affichage “en attente”, dans l’affirmative on lance enfin la séquence de création des groupes et des tuiles.

C’est un peu “sportif” comme montage, mais cela évite les inévitables plantages en situation réelle lorsque le menu est alimenté par un Binding venant d’un VM. Une fois le control affiché, tout changement qui interviendrait ne pose plus de problème, les composants ont tous des mesures correctes.

C’est le cas pour l’événement de resize qui reconstruit les groupes en fonction de la hauteur et de la largeur disponible.

Les entrées de menu

Je ne visais pas la perfection absolue mais en revanche l’utilisabilité la meilleure. Ainsi j’ai du faire un compromis entre pureté et isolation du menu avec les besoins de l’application et réalisme…

Les menus sont construits comme une List<MenuEntry>, c’est à l’application de fabriquer cette liste et la fournir au menu.

Lorsqu’une tuile est cliquée, l’événement global retourne l’objet MenuEntry concerné.

Il fallait que les informations contenues servent à créer le menu mais aussi qu’elle puisse permettre à l’application de prendre des décisions sur le module à afficher.

La classe MenuEntry contient ainsi des informations propres à la conception du menu à tuile (nom de groupe, texte affiché, couleur de la tuile, taille de la fonte, nom de la commande à exécuter…) mais en plus elle stocke des informations utiles à l’application comme IsVisible ou IsEnabled qui permettent en fonction des droits de l’utilisateur de déterminer si une tuile est visible ou non, ou si elle est Enabled ou non. De même MenuEntry possède un Tag qui permet à l’application d’y stocker tout ce qui pourrait être nécessaire au lancement de la vue sélectionnée (une sorte de contexte donc), le tout complété par un marqueur IsScreenReadOnly qui permet de savoir si l’utilisateur à des droits complets (lecture/écriture) sur la vue ou seulement des droits en lecture (ce que la VM de la vue doit être capable de gérer, le menu ne faisant que stocker cette info).

Bien entendu tout cela s’entend dans le contexte réel d’une application et beaucoup de code en dehors du menu s’occupe de lire les droits de l’utilisateur connecté, de fabriquer la liste des tuiles avec leurs paramètres puis de les interpréter lorsque la tuiles est cliquée.

On peut fort bien utiliser le menu en faisant abstraction de ces propriétés spéciales.

Les tuiles sont donc groupées. Pour cela elles utilisent le nom de groupe que chaque entrée possède. Je conseille d’utiliser des constantes statiques pour ne pas risquer une distorsion de casse ou une faute qui créerait un groupe différent là où il n’en n’existe qu’un seul.

De même les tuiles sont rangées par ordre alphabétique. Mais chaque entrée possède un champ Order (par défaut à 1000) qu’il suffit de modifier pour modifier l’ordre d’affichage (en dessous de 1000 pour qu’il soit dans les premiers de la liste, au dessus de 1000 pour l’envoyer en fin liste, avec tous les dégradés possibles).

Le reste, c’est dans le code :-)

Le code est documenté, je ne vais pas tout vous raconter, on ne prend possession d’un code que lorsqu’on fait l’effort de lire !

Alors je serai bref, en fin de billet vous trouverez un zip du projet menu !

Conclusion

Il s’agit d’un petit artifice qui peut rendre une application WPF plus jolie, plus moderne. Ce n’est aussi qu’une version 1.0 qui peut s’agrémenter de templates plus jolis ou d’autres fonctions. Mais l’essentiel est là, et ça fonctionne.

Simple à prendre ne main, documenté, c’est le genre de code que j’aime récupérer pour le bricoler. J’espère qu’il vous amusera autant qu’il m’aurait plu de pouvoir récupéré un équivalent !

N’hésitez à poster vos remarques, vos propositions, les bugs aussi que peut-être vous découvrirez, et peut-être même que ce projet deviendra un jour un projet CodePlex, le principe de ce petit menu à tuile m’amuse beaucoup. Qui se dévoue pour une version Silverlight ? (peu de chose à changer, voire rien à mon avis).

Enaxos Tile Menu Source (VS2012) Tile Menu

Stay Tuned !

blog comments powered by Disqus