MSDN Magazine > Accueil > Tous les numéros > 2008 > May >  Un code d’enfer: Changement de page Silverlight...
Un code d’enfer
Changement de page Silverlight en toute simplicité
Jeff Prosise

Téléchargement du code disponible sur: WickedCode2008_05.exe (1110 KB)
Browse the Code Online
Il y a deux ans, je marchais dans une rue de Redmond lorsqu'un ami m'a arrêté. « J'ai quelque chose à te montrer » m'a-t-il dit. Il a ouvert son portable et m'a montré une démonstration qui a changé ma vie. Cette démonstration était une première version du SilverlightTM Page Turn Sample, que vous pouvez maintenant trouver sur silverlight.net/samples/1.0/Page-Turn/default.html.
Je n'en croyais pas mes yeux : la démo fonctionnait (incroyable) dans un navigateur ! Plus étonnant encore, elle n'avait pas besoin de Microsoft® .NET Framework ni d'Internet Explorer®, et bien que personne ne l'ait su à l'époque, elle n'allait bientôt plus avoir besoin de Windows® non plus.
PageTurn est la démonstration idéale de Silverlight 1.0. Je l'utilise tout le temps lorsque je veux expliquer Silverlight à des béotiens. Mais jetez un coup d'œil à PageTurn et vous découvrirez que créer sa propre application de changement de page n'est pas une sinécure. PageTurn se base sur des transformateurs, des zones de découpage, des objets XAML créés dynamiquement, etc. Il faut du temps et des efforts (sans oublier une excellente connaissance de Silverlight) pour comprendre le code source. Il fait une démonstration adroite de certaines des capacités les plus intéressantes de Silverlight, mais n'a pas nécessairement été conçu pour une utilisation générale.
C'est pourquoi j'ai créé une infrastructure universelle de changement de page qui facilite incroyablement l'intégration des changements de page dans vos applications Silverlight 1.0. Grâce à mon infrastructure, vous pouvez créer une application toute entière avec seulement quelques lignes de JavaScript. La procédure ne nécessite qu'une connaissance minimale de Silverlight, et comme toute l'infrastructure se compose d'environ 500 lignes de JavaScript, vous pouvez consulter et comprendre son fonctionnement sans devoir vous encombrer l'esprit de milliers de lignes de code. Et vous pouvez évidemment la modifier à votre gré.

Application PageTurnDemo
Avant de présenter l'infrastructure, examinons tout d'abord une application créée autour d'elle. L'application PageTurnDemo illustrée à la figure 1 vous permet de feuilleter les premières pages du numéro de novembre 1988 du Microsoft Systems Journal, aujourd'hui devenu le MSDN® Magazine. (Comment évitez les problèmes de copyright lors de la reproduction de pages d'un magazine ? Utilisez des pages du magazine avec lequel vous travaillez et utilisez un article que vous avez écrit vous-même, tout simplement !) Chacune des pages est une image numérisée, mais l'une d'elles (la dernière) se superpose à l'image dotée de texte XAML. J'ai inclus ce texte pour souligner un point important : les pages intégrées à l'infrastructure ne se limitent pas à de simples images mais peuvent facilement contenir du XAML arbitraire, y compris des éléments Images, TextBlocks, MediaElements, etc.
Figure 1 PageTurnDemo affiche une page partiellement tournée (Cliquer sur l'image pour l'agrandir)
Vous pouvez exécuter la démonstration en téléchargeant le code source et en la lançant à partir de Visual Studio® 2008 ou l'afficher sur wintellect. com/silverlight/pageturndemo. Une fois la couverture du magazine affichée (une barre de progression vous informe de la progression du téléchargement qui récupère toutes les images utilisées dans l'application), utilisez le bouton gauche de la souris pour faire glisser le curseur de droite à gauche sur la couverture pour passer à la page suivante. Vous pouvez continuer à faire glisser les pages de droite restantes pour révéler plus de pages, ou glisser les pages de gauche à droite pour revenir en arrière.
La création de cet exemple était-elle difficile ? Outre l'analyse, le rognage, le dimensionnement des images et leur compression dans un fichier ZIP, absolument pas. Les trois fichiers de code source clé (que vous pouvez trouver dans le téléchargement accompagnant cette rubrique) sont le fichier HTML, le fichier JavaScript associé et le fichier XAML. Enlevez le code qui utilise l'objet téléchargeur Silverlight pour télécharger les images et il reste très peu de code source. En fait, environ 10 lignes seulement sont consacrées à la fonctionnalité de changement de page.

Utilisation de l'infrastructure de changement de page
PageTurnDemo illustre les quatre étapes fondamentales que vous devez respecter pour utiliser l'infrastructure de changement de page. La première étape consiste à inclure PageTurn.js (le fichier script contenant la mise en œuvre d'infrastructure) dans le fichier HTML. Voici la ligne pertinente dans Default.html :
<script ... src="PageTurn.js"></script>
La deuxième étape consiste à instancier l'infrastructure. Comme l'infrastructure de changement de page est encapsulée dans une classe JavaScript nommée PageTurnFramework, l'instruction suivante dans Default.html.js instancie l'infrastructure :
_ptf = new PageTurnFramework(_control,
    _control.content.findName('PageTurnCanvas'));
Le premier paramètre passé au constructeur de PageTurnFramework est une référence au contrôle Silverlight. Le deuxième paramètre est une référence au canevas contenant vos pages. Dans le document XAML de PageTurnDemo, ce canevas est nommé « PageTurnCanvas ».
La troisième étape consiste à inscrire vos pages dans l'infrastructure. Chaque page est représentée par un canevas et PageTurnFramework comporte une méthode addPage que vous pouvez appeler pour inscrire des pages. AddPage accepte deux paramètres.
_ptf.addPage(_control.content.findName('EvenPage0'),
    _control.content.findName('OddPage0'));
Le premier est une référence au canevas représentant la page de gauche dans une paire de pages se faisant face. Le deuxième est une référence au canevas représentant la page de droite. Vous pouvez appeler addPage autant de fois que nécessaire pour inscrire toutes vos paires de pages. Ensuite, le dernier appel d'addPage doit être suivi par un appel d'initializeFramework :
_ptf.initializeFramework();
la méthode initializeFramework initialise l'état interne de l'infrastructure. Elle crée notamment plusieurs objets XAML utilisés pour découper et faire pivoter des pages, et enregistre également des gestionnaires pour des événements clé.
La quatrième et dernière condition pour utiliser l'infrastructure est d'assurer un nettoyage correct. Pour éviter les fuites de mémoire, les applications basées sur navigateur qui inscrivent les gestionnaires d'événements par programmation doivent également les désinscrire. PageTurnFramework comprend une méthode de suppression qui désinscrit les gestionnaires d'événements inscrits par addPage et initializeFramework. Dans PageTurnDemo, un attribut onunload dans l'élément <body> appelle une fonction de suppression locale lorsque la page se décharge :
<body ... onunload="dispose()">
Cette fonction de suppression locale, qui réside dans Default.html.js, appelle la méthode de suppression de l'infrastructure :
if (_ptf != null)
    _ptf.dispose();
J'ai utilisé un événement DOM pour déclencher des appels de suppression parce que Silverlight ne lance pas d'événements de téléchargement. Vous pouvez tout aussi facilement utiliser des événements window.unload si vous préférez.
La classe PageTurnFramework présente six méthodes publiques que vous pouvez appeler pour ajouter une fonctionnalité de changement de page à vos applications (voir figure 2). PageTurnDemo en utilise trois : AddPage, initializeFramework et Dispose. Les autres méthodes peuvent être utilisées pour ajouter des fonctionnalités supplémentaires. Par exemple, si vous souhaitez inclure une barre de navigation composée de miniatures de page dans votre application (comme le fait la démonstration Silverlight PageTurn), vous pouvez appeler PageTurnFrame work.goToPage lors d'un clic sur une miniature pour accéder directement à la page correspondante.

Méthode Description
addPage Inscrit une paire de pages dans l'infrastructure.
Dispose Libère des ressources détenues par l'infrastructure pour un nettoyage correct.
getCurrentPageIndex Revient à l'index basé sur 0 de la paire de pages actuellement affichée.
getPageCount Revient au nombre de paires de pages ajoutées avec addPage.
goToPage Affiche la paire de pages spécifiée.
initializeFramework Initialise l'infrastructure de changement de page.

Structure XAML
L'infrastructure de changement de page impose quelques conditions fondamentales à la structure de documents XAML. Ces conditions sont :
  1. Représenter chaque page dans une paire de pages par un canevas XAML (un « canevas de page »).
  2. Inclure un canevas de changement de page qui est un conteneur pour les canevas de toute la page.
  3. Attribuer une largeur, une hauteur et une couleur d'arrière-plan (même s'il est transparent) au canevas racine.
La raison de cette troisième condition est que lors de l'initialisation, l'infrastructure inscrit un gestionnaire pour les événements MouseLeave lancés par le canevas racine. Ces événements sont utilisés pour effectuer les changements de page si le curseur quitte le contrôle Silverlight avec une page partiellement tournée.
En matière de méthodes recommandées, toutes les applications Silverlight 1.0 qui capturent la souris, comme le fait l'infrastructure de changement de page au début d'un changement de page, doivent traiter les événements MouseLeave émanant du canevas racine et saisir cette occasion pour libérer la souris. Et pour qu'un élément reçoive les événements de souris tels que MouseEnter et MouseLeave, l'élément doit être « testable » dans Silverlight. On y parvient en s'assurant que l'élément (dans ce cas, un canevas) a une taille et un arrière-plan « non nuls ».
Avec ces conditions à l'esprit, la figure 3 présente la structure générale d'un document XAML utilisé avec l'infrastructure de changement de page. Le canevas de changement de page est celui que vous passez au constructeur de classe PageTurnFramework. Les canevas de page sont passés à PageTurnFramework addPage. Le canevas de changement de page et les canevas de page devraient être généralement étiquetés avec des largeurs et des hauteurs explicites afin de garantir que les événements de souris utilisés par l'infrastructure fonctionnent convenablement quel que soit le contenu XAML des canevas. Le reste est du XAML.
<Canvas
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Background="color" Width="width" Height="height">

    <!-- Other XAML content goes here -->

    <!-- Page turn canvas -->
    <Canvas>

        <!-- Canvases representing first page pair -->
        <Canvas>
            <!-- Content for left-hand page goes here -->
        </Canvas>
        <Canvas>
            <!-- Content for right-hand page goes here -->
        </Canvas>

       <!-- Canvases representing additional page pairs go here -->

    </Canvas>

    <!-- Other XAML content goes here -->

</Canvas>

Le canevas de changement de page peut heureusement exister à côté de tout autre contenu riche dans votre document XAML. Et les pages individuelles peuvent être de simples images XAML ou des rendus XAML complexes. Vous devez généralement éviter d'utiliser la propriété de découpage des canevas de page car l'infrastructure elle-même utilise cette propriété. Cependant, vous pouvez toujours déclarer un sous-canevas dans un canevas de page et utiliser sa propriété de découpage pour définir des régions de découpage.
Si vous voulez que la première page de gauche soit vierge afin que la première page de droite ait l'apparence de la couverture d'un livre fermé, déclarez simplement un rectangle opaque dans la première page de gauche. De même, vous pouvez utiliser un rectangle opaque pour la dernière page de droite pour donner l'apparence d'un livre fermé lorsque l'utilisateur tourne la dernière page. PageTurnDemo fait les deux pour confirmer l'illusion que vous feuilletez les pages d'un magazine.
Lorsque vous numérisez les pages d'un livre ou d'une revue à utiliser avec l'infrastructure de changement de page, comme je l'ai fait pour PageTurnDemo, les numérisations présentent souvent des ombres au niveau des bords intérieurs qui donnent aux pages une apparence plus réaliste. Si vous n'utilisez pas d'images numérisées, un peu de XAML peut simuler des ombres. Pour l'application illustrée à la figure 4 (qui représente certains de mes avions et jets télécommandés et que vous pouvez voir sur wintellect. com/silverlight/mymodels), j'ai utilisé les rectangles XAML de la figure 5 pour créer des ombres autour de la séparation verticale au centre de chaque paire de pages. Les valeurs alpha des couleurs GradientStop font s'atténuer les rectangles de droite à gauche sur les pages de gauche et de gauche à droite sur les pages de droite.
<!-- Shadow on left-hand page -->
<Rectangle Canvas.Left="380" Canvas.Top="0" Width="20" Height="600">
    <Rectangle.Fill>
        <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
            <GradientStop Color="#00000000" Offset="0.0" />
            <GradientStop Color="#40000000" Offset="1.0" />
        </LinearGradientBrush>
    </Rectangle.Fill>
</Rectangle>

<!-- Shadow on right-hand page -->
<Rectangle Canvas.Left="0" Canvas.Top="0" Width="20" Height="600">
    <Rectangle.Fill>
        <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
            <GradientStop Color="#40000000" Offset="0.0" />
            <GradientStop Color="#00000000" Offset="1.0" />
        </LinearGradientBrush>
    </Rectangle.Fill>
</Rectangle>

Figure 4 Échantillon de changement de page avec ombres XAML à l'intérieur des pages (Cliquer sur l'image pour l'agrandir)
Si quelque chose ne vous semble pas clair, vous pouvez démarrer une application à partir de PageTurnDemo. Il vous suffit de remplacer mon XAML pour les pages individuelles par un XAML qui vous est propre. Mes canevas de page ont une largeur de 400 et une hauteur de 544, mais vous pouvez modifier ces dimensions comme vous l'entendez.

Éléments internes de l'infrastructure
L'infrastructure de changement de page est implémentée dans PageTurn.js, que vous pouvez consulter dans le téléchargement de cette édition. La classe PageTurnFramework commence à quelques lignes du haut. JavaScript ne prend pas les classes en charge, mais cette infrastructure utilise la même structure prototype que celle utilisée par ASP.NET AJAX et de nombreuses applications Silverlight 1.0 pour simuler des classes dans JavaScript. Ce n'est qu'une illusion, mais elle est efficace.
Le constructeur de classe définit toutes les variables d'instance (l'équivalent des champs dans C#) nécessaires pour enregistrer l'état interne. Par exemple, l'instruction
   this._control = control;
déclare un « champ » nommé _control et l'initialise avec la référence de commande Silverlight passée au constructeur. En tout, environ 40 champs sont déclarés et utilisés pour tout maintenir, du chiffre de pourcentage complet pour un changement de page en cours (_percent) aux jetons représentant des gestionnaires d'événements inscrits (qui supprime des usages pour désinscrire des gestionnaires). Les champs enregistrent également des références à plusieurs objets XAML qui sont créés dynamiquement dans initializeFramework et des objets XAML inscrit avec des appels à addPage.
Le prototype PageTurnFramework contient toutes les méthodes PageTurnFramework. Les méthodes sont réparties en trois grandes catégories : les méthodes publiques telles qu'addPage et initializeFramework ; les gestionnaires d'événements, qui agissent en réponse aux événements de souris et les événements Storyboard.Completed utilisés en interne par l'infrastructure ; les méthodes privées, qui sont utilisées en interne par l'infrastructure mais ne sont pas conçues pour être appelées depuis l'extérieur. Les codes intéressants ne manquent pas, mais l'espace étant limité, vous devrez télécharger PageTurn.js pour les consulter.
Un des aspects intéressants de l'architecture d'infrastructure est la façon dont elle effectue les changements de page si le bouton de la souris est relâché (ou le curseur quitte la commande) pendant un changement de page. Au moment de l'initialisation, l'infrastructure utilise createFromXaml pour créer un objet Storyboard à utiliser comme minuteur. Il ajoute ensuite le Storyboard au canevas de changement de page (c'est une des raisons pour lesquelles vous devez faire une référence au canevas de changement de page au constructeur de classe) et inscrit un gestionnaire pour les événements Storyboard.Completed.
Pour terminer un changement de page incomplet, l'infrastructure appelle Storyboard.begin pour démarrer le minuteur. À chaque progression du minuteur, il avance la page d'un autre incrément (en utilisant la taille de l'incrémentation enregistrée dans le champ _step) et appelle à nouveau Storyboard.begin si le changement de page n'est toujours pas terminé. Vous pouvez visualiser cette procédure en tournant une page à moitié avant de relâcher le bouton de la souris. Selon le degré de progression du changement de page lorsque vous avez relâché le bouton, la page reviendra en position entièrement fermée ou entièrement ouverte.
La définition XAML du Storyboard est enregistrée dans la variable nommée _sb de la méthode initializeFramework. Vous pouvez vous demander pourquoi cette définition inclut un attribut x:Name dont la valeur est un GUID. Dans Silverlight 1.0, les Storyboards créés avec createFromXaml doivent être nommés ou la commande createFromXaml échouera. J'ai dû nommer le Storyboard, mais j'ai voulu m'assurer que son nom n'entrait en conflit avec d'autres Storyboards utilisés dans l'application. Donc, je l'ai nommé d'après un GUID.
Une autre partie intéressante de l'architecture est la façon dont l'infrastructure utilise les événements de souris. Pour les canevas représentant des pages, addPage inscrit des gestionnaires pour les événements MouseLeftButtonDown, MouseMove et MouseLeftButtonUp. Un changement vers la gauche commence lorsque l'on clique sur une page de droite et progresse au fur et à mesure que la souris est déplacée vers la gauche avec le bouton gauche enfoncé. De même, un changement vers la droite commence lorsque l'on clique sur un canevas de gauche et progresse au fur et à mesure que la souris est déplacée vers la droite. La méthode clé utilisée par les gestionnaires d'événements est _turnTo, qui fait avancer une page partiellement tournée jusqu'à la position indiquée par le paramètre percent-complete.
Un dernier aspect de l'infrastructure méritant d'être étudié de plus près est la façon dont elle utilise des transformateurs et des zones de découpage pour représenter des changements de page. initializeFramework crée deux objets PathGeometry : l'un destiné à servir de zone de découpage pour les pages de droite et l'autre pour les pages de gauche. Il crée également un objet TransformGroup contenant un RotateTransform et un TranslateTransform.
La figure 6 montre comment les zones de découpage et les transformateurs sont utilisés pour représenter une page partiellement tournée. Le triangle rouge est la zone de découpage utilisée sur la page de droite du haut. Au fur et à mesure que la souris se déplace vers la gauche, la zone de découpage diminue de manière à ce qu'une plus petite partie de la première page soit visible et une plus grande partie de la page de dessous apparaisse. Le triangle bleu représente la zone de découpage utilisée sur la page de gauche qui est révélé au fur et à mesure que le changement progresse et le rectangle jaune représente la portion de cette page qui est coupée.
Figure 6 Zones de découpage et transformateurs utilisés pour représenter les changements de page (Cliquer sur l'image pour l'agrandir)
Les commandes RotateTransform et TranslateTransform sont combinées pour positionner et orienter la page. (Et oui, vive la trigonométrie !) Au fur et à mesure que la souris se déplace vers la gauche, imaginez le rectangle jaune glissant vers la gauche et pivotant en position verticale. Alliez cela à une image mentale du triangle bleu et du triangle rouge devenant respectivement plus grand et plus petit et vous aurez une idée assez précise de la manière dont le changement de page fonctionne.

Conclusion
Avant même que l'encre de cette rubrique ne soit sèche, j'ai pensé à d'autres additions utiles à l'API PageTurnFramework. Par exemple, il pourrait être utile que l'infrastructure lance un événement à chaque fois qu'une page est tournée afin de pouvoir écrire des gestionnaires qui mettent à jour un autre contenu de la page. Il pourrait également être intéressant d'exposer des propriétés de contrôle de paramètres de changement de page clé, comme la largeur de l'ombre qui suit la progression du changement de page et la taille de l'incrémentation utilisée pour animer des changements incomplets.
N'hésitez pas à utiliser cette infrastructure dans vos propres projets et à la modifier. Envoyez-moi vos commentaires et vos éventuelles suggestions d'additions utiles à l'ensemble des fonctionnalités et à l'API. Je combinerai vos suggestions aux miennes et m'assurerai que la version 2.0 de l'infrastructure de changement page soit encore meilleure que la version 1.0.

Veuillez envoyer vos questions et commentaires à l’attention de Jeff à  wicked@microsoft.com.


Jeff Prosise participe à MSDN Magazine et il est l’auteur de plusieurs livres, notamment Programming Microsoft .NET. Il est également cofondateur de Wintellect (www.wintellect.com), une entreprise de conseil et de formation en logiciels spécialisée dans .NET Framework. Un commentaire sur cette rubrique ? Contactez Jeff à l'adresse wicked@microsoft.com.

Page view tracker