Vous êtes sur la page 1sur 518

Développez

des applications
Internet
avec
Silverlight 5

Patrice REY
Toutes les marques citées ont été déposées par leurs propriétaires respectifs.

La loi du 11 Mars 1957 n’autorisant aux termes des alinéas 2 et 3 de l’article 41,
d’une part, que les «copies ou reproductions strictement réservées à l’usage privé
du copiste et non destinées à une utilisation collective», et, d’autre part, que les
analyses et les courtes citations dans un but d’exemple et d’illustration, «toute
représentation ou reproduction intégrale, ou partielle, faite sans le consentement
de l’auteur ou de ses ayants droit ou ayant cause, est illicite» (alinéa 1er de l’article
40).
Cette représentation ou reproduction, par quelque procédé que ce soit,
constituerait donc une contre-façon sanctionnée par les articles 425 et suivants du
Code Pénal.

Titres déjà parus


Initiation au jeu 2D avec Silverlight 4
(ISBN : 978-2-8106-1168-3)
Les structures de données illustrées avec WPF et C#4
(ISBN : 978-2-8106-1387-8)
La programmation graphique 2D de WPF 4
(ISBN : 978-2-8106-1199-7)
La programmation graphique 3D de WPF 4
(ISBN : 978-2-8106-2086-9)
Guide pratique de la modélisation 3D avec WPF 4
(ISBN : 978-2-8106-1308-3)

Éditeur
Books on Demand GmbH,
12/14 rond point des Champs Élysées,
75008 Paris, France
Impression :
Books on Demand GmbH, Norderstedt, Allemagne
ISBN : 978-2-8106-2222-1
Dépôt légal : Mars 2012

Auteur
Patrice REY
85 rue de vincennes
App 23 B
33000 BORDEAUX

e-mail : reypatrice@orange.fr
Table des matières

Avant-propos ............................................................................................ 13
Chapitre 1 - Démarrez un projet Silverlight 5
1. L’environnement de développement Visual Studio 2010 .............. 18
2. La création d’un projet .................................................................. 20
2.1 - Un projet Silverlight seul ............................................................ 20
2.2 - Ma première page Silverlight .................................................. 23
2.3 - Un projet hébergé dans un site ASP.NET ............................... 26
3. Compilation et déploiement .......................................................... 28
4. Configuration de la page HTML test ................................................ 31
Chapitre 2 - L’utilisation de XAML
1. Les espaces de noms ..................................................................... 34
2. Le code-behind ................................................................................ 36
3. Les propriétés .................................................................................... 37
3.1 - Les propriétés simples .............................................................. 38
3.2 - Les propriétés complexes ........................................................ 39
3.3 - Les propriétés attachées ......................................................... 40
3.4 - Les propriétés implicites ........................................................... 41
3.5 - Les caractères spéciaux .......................................................... 42
4. Les événements ................................................................................. 42
5. Les ressources .................................................................................... 43
6. Les dictionnaires de ressources ........................................................ 45
6.1 - Création d’un dictionnaire ......................................................... 46
6.2 - Fusion des dictionnaires ............................................................. 47
7. Le databinding .................................................................................. 48
6 Table des matières

Chapitre 3 - Les panneaux et le système de disposition


1. La classe Panel .................................................................................. 54
2. Le conteneur Canvas ...................................................................... 56
3. Le conteneur StackPanel ................................................................ 57
4. Le conteneur Grid ............................................................................ 58
5. Le conteneur DockPanel ................................................................ 61
6. Le conteneur WrapPanel ................................................................ 63
7. Les propriétés impactant la disposition .......................................... 64
7.1 - Les dimensions ............................................................................ 64
7.2 - Les marges .................................................................................. 65
7.3 - L’alignement .............................................................................. 66
7.4 - La profondeur ............................................................................ 67
7.5 - La visibilité .................................................................................. 68
8. Les autres conteneurs ....................................................................... 69
8.1 - Le conteneur Border ................................................................. 69
8.2 - Le conteneur Viewbox ............................................................. 69
8.3 - Le conteneur ScrollViewer ....................................................... 71
Chapitre 4 - Propriétés de dépendances et événements routés
1. Les propriétés de dépendances ...................................................... 74
1.1 - L’exemple du rectangle ........................................................... 74
1.2 - Implémentation d’une propriété simple ................................. 76
1.3 - Implémentation d’une propriété attachée ........................... 84
2. La gestion de l’interactivité .............................................................. 88
2.1 - Les événements routés ............................................................. 88
2.2 - Le routage de type bubbling .................................................. 91
2.3 - Les événements liés au clavier ................................................ 92
2.4 - Le survol et le positionnement de la souris .............................. 95
2.5 - La gestion du clic de la souris .................................................. 97
2.6 - La gestion de la molette de la souris ...................................... 99
2.7 - La capture de la souris ............................................................. 101
2.8 - Le focus ...................................................................................... 103
2.9 - Les commandes ........................................................................ 104
Table des matières 7

Chapitre 5 - Les contrôles Silverlight


1. Le texte statique TextBlock ............................................................... 110
2. Le contrôle Image ............................................................................ 114
3. La classe ContentControl ................................................................ 116
4. Les boutons ........................................................................................ 118
4.1 - Le contrôle HyperlinkButton ....................................................... 119
4.2 - Les contrôles CheckBox et RadioButton ................................... 120
5. Le contrôle ToolTip ............................................................................ 123
6. Le contrôle Popup ............................................................................ 126
7. La classe ItemsControl ..................................................................... 128
7.1 - Le contrôle ListBox ...................................................................... 129
7.2 - Le contrôle ComboBox ............................................................. 132
7.3 - Le contrôle TabControl ............................................................. 134
8. Les contrôles gérant le texte ........................................................... 137
8.1 - Le contrôle TextBox ................................................................... 137
8.2 - Le contrôle PasswordBox ......................................................... 139
8.3 - Le contrôle AutoCompleteBox ............................................... 140
8.4 - La classe TextElement .............................................................. 143
8.5 - Le contrôle RichTextBox ........................................................... 146
8.6 - Le contrôle RichTextBlock ........................................................ 153
9. La classe RangeBase ....................................................................... 154
9.1 - Le contrôle ProgressBar ........................................................... 155
9.2 - Le contrôle Slider ...................................................................... 157
9.3 - Le contrôle ScrollBar ................................................................ 158
10. Les contrôles Calendar et DatePicker ......................................... 159
Chapitre 6 - Le modèle d’application Silverlight
1. La classe Application ....................................................................... 164
2. Passage de paramètres dans le plug-in ......................................... 170
3. Ecran d’accueil personnalisé ......................................................... 175
4. Les fichiers de ressources ................................................................. 178
4.1 - Les fichiers embarqués .............................................................. 179
4.2 - Les fichiers téléchargés ............................................................. 181
8 Table des matières

5. Les bibliothèques de classes ........................................................... 186

Chapitre 7 - La navigation
1. Le chargement des contrôles ......................................................... 192
1.1 - Les contrôles embarqués ......................................................... 192
1.2 - Naviguer d’une page à l’autre .............................................. 196
2. La boite de dialogue personnalisée ............................................ 201
3. Le contrôle Frame ............................................................................ 205
3.1 - Utilisation d’un Frame ............................................................... 205
3.2 - Le mapping d’URI .................................................................... 207
4. Authentification avec INavigationContentLoader ....................... 209
Chapitre 8 - Le graphisme
1. Les formes géométriques basiques ............................................... 216
1.1 - La classe Shape .......................................................................... 216
1.2 - La classe Rectangle ................................................................... 217
1.3 - La classe Ellipse ........................................................................... 218
1.4 - La classe Line .............................................................................. 219
1.5 - La classe Polyline ....................................................................... 221
1.6 - La classe Polygon ...................................................................... 222
2. Les tracés et la géométrie ............................................................... 224
2.1 - La ligne droite ........................................................................... 228
2.2 - La ligne courbe ........................................................................ 228
2.3 - Les courbes de Bézier ............................................................. 230
2.4 - Mini-langage de tracé ............................................................ 232
2.5 - La découpe (le clipping) ....................................................... 233
3. Les transformations ........................................................................... 235
3.1 - Le déplacement (TranslateTransform) .................................. 236
3.2 - La mise à l’échelle (ScaleTransform) .................................... 237
3.3 - La rotation (RotateTransform) ................................................ 238
3.4 - L’inclinaison (SkewTransform) ................................................. 240
3.5 - Regroupement de transformations ....................................... 241
3.6 - La classe CompositeTransform .............................................. 243
3.7 - La transformation matricielle ................................................. 243
Table des matières 9
4. Les projections en perspective ......................................................... 245

Chapitre 9 - Les pinceaux et les images


1. Les pinceaux ..................................................................................... 250
1.1 - Le pinceau uni ............................................................................ 251
1.2 - Le pinceau dégradé ................................................................. 253
1.3 - Le pinceau image ..................................................................... 255
2. Opacité et masque d’opacité ........................................................ 257
3. Les effets graphiques ....................................................................... 259
3.1 - L’ombre portée ......................................................................... 260
3.2 - L’effet de flou ............................................................................ 261
3.3 - Les effets personnalisés ............................................................ 262
4. Les images avec WriteableBitmap ................................................. 263

Chapitre 10 - Les animations


1. Les règles de l’animation ............................................................... 270
2. La classe Timeline ............................................................................. 271
3. Une animation basique en XAML et par code .............................. 273
4. Contrôler une animation .................................................................. 279
5. Les animations réalistes ................................................................... 283
5.1 - Principe ....................................................................................... 283
5.2 - L’effet de rebondissement avec BounceEase ....................... 286
5.3 - L’effet du ressort oscillant avec ElasticEase ........................... 288
5.4 - L’effet du freinage avec PowerEase ...................................... 291
5.5 - Fonction d’accélération personnalisée ................................. 295
6. Les animations d’images clés .......................................................... 300
6.1 - Avec l’interpolation linéaire .................................................... 302
6.2 - Avec l’interpolation par palier ................................................ 304
6.3 - Avec l’interpolation par courbe de Bézier ............................ 307
6.4 - Avec l’interpolation réaliste .................................................... 309
7. Le rendu image par image ............................................................. 312
8. L’accélération matérielle ................................................................. 321
9. Le cache de composition ............................................................... 322
10 Table des matières

Chapitre 11 - L’audio et la vidéo


1. Les formats audio et vidéo supportés ........................................... 328
2. Le contrôle MediaElement ............................................................. 328
3. La prise en charge de l’audio ....................................................... 329
4. La prise en charge de la vidéo ..................................................... 336
5. Le pinceau vidéo ............................................................................. 341
6. La vidéo réfléchie ............................................................................. 343
7. L’acquisition vidéo ............................................................................ 345
Chapitre 12 - Les styles et les modèles
1. Les styles ............................................................................................ 352
1.1 - Définition d’un style .................................................................. 352
1.2 - Le style implicite ....................................................................... 355
1.3 - L’héritage de style ................................................................... 357
1.4 - Style et databinding ................................................................ 358
2. Les modèles (template) .................................................................. 362
2.1 - Visualiser un template ............................................................... 362
2.2 - Le ContentPresenter ................................................................. 363
2.3 - Personnaliser un template ....................................................... 365
2.4 - La gestion des états visuels ..................................................... 370
Chapitre 13 - La gestion des données
1. Visualiser des données ...................................................................... 378
1.1 - La propriété DataContext ....................................................... 378
1.2 - Le contrôle Label ..................................................................... 380
1.3 - Les classes d’attributs .............................................................. 382
1.4 - Le contrôle DescriptionViewer ............................................... 387
2. Notification et validation ................................................................. 391
3. Le contrôle DataGrid ....................................................................... 400
3.1 - Afficher des données ............................................................... 400
3.2 - Les fonctionnalités .................................................................... 403
3.3 - La personnalisation des colonnes .......................................... 405
3.4 - Le détail d’une ligne ................................................................ 415
Table des matières 11

3.5 - Regrouper des lignes ................................................................ 416


3.6 - Utiliser la pagination ................................................................. 420
4. Le contrôle TreeView ....................................................................... 424
4.1 - Une arborescence basique ...................................................... 426
4.2 - Une arborescence liée à des données .................................. 428
Chapitre 14 - La représentation graphique des données
1. Constituer une collection de données ........................................... 438
1.1 - La classe ObjectCollection ....................................................... 438
1.2 - L’interface IEnumerable<T> ...................................................... 440
2. Les aires graphiques .......................................................................... 443
3. Les graphiques à barres horizontales ............................................ 446
4. Les graphiques à barres verticales ................................................ 450
5. Les graphiques à lignes .................................................................. 454
6. Les graphiques à bulles .................................................................. 456
7. Les graphiques à secteurs ............................................................. 458
Chapitre 15 - La modélisation 3D
1. Ma première scène 3D ..................................................................... 465
2. Modéliser un cube ............................................................................ 480
3. Ajouter une texture ........................................................................... 484
4. L’éclairage ........................................................................................ 488
5. Dupliquer des objets 3D .................................................................... 492
6. Les mouvements des objets 3D ........................................................ 498

Index ........................................................................................................... 505


Avant-propos

Quoi de plus passionnant que de développer ses propres applications Internet qui,
de nos jours, ne cessent de se développer sur de nombreuses plateformes.
Microsoft a mis en ligne la version n°5 de Silverlight, une machine à gagner qui
permet de développer des applications web riches. Silverlight 5, dont le modèle
de programmation est basé sur .NET, se distingue particulièrement par la facilité
avec laquelle il permet de mélanger et d’utiliser de nombreux formats et médias
existants : texte, données XML, vidéo, audio, animations, etc.
De plus, Silverlight a été pensé pour une prise en main rapide grâce à deux familles
d’outils (Visual Studio et Suite Expression) et pour une ubiquité de déploiement,
permettant aux applications Silverlight de s’exécuter sur Windows et sur Mac OS X
dans les navigateurs web Internet Explorer, Safari et Firefox.
Le framework .NET est un code pré-écrit par Microsoft et distribué gratuitement
sous forme d’un runtime qui doit être en exécution sur la machine hôte (Flash, de
chez Adobe, procède de la même façon).
La version 5 apporte de grandes nouveautés, avec en particulier la véritable
programmation 3D par l’utilisation d’une implémentation basée sur le framework
XNA.

Les logiciels requis pour le développement

Pour réaliser plus facilement des programmes écrits en langage C#, il est préférable
d’utiliser un environnement de développement intégré ou IDE (Integrated
Development Environment). Cet IDE permet de développer, compiler et exécuter
un programme dans un langage donné qui sera, dans ce livre, le C# (se prononce
C Sharp). Microsoft propose comme IDE soit Visual Studio 2010 (dans sa version
payante avec 30 jours d’essai gratuit), soit Visual C# 2010 Express (dans sa version
allégée mais gratuite).
Puissante interface IDE garantissant la production d’un code de qualité, Visual
Studio 2010 intègre de nouvelles fonctionnalités qui simplifient le processus de
développement d’application, de la conception au déploiement.
14 Avant-propos
Pour télécharger et installer l’environnement de développement intégré, ouvrez
votre navigateur et allez sur les pages web suivantes au choix:
• pour télécharger Visual C# 2010 Express: http://msdn.microsoft.com/fr-fr/
express/aa975050.aspx
• pour télécharger une version d’évaluation Visual Studio 2010 Ultimate avec
une période d’essai gratuite de 30 jours: http://www.microsoft.com/france/
visualstudio/download
Pour développer des applications Silverlight 5, il faut installer plusieurs
composants fournis gratuitement par Microsoft. Vous devez aller sur la page des
téléchargements de Microsoft à l’adresse suivante http://www.silverlight.net/
downloads pour récupérer les composants (figure A1).
FIGURE A1

La liste des composants à télécharger et à installer est la suivante, dans cet ordre:
• cliquez sur le lien Service Pack1 pour Visual Studio 2010: composant à installer si
votre Visual Studio 2010 n’est pas en SP1.
• cliquez sur le lien Silverlight 5 Tools for Visual Studio 2010 SP1: composant à
installer pour l’utilisation de Silverlight 5 dans Visual Studio.
• cliquez sur le lien Silverlight 5 Toolkit: composant à installer pour l’utilisation des
contrôles Silverlight 5 supplémentaires dans Visual Studio.
• cliquez sur le lien Silverlight 5 Developer Runtime for Windows (32 bit) ou bien
Silverlight 5 Developer Runtime for Windows (64 bit) selon votre plateforme:
composant à installer pour la visualisation des applications Silverlight 5 dans le
navigateur.
Avant-propos 15

Le contenu du livre

Les différents chapitres permettent d’apprendre à développer des applications


Internet pour Silverlight 5. Les chapitres abordent les points suivants:
• le chapitre 1 vous montre la démarche pour développer une application
Silverlight 5, pour la compiler, et pour effectuer un déploiement dans le
navigateur Internet.
• le chapitre 2 vous explique l’utilisation de XAML au travers des notions
importantes à maitriser que sont les espaces de noms, les propriétés, les
événements, les ressources, les dictionnaires de ressources et le databinding.
• le chapitre 3 vous explique ce qu’est le système de disposition avec notamment
l’utilisation et la personnalisation des panneaux comme le Grid, le Canvas, le
StackPanel, le DockPanel, le WrapPanel, et différents autres conteneurs.
• le chapitre 4 traite de la notion de propriété de dépendance et de la notion des
événements routés; ces deux notions sont essentielles dans la programmation
en C#.
• le chapitre 5 vous montre l’utilisation et la personnalisation de l’ensemble
des contrôles Silverlight couramment utilisés comme les contrôles TextBlock,
Image, Button, ToolTip, Popup, ListBox, ComboBox, TabControl, TextBox,
PasswordBox, AutoCompleteBox, RichTextBox, ProgressBar, Slider, ScrollBar,
Calendar et DatePicker.
• le chapitre 6 vous explique le modèle Silverlight au travers de l’objet Application,
du passage des paramètres dans le plugin, l’écran d’accueil personnalisé pendant
le téléchargement, les fichiers de ressources et les bibliothèques de classes.
• le chapitre 7 vous explique la navigation avec le chargement des contrôles, la
navigation d’une page à l’autre, la boite de dialogue personnalisée, l’objet Frame
et l’authentification utilisateur.
• le chapitre 8 traite le graphisme dans Silverlight avec les formes géométriques,
les tracés géométriques et les transformations géométriques.
• le chapitre 9 vous explique les pinceaux, les images, l’opacité, le masque
d’opacité et les effets graphiques.
• le chapitre 10 traite de l’animation avec le contrôle des animations, les animations
réalistes, les animations d’images clés, le rendu image par image, l’accélération
matérielle et le cache de composition.
• le chapitre 11 traite de l’audio et de la vidéo avec le contrôle MediaElement, la
prise en charge de l’audio et de la vidéo, le pinceau vidéo, la vidéo réfléchie et
16 Avant-propos
l’acquisition vidéo.
• le chapitre 12 vous explique la notion de style et la notion de modèle; ces deux
notions sont essentielles dans la programmation des interfaces utilisateur
évoluées.
• le chapitre 13 traite de la gestion des données avec la visualisation des données,
la notification et la validation des données, le contrôle DataGrid et le contrôle
TreeView.
• le chapitre 14 traite de la représentation graphique des données avec la notion
de constitution d’une collection de données, et la représentation graphique des
aires, des diagrammes et des graphiques à barres.
• le chapitre 15 traite de la modélisation 3D qui est la grande nouveauté de
Silverlight 5, avec l’utilisation du framework XNA pour modéliser des objets 3D,
pour les ajouter dans une scène 3D avec un éclairage, des textures, une caméra
et un positionnement.
Toutes les notions abordées dans ce livre font l’objet d’une utilisation directe et
pratique que vous trouverez dans le code source en téléchargement gratuit. Il
est ainsi possible de suivre aisément la réalisation technique des programmes en
fonction des chapitres du livre.

Les liens de téléchargement

Tout le code source de cet ouvrage pourra être téléchargé gratuitement à l’adresse
web suivante:
http://www.reypatrice.fr

Vous trouverez dans l’arborescence des fichiers du projet, un dossier pour


chacun des chapitres. Chaque dossier contient tous les éléments nécessaires de
programmation qui sont expliqués dans le livre. De cette façon, vous serez capable
de suivre et de programmer assez rapidement et assez facilement. C’est une
démarche volontaire, dans un but pédagogique, pour progresser rapidement.

Bonne lecture à tous.


CHAPITRE 1 DANS CE CHAPITRE
• L’environnement de

Démarrez un projet
développement Visual
Studio 2010
• Création d’un projet
Silverlight 5 • Compilation et
déploiement d’une
application
• Configuration de la
Ce livre porte sur la version 5 de Silverlight, qui est page HTML test

une technologie de développement d’application


cliente graphiquement riche. Elle est intégrée
au navigateur HTML ou installée sur le poste de
travail, pour les systèmes Mac OS X et Windows.
L’exécution d’un plug-in Silverlight dans une page
HTML nécessite la présence d’un runtime sur la
machine, disponible pour Mac OS X et Windows.
Silverlight exploite une version multiplate-forme et
allégée du framework .NET, dans laquelle seules les
fonctionnalités essentielles sont présentes. Dans ce
livre, nous utiliserons le langage compilé C# pour
les développements.
L’environnement de développement intégré
Visual Studio 2010 est l’outil par excellence pour
développer des applications Silverlight.
Dans ce chapitre, nous verrons comment créer,
compiler et déployer une application Silverlight
en utilisant Visual Studio 2010. Lors de cet
apprentissage, nous aurons un aperçu sur la
façon dont les contrôles Silverlight réagissent aux
événements. Ensuite nous verrons comment les
applications Silverlight sont compilées et déployées
sur le web. Enfin nous aborderons les deux options
qui permettent l’hébergement d’un contenu
Silverlight: avec le cas d’une page HTML classique
ou bien avec le cas d’une page ASP.NET.
18 Développez des applications Internet avec Silverlight 5

Actuellement, les plates-formes supportées par Silverlight 5 sont Windows (2000


SP4, XP SP2, Vista, Server 2008, Windows 7) et Mac OS X (à partir de la version
Tiger 10.4.11).
Les navigateurs supportés sont Internet Explorer (depuis la version 6), Firefox 3.6
et plus (sous Windows et Mac OS X), Safari 4 et plus (sous Mac OS X), et Chrome
12 et plus (sous Windows).
Le détail actualisé des configurations requises est disponible à l’adresse http://
www.microsoft.com/getsilverlight, en sélectionnant l’onglet System Requirements.

1 - L’environnement de développement Visual Studio 2010

L’environnement de développement intégré Visual Studio 2010 est l’outil par


excellence pour le développement d’applications Silverlight, que ce soit dans sa
version gratuite (Visual C# Express) ou bien dans ses versions payantes (Ultimate
et Professionnal).
Le SDK (Software Development Kit) Silverlight 5 intègre des bibliothèques de
classes d’extension redistribuables. Il contient la documentation, les outils de
développement de base et les assemblys d’extension redistribuables apportant
des fonctionnalités supplémentaires à celles du runtime de base.
Pour créer des projets Silverlight 5 dans Visual Studio 2010, il est nécessaire
d’installer le Silverlight 5 Tools for Visual Studio 2010, téléchargeable gratuitement
sur le site de Microsoft.
La figure 1.1 montre le visuel de Visual Studio 2010 en action. Il est possible
d’ajouter des contrôles et de les modifier simultanément dans le mode XAML
(mode de programmation) et le mode Design (mode graphique). A partir de la
boite à outils, les contrôles peuvent être ajoutées par un glisser-déplacer dans
les 2 modes. La fenêtre des propriétés permet de personnaliser les propriétés
du contrôle sélectionné. L’explorateur de solutions visualise l’arborescence des
fichiers du projet.
Il existe aussi un autre outil de développement qui est Expression Blend 5,
Copyright 2012 Patrice REY

totalement compatible avec Silverlight 5 et Visual Studio 2010.


Expression Blend est un outil de développement orienté pour la conception
visuelle. Il intègre un éditeur XAML plus riche et plus ergonomique que celui de
Visual Studio, notamment pour définir graphiquement un template d’un contrôle
ou bien une animation. Dans ce livre, nous utiliserons uniquement Visual Studio
2010 pour les développements.
CHAPITRE 1 □ Démarrez un projet Silverlight 5 19
FIGURE 1.1

la boite à outils qui la page XAML en l’explorateur de


contient tous les mode graphique qui solutions qui
contrôles qui peuvent permet de déplacer, permet d’ouvrir,
être ajoutés par un redimensionner et d’ajouter et de
glisser-déplacer modifier les contrôles modifier des fichiers

la page XAML en mode la fenêtre des


programmation qui
propriétés qui
permet d’ajouter et de permet de
modifier les contrôles
personnaliser les
propriétés des
contrôles
20 Développez des applications Internet avec Silverlight 5

2 - La création d’un projet

Vous pouvez créer deux types de projet pour afficher un contenu Silverlight avec
Visual Studio 2010 (ou avec Expression Blend):
• un site web ordinaire contenant des pages HTML: dans ce cas, le point d’entrée
de votre application Silverlight est un fichier HTML classique qui contient une
région pour l’hébergement du contenu Silverlight.
• un site web ASP.NET: dans ce cas, Visual Studio génère deux projets; le premier
projet contient les fichiers concernant l’application Silverlight; le deuxième
projet contient les fichiers nécessaires à l’émulation d’un site web ASP.NET et
déploie vos fichiers Silverlight; le point d’entrée de votre application Silverlight
peut être soit une page HTML classique, soit une page ASP.NET incluant du
contenu généré par le serveur.
Votre application Silverlight fonctionnera de la même façon dans les deux cas, le
navigateur recevra un document HTML dans lequel il y aura une région incluant un
contenu Silverlight. Cependant, si votre application doit télécharger des ressources
sous forme de fichiers par exemple ou bien récupérer des données précises dans
une base de données sur un serveur, la solution sera d’être dans une configuration
de serveur. Généralement on choisira la deuxième option comme étant la meilleure
approche pour un environnement de production.

2.1 - Un projet Silverlight seul

La création d’un projet se fait en choisissant le menu fichier, puis nouveau,


puis projet. Une fenêtre intitulée nouveau projet s’ouvre (figure 1.2). Dans les
modèles installés, on sélectionne silverlight. On cible le .NET framework 4 dans
la liste déroulante. On clique sur application silverlight pour indiquer que l’on
souhaite réaliser un projet pour la création d’une application Internet enrichie
utilisant Silverlight. On donne un nom à l’application, on choisit un emplacement
Copyright 2012 Patrice REY

de stockage sur l’ordinateur (bouton parcourir...) et on coche créer un répertoire


pour la solution. Un clic sur le bouton OK permet à Visual Studio de procéder à la
génération des fichiers et dossiers.
Une deuxième fenêtre intitulée nouvelle application Silverlight s’ouvre. On décoche
la première case à cocher et on choisit silverlight 5 dans la liste déroulante des
options (figure 1.3).
CHAPITRE 1 □ Démarrez un projet Silverlight 5 21
FIGURE 1.2

2
3

4
5

FIGURE 1.3

Visual Studio génère alors la solution de projet avec les fichiers et dossiers requis.
L’explorateur de solutions visualise de façon arborescente cet ensemble (figure
1.4).
22 Développez des applications Internet avec Silverlight 5
En y regardant de plus près, on trouve:
• App.xaml et App.xaml.cs: ce sont les fichiers de configuration de l’application
Silverlight; ils permettent de définir des ressources utilisables par toutes les
pages de l’application; ils permettent de programmer les événements de
lancement, d’arrêt et d’erreur, de l’application; ils permettent enfin de définir
la page MainPage.xaml comme page de démarrage.
• MainPage.xaml : ce fichier définit l’interface utilisateur (les contrôles, les
images et les textes) de votre première page; MainPage est une classe qui
dérive de UserControl et qui est personnalisée en fonction de ce que vous
souhaitez créer.
• MainPage.xaml.cs: ce fichier contient le code de programmation C# pour
implémenter les événements en fonction des actions de l’utilisateur.
FIGURE 1.4

En plus de cela, on trouve, dans le dossier properties, un fichier AppManifest.


xml qui contient une liste des assemblys utilisées par l’application, et un fichier
AssemblyInfo.cs qui contient les informations du projet (nom, version, etc.). Ces
fichiers sont générés par Visual Studio et sont mis à jour automatiquement.
En générant la solution (avec le raccourci clavier F6 ou bien par le menu générer
puis générer la solution), Visual Studio génère l’application dans un sous-dossier
Copyright 2012 Patrice REY

de référence intitulé Debug dans le dossier Bin (figure 1.5). Pour afficher tous les
dossiers et fichiers contenus dans le répertoire de la solution, il suffit de cliquer sur
l’icône afficher tous les fichiers dans l’explorateur de solutions (figure 1.5). On voit
qu’une page HTML, intitulée SilverlightApplication6TestPage.html, a été générée.
Elle contient dans la balise <object> les paramètres de configuration pour afficher
l’application Silverlight.
CHAPITRE 1 □ Démarrez un projet Silverlight 5 23
FIGURE 1.5

2.2 - Ma première page Silverlight

Pour personnaliser notre première page Silverlight, on double clique sur le fichier
MainPage.xaml et dans l’éditeur XAML, on ajoute les balises requises. La figure 1.6
montre les boutons de l’éditeur que vous pouvez utiliser pour plus de pratique et
de clarté lors de la programmation:
• la possibilité de régler le zoom de la vue graphique par l’intermédiaire de la
glissière de grossissement (repère n°1).
• un double clic sur l’onglet Design ou sur l’onglet XAML permet de passer l’onglet
correspondant en une vue unique (repère n°2).
• un clic sur les doubles flèches verticales pour intervertir les 2 vues (repère n°3).
• un clic sur les repères de positionnement des conteneurs permet de basculer
en mode horizontal ou en mode vertical (repère n°4).
• un clic sur le repère de visibilité permet de rendre visible ou non l’onglet du
dessous (repère n°5).
Depuis le boite à outils, par un glisser déplacer, on ajoute un contrôle de type
TextBlock et un bouton de type Button. Après les avoir positionnés sur la grille
intitulée LayoutRoot (propriété Name), on renomme le nom du texte par x_text
et le nom du bouton par x_btn. On change le texte qui s’affiche sur le bouton
par la chaîne cliquez moi (propriété Content du bouton). Comme on veut pouvoir
cliquer sur le bouton et provoquer le changement du texte x_text, on ajoute un
gestionnaire d’événement pour l’événement Click.
24 Développez des applications Internet avec Silverlight 5
FIGURE 1.6

1
3

4 5

2 2

La figure 1.7 visualise la démarche courante employée dans l’éditeur XAML:


• dans la balise <Button>, on positionne le curseur et en appuyant sur la barre
d’espace, l’intellisense de Visual Studio affiche les choix possibles (repère n°1).
• on défile la liste déroulante jusqu’à l’affichage de l’événement Click (repère
n°2).
• l’événement Click est écrit et dans la liste déroulante proposée, on choisit
nouveau gestionnaire (repère n°3); on appuie sur la touche TAB (repère n°4) et
l’éditeur remplit automatiquement le gestionnaire avec un nom (x_btn_Click).
• en ouvrant ensuite le fichier MainPage.xaml.cs, le code de programmation
pour l’événement Click est écrit (repère n°5); il ne reste plus qu’à l’implémenter
pour ce que l’on souhaite exécuter comme instructions.
Copyright 2012 Patrice REY

Nous souhaitons que le clic sur le bouton remplace le texte de x_text par un autre
texte. Pour cela on ajoute un code qui modifie la propriété Text de x_text, de type
TextBlock.
La modification de la propriété Text du TextBlock x_text se fait de la manière
suivante:
CHAPITRE 1 □ Démarrez un projet Silverlight 5 25
//evenement clic sur bouton
private void x_btn_Click(object sender, RoutedEventArgs e) {
x_text.Text = «vous avez cliqué sur le bouton»;
}

FIGURE 1.7

1 2

Sur la figure 1.8, on visualise le résultat obtenu dans Internet Explorer (repère
n°1) et dans Firefox (repère n°2). En cliquant sur le bouton, le champ x_text est
remplacé par un autre texte (repère n°3).
Il faut remarquer que l’appel de la page test se fait directement par l’appel d’un
fichier (SilverlightApplication6TestPage.html) dans le système de fichiers de
l’ordinateur.
26 Développez des applications Internet avec Silverlight 5
FIGURE 1.8

2.3 - Un projet hébergé dans un site ASP.NET

Le fait d’utiliser les technologies serveur permet de pouvoir faire interagir


l’application Silverlight avec des fichiers et des données qui sont stockés sur un
serveur.
La création d’un projet avec l’émulation des technologies serveur, se fait en
choisissant le menu fichier, puis nouveau, puis projet (de façon identique à la
création d’un projet Silverlight seul comme au paragraphe 2.1). Dans la deuxième
fenêtre qui s’ouvre (figure 1.9), il faut cocher héberger l’application ... (repère n°1),
puis sélectionner projet d’application web dans la liste déroulante (repère n°2),
enfin sélectionner silverlight 5 (repère n°3).
Copyright 2012 Patrice REY

Après avoir compilé la solution, l’explorateur de solutions génère un fichier


SilverlightApplication7.xap dans le dossier ClientBin (figure 1.10). La page
SilverlightApplication7TestPage.aspx est par défaut la page de démarrage pour
afficher l’application. Si nous n’avons pas besoin de la technologie ASP.NET, il est
possible de choisir alors la page SilverlightApplication7TestPage.html comme page
de démarrage par défaut. Pour cela, en faisant un clic droit sur le fichier, on choisit
CHAPITRE 1 □ Démarrez un projet Silverlight 5 27
définir comme page de démarrage dans le sous-menu (repère n°2).
FIGURE 1.9

FIGURE 1.10

2
28 Développez des applications Internet avec Silverlight 5
En testant le projet (figure 1.11), on voit bien que Visual Studio procède à
l’émulation d’un serveur web, en appelant une page de type aspx (repère n°1) ou
une page de type html (repère n°2), en fonction du choix de la page de démarrage
par défaut choisie.
FIGURE 1.11

3 - Compilation et déploiement

Quand vous compilez le projet, Visual Studio utilise le même compilateur csc.exe
que celui des applications .NET écrites en C#. Si on regarde, après la compilation,
à l’intérieur du sous-dossier Debug du dossier Bin (figure 1.12), on s’aperçoit qu’il
y a un ensemble de fichiers générés:
• le fichier SilverlightApplication7.pdb qui est un fichier qui contient des
informations nécessaires à Visual Studio pour le débogage.
• le fichier AppManifest.xaml qui est un fichier qui liste les assemblys nécessaires
au déploiement.
• le fichier SilverlightApplication7TestPage.html qui est le fichier de point
d’entrée, appelé par le navigateur client.
• le fichier SilverlightApplication7.xap qui est une archive contenant tout ce qui
Copyright 2012 Patrice REY

est nécessaire au déploiement de l’application Silverlight.


• le fichier SilverlightApplication7.dll qui est un fichier du projet compilé.
Bien évidemment, vous pouvez changer le nom de l’assembly, le nom de l’espace
des noms par défaut, et le nom du fichier XAP (figure 1.13). Pour accéder aux
propriétés du projet, il suffit de faire un clic droit sur le nom du projet dans
l’explorateur de solutions, puis de choisir le sous-menu propriétés.
CHAPITRE 1 □ Démarrez un projet Silverlight 5 29
FIGURE 1.12

FIGURE 1.13
30 Développez des applications Internet avec Silverlight 5
Pour comprendre le modèle de déploiement de Silverlight (figure 1.14), après
avoir localisé le fichier au format xap, on en fait une copie et on le renomme au
format zip (format des archives compressées). On décompresse l’archive et on
s’aperçoit qu’elle contient, entre autre, le fichier AppManifest.xaml et le fichier
SilverlightApplication7.dll. Si l’application avait besoin d’autres assemblys pour
son fonctionnement, on y trouverait les fichiers complémentaires au format dll
(comme par exemple System.Windows.Controls.dll).
Le fait d’utiliser un format compressé (format xap) est très pratique. Le temps
de téléchargement se voit ainsi réduit (transfert du fichier xap). Quand le fichier
xap est complètement téléchargé sur le poste client, il est alors décompressé. A
remarquer que le fait d’avoir un simple fichier xap nécessaire au déploiement,
confère une simplicité lors du déploiement sur le serveur.
FIGURE 1.14

Chaque projet Silverlight contient un dossier références qui référence un ensemble


d’assemblys qui sont présentes dans le runtime Silverlight sur la machine cliente.
Par conséquent, il n’est pas nécessaire qu’elles soient ajoutées au fichier xap de
déploiement. Ces assemblys, référencées par défaut, sont:
• mscorlib.dll: assembly qui contient la plus grande part fondamentale du
framework .NET (les types de données, les exceptions, les collections génériques,
les classes de support pour les fichiers, le débogage, le multithreading, etc.).
• System.dll: assembly qui contient des collections génériques additionnelles,
des classes qui traitent des URI et des classes qui se chargent des expressions
régulières.
• System.Core.dll: assembly qui contient les classes nécessaires au support de
LINQ.
Copyright 2012 Patrice REY

• System.Net.dll: assembly qui contient les classes nécessaires au support des


réseaux, au support de l’interfaçage des connections internet.
• System.Windows.dll: assembly qui contient les classes nécessaires à la
construction des interfaces utilisateur.
• System.Windows.Browser.dll: assembly qui contient des classes nécessaires à
l’interaction avec des éléments HTML.
CHAPITRE 1 □ Démarrez un projet Silverlight 5 31
• System.Xml.dll: assembly qui contient des classes nécessaires au traitement
XML.
FIGURE 1.15

ensemble d’assemblys
déjà présentes dans le
runtime Silverlight

4 - Configuration de la page HTML test

Le corps de la page HTML générée contient les spécifications du plug-in Silverlight.


L’application est spécifiée au moyen d’une balise <object>, de ses attributs et de
ses éléments au moyen de balises <param> imbriquées. L’attribut data de la balise
<object> permet d’optimiser les performances pour certains navigateurs, l’attribut
type indique le type MIME correspondant à la version 2 de Silverlight (version
stabilisée), et les attributs width et height spécifient la taille de la zone de rendu
du plug-in Silverlight.
Les éléments <param> permettent de spécifier et de configurer l’application
Silverlight:
• source indique l’URL du paquet Silverlight contenant l’application,
• minRuntimeVersion indique la version du runtime requise,
• autoUpgrade indique si une mise à jour automatique du runtime doit être
lancée si une version antérieure est détectée,
• background indique la couleur de fond de la fenêtre du plug-in,
• onError spécifie le gestionnaire d’événements JavaScript à appeler en cas
d’erreur.
Le code minimal de configuration du plug-in Silverlight est le suivant:
<object data= «data:application/x-silverlight-2,»
type= «application/x-silverlight-2» width= «100%» height= «100%»>
<param name= «source» value= «ClientBin/SilverlightApplication7.xap»/>
<param name= «onError» value= «onSilverlightError» />
<param name= «background» value= «white» />
<param name= «minRuntimeVersion» value= «5.0.61118.0» />
32 Développez des applications Internet avec Silverlight 5
<param name= «autoUpgrade» value= «true» />
<a href= «http://go.microsoft.com/fwlink/?LinkID=149156&v=5.0.61118.0»
style= «text-decoration:none»>
<img src= «http://go.microsoft.com/fwlink/?LinkId=161376» alt= «Télécharger
Microsoft Silverlight» style= «border-style:none»/>
</a>
</object>

Le contenu de l’élément <object> décrit le code HTML alternatif à utiliser en cas


de problème de chargement de l’application comme dans le cas d’un navigateur
non supporté, ou bien le cas d’un navigateur où le runtime n’est pas installé. Il est
conseillé d’enrichir ce code alternatif par défaut.
D’autres balises <param> sont disponibles pour configurer le plug-in Silverlight.
Les principales sont:
• enableHtmlAccess: booléen qui indique si le plug-in peut accéder aux balises
HTML de la page,
• initParams: chaîne que vous pouvez utiliser pour passer des informations
personnalisées au plug-in,
• splashScreenSource: qui indique le chemin d’un fichier XAML à afficher le
temps du téléchargement du fichier xap de l’application,
• windowless: booléen qui spécifie le mode de rendu en fenêtre,
• onSourceDownloadProgressChanged: indique le gestionnaire d’événement
JavaScript à utiliser le temps du téléchargement,
• onSourceDownloadComplete: indique le gestionnaire d’événement JavaScript
à utiliser quand le téléchargement est terminé,
• onLoad: indique le gestionnaire d’événement JavaScript à utiliser quand le
fichier xap est téléchargé et que votre première page doit être initialisée,
• onResize: indique le gestionnaire d’événement JavaScript à utiliser lors du
redimensionnement du navigateur.
Copyright 2012 Patrice REY
CHAPITRE 2 DANS CE CHAPITRE
• Les espaces de noms
• Le code-behind
L’utilisation de • Les propriétés
• Les événements
XAML • Les ressources
• Les dictionnaires de
ressources
• Le databinding

Le développement d’applications Silverlight


se compose de deux grandes activités: la
programmation en langage .NET et la conception
graphique basée sur le langage XAML (eXtensible
Application Markup Language).
XAML est un langage XML qui se prête bien à
la génération de code au moyen de logiciels
spécifiques aux infographistes et aux intégrateurs.
Le développeur travaille quant à lui dans Visual
Studio à la fois sur le langage XAML et le langage
.NET utilisé.
Nous allons voir dans ce chapitre les principales
règles du langage XAML. Vous vous rendrez compte
de ce qui est possible et de ce qui ne l’est pas dans
la programmation des interfaces utilisateur.
En explorant les diverses balises dans un document
XAML, vous apprendrez comment il est facile et
rapide de créer une interface utilisateur dans
Silverlight.
En fin de chapitre, vous aurez un aperçu des
extensions markup qui étendent XAML avec
des caractéristiques spécifiques de Silverlight.
L’utilisation des ressources et le mécanisme de leur
réutilisabilité seront abordés à cette occasion.
34 Développez des applications Internet avec Silverlight 5

L’interface visuelle d’une application Silverlight est construite par imbrication


de composants et de conteneurs .NET visuels. Le format XML, par sa faculté à
représenter les arborescences et par sa capacité à être outillé, représente le
support utilisé pour la formalisation des interfaces graphiques.
Le langage XAML utilise un schéma XML, et l’infrastructure XAML utilise un moteur
de désérialisation. Des outils de conception visuelle permettent d’automatiser
l’écriture de code XAML.
XAML est un langage déclaratif utilisé pour décrire un ensemble composite
d’objets .NET et de les instancier automatiquement. Spécifiquement, XAML peut
initialiser des objets et définir des propriétés d’objets, à l’aide d’une structure de
langage qui affiche les relations hiérarchiques entre plusieurs objets, et à l’aide
d’une convention de type de stockage qui prend en charge l’extension de types.
Vous pouvez créer des éléments d’interface utilisateur visibles dans le balisage
XAML déclaratif. Vous pouvez ensuite utiliser un fichier code-behind distinct pour
répondre aux événements et manipuler les objets que vous déclarez en XAML.
Tout ce qui est codé en XAML peut être aussi codé en langage .NET (C#, Visual
Basic, etc.), en sachant que l’inverse n’est pas vrai.
Le standard XAML obéit principalement à quelques règles:
• chaque élément dans un document XAML est mappé à une instance d’une
classe Silverlight. L’élément <Button>, par exemple, signifie à Silverlight
l’instanciation d’un objet de la classe Button.
• comme dans un document XML où un nœud parent peut contenir plusieurs
nœuds enfant, en XAML, un élément peut contenir d’autres éléments. Par
exemple, un conteneur de type Grid peut contenir des éléments de type Button.
XAML donne alors à chaque classe la possibilité de décider l’arrangement à
effectuer en fonction de la situation donnée.
• chaque élément XAML à la possibilité de fixer des propriétés et des gestionnaires
d’événements au travers de ses attributs, en utilisant si nécessaire une syntaxe
spéciale.
Copyright 2012 Patrice REY

1 - Les espaces de noms

Quand vous créez un nouveau projet Silverlight (ici le projet UtiliserXaml.sln dans
le dossier chapitre02 du code source téléchargeable gratuitement sur http://www.
reypatrice.fr), vous remarquez que le nœud racine est <UserControl>. Après la
fermeture de cette balise (</UserControl>), aucun contenu ne doit être écrit.
CHAPITRE 2 □ L’utilisation de XAML 35
Cette page, représentée par l’instanciation d’un objet UserControl, contient une
grille, de type Grid, qui est indiquée par la balise <Grid>. Cette balise représente
un nœud enfant pour le nœud parent <UserControl>. Chaque page, de type
UserControl, ne peut contenir qu’un seul conteneur. Par conséquent, la balise
parent (<UserControl>) n’aura qu’une seule balise enfant (<Grid> ici, mais cela
pourrait être un autre conteneur comme <Canvas> par exemple).
Le parseur de Silverlight, en lisant ce code XAML, doit pouvoir le reconnaitre de
façon à instancier les objets décrits, avec une démarche propre à chaque objet
et une façon de faire en fonction de l’objet à instancier. Pour cela, il faut indiquer
des espaces de noms qui contiennent les informations nécessaires au décodage,
sous forme de schémas XML définissant les objets utilisés. Dans les attributs de
<UserControl>, on trouve quatre indications de référence à des espaces de noms,
qui commence par xmlns (pour indiquer xml namespace). On y trouve:
• le premier schéma, défini par l’attribut xmlns sans préfixe, est celui de WPF,
repris pour Silverlight; il autorise les éléments XML dont le nom est celui de
classes Silverlight; il est obligatoire.
• le deuxième schéma, de préfixe x:, est celui du langage XAML; il permet
d’utiliser des instructions spécifiques au sein des éléments Silverlight décrits
en XAML; il est obligatoire.
• le troisième schéma, de préfixe d:, est facultatif; il permet d’utiliser des attributs
spécifiques à l’éditeur XAML en mode de conception visuelle comme la largeur
et la hauteur (d:DesignWidth et d:DesignHeight).
• le quatrième schéma, de préfixe mc:, est facultatif; il est utilisé pour permettre
la compatibilité du code avec des outils ne supportant pas les attributs
spécifiques à l’éditeur XAML.

<UserControl x:Class= «UtiliserXaml.MainPage»


xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:d= «http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc= «http://schemas.openxmlformats.org/markup-compatibility/2006»
mc:Ignorable= «d» d:DesignHeight= «300» d:DesignWidth= «400»>
<Grid x:Name= «LayoutRoot» Background= «White»>
</Grid>
</UserControl>

Ces schémas XML sont virtuels et représentent en fait des espaces de noms .NET.
L’association est obtenue au moyen de la technique dite du mapping XAML (nous
y reviendrons plus en détail dans un autre chapitre).
36 Développez des applications Internet avec Silverlight 5

2 - Le code-behind

A chaque fichier XAML, Visual Studio associe automatiquement un fichier de code


.NET (en C# pour cet ouvrage) appelé code-behind. Dans le cas de MainPage.xaml
et du langage C#, le fichier de code-behind est MainPage.xaml.cs (pour indiquer
qu’il s’agit d’un code C#). Ces deux fichiers définissent la classe MainPage. Le nom
de la classe est déclaré dans le fichier XAML au moyen de l’attribut x:Class (espace
de noms xmlns:x) sur l’élément <UserControl>.
<UserControl x:Class= «UtiliserXaml.MainPage»
...
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
...
mc:Ignorable= «d» d:DesignHeight= «300» d:DesignWidth= «400»>
<Grid x:Name= «LayoutRoot» Background= «White»>
</Grid>
</UserControl>
Le fichier de code-behind contient la déclaration d’une classe partielle
MainPage héritée de UserControl et dont le constructeur appelle une méthode
InitializeComponent().
public partial class MainPage : UserControl {
public MainPage() {
InitializeComponent();
}
}
La compilation de MainPage.xaml génère dynamiquement le complément de la
classe partielle MainPage avec une méthode InitializeComponent() qui instancie
les éléments décrits en XAML, et crée des références d’objet C# correspondant à
leur propriété Name (si celle-ci a été déclarée). Il est également possible d’utiliser
l’attribut XAML x:Name, notamment pour les classes ne disposant pas de propriété
Name.
Pour savoir ce qu’exécute comme instructions la méthode InitializeComponent(),
en faisant un clic droit dessus et en choisissant atteindre la définition (figure 2.1),
Copyright 2012 Patrice REY

l’éditeur ouvre le fichier généré automatiquement par Visual Studio. On voit donc
le détail de cette méthode par programmation.
public partial class MainPage : System.Windows.Controls.UserControl {
internal System.Windows.Controls.Grid LayoutRoot;
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
CHAPITRE 2 □ L’utilisation de XAML 37
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Windows.Application.LoadComponent(this,
new System.Uri(«/UtiliserXaml;component/MainPage.xaml»,
System.UriKind.Relative));
this.LayoutRoot = ((System.Windows.Controls.Grid)
(this.FindName(«LayoutRoot»)));
}
}
FIGURE 2.1

3 - Les propriétés

En XAML, les propriétés des contrôles sont fixées par les attributs dans les balises
correspondantes. La syntaxe utilisée sera fonction de la complexité de la propriété.
Par exemple une propriété concernant une couleur pourra être traduite en
indiquant le nom de la couleur. Mais si l’on désire affecter un objet représentant un
dégradé de couleurs, l’objet étant complexe à réaliser, cela nécessitera une syntaxe
plus poussée pour son expression en XAML.
Le projet UtiliserXaml.sln (figure 2.2), dans le dossier chapitre02, va nous permettre
de voir les syntaxes utilisées pour exprimer les propriétés.
Globalement, le fichier MainPage.xaml se compose d’un conteneur intitulé x_
grid_root, de type Grid, divisé en 3 lignes et 1 colonne. Sur la première ligne, on
positionne un contrôle TextBox, intitulé x_text_mot, qui affiche le verbe apprendre.
Sur la deuxième ligne, on positionne un bouton intitulé x_btn_trouver, de type
Button. Sur la troisième ligne, on positionne un TextBox, intitulé x_text_def, qui
38 Développez des applications Internet avec Silverlight 5
FIGURE 2.2

affichera la définition du mot apprendre quand on cliquera sur le bouton. La


structure globale du fichier MainPage.xaml est donc la suivante:
<UserControl x:Class= «UtiliserXaml.MainPage»
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:d= «http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc= «http://schemas.openxmlformats.org/markup-compatibility/2006»
mc:Ignorable=»d» d:DesignHeight= «300» d:DesignWidth= «400»>
<Grid x:Name= «x_grid_root»>
<!-- fond de la grille -->
<Grid.Background>
...
</Grid.Background>
<!-- les lignes et colonnes de la grille -->
<Grid.RowDefinitions>
...
</Grid.RowDefinitions>
<!-- les elements -->
<TextBox Name= «x_text_mot»
... />
<Button Name= «x_btn_trouver»
.../>
<TextBox Name= «x_text_def»
... />
</Grid>
Copyright 2012 Patrice REY

</UserControl>

3.1 - Les propriétés simples

Pour le contrôle TextBox x_text_mot, il faut lui fixer des propriétés pour sa taille
(Width et Height), sa police d’écriture (FontFamily) et sa taille d’écriture (FontSize).
Une taille s’exprime en pixels comme Width= «376». Quand le parseur va lire cette
CHAPITRE 2 □ L’utilisation de XAML 39
largeur, il sait qu’il doit lire un nombre pour représenter une largeur en pixels (376
pixels ici). Il traduit donc une chaîne représentant un nombre en une valeur (de
type double ici). La propriété Text représente le contenu du TextBox, donc la valeur
attendue est une chaîne de caractères à traduire. La police d’écriture (FontFamily)
est une chaîne nommant une police qui sera convertie pour écrire du texte avec des
caractères de cette police. Toutes ces propriétés sont dites propriétés simples car
elles utilisent la syntaxe dite attribut. Autrement dit, on peut fixer ces propriétés
par une simple chaîne (un nom, un nombre) en écrivant: propriété = «une_chaine».
<TextBox Height= «23» HorizontalAlignment= «Center» Name= «x_text_mot»
VerticalAlignment= «Center» Width= «376» Text= «apprendre» FontSize= «12»
FontFamily= «Verdana»
... />

3.2 - Les propriétés complexes

On aurait pu affecter au conteneur x_grid_root un fond composé par une couleur


unie, comme par exemple une couleur blanche en affectant à la propriété
Background la valeur White. Seulement on préfère un fond composé par un
dégradé de couleurs. Un dégradé se compose généralement d’au moins 2 couleurs
au minimum avec, pour chacune une intensité choisie. On comprend tout de
suite que cette propriété doit être affectée par un objet complexe qui représente
un dégradé. La classe LinearGradientBrush représente les objets pour créer des
dégradés.
Pour affecter un objet complexe à une propriété, on utilise la syntaxe élément.
propriété (une syntaxe dite en notation pointée). Ici l’élément est Grid et la
propriété est Background, donc la syntaxe utilisée sera <Grid.Background> </Grid.
Background>.
<Grid x:Name= «x_grid_root»>
<!-- fond de la grille -->
<Grid.Background>
...
</Grid.Background>
...
</Grid>
A l’intérieur de cette balise, on ajoutera un élément <LinearGradientBrush> </
LinearGradientBrush> pour indiquer que le fond de la grille sera composé d’un
objet dégradé.
<Grid x:Name= «x_grid_root»>
<!-- fond de la grille -->
40 Développez des applications Internet avec Silverlight 5
<Grid.Background>
<LinearGradientBrush>
</LinearGradientBrush>
</Grid.Background>
...
</Grid>
Comme un objet LinearGradientBrush est composé d’une collection de
GradientStop (qui représente chacun une couleur avec une position), on affectera
à sa propriété GradientStops (la collection) les objets GradientStop. La propriété
GradientStops est une propriété qui nécessite de lui affecter un objet complexe
d’où l’utilisation encore de la syntaxe élément.propriété, ce qui donne:
<Grid.Background>
<LinearGradientBrush EndPoint= «0.5,1» StartPoint= «0.5,0»>
<LinearGradientBrush.GradientStops>
<GradientStop Color= «DimGray» Offset= «0» />
<GradientStop Color= «White» Offset= «1» />
<GradientStop Color= «DarkGray» Offset= «0.451» />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Grid.Background>
Nous verrons dans un chapitre plus loin la personnalisation des couleurs dans un
dégradé (objet GradientStop). Ce dégradé peut être écrit aussi par programmation
C# en utilisant la même démarche, ce qui donnerait:
LinearGradientBrush brush = new LinearGradientBrush();
GradientStop gradientStop1 = new GradientStop();
gradientStop1.Offset = 0;
gradientStop1.Color = Colors. DimGray;
brush.GradientStops.Add(gradientStop1);
GradientStop gradientStop2 = new GradientStop();
gradientStop2.Offset = 1;
gradientStop2.Color = Colors.White;
brush.GradientStops.Add(gradientStop2);
GradientStop gradientStop3 = new GradientStop();
gradientStop3.Offset = 0.451;
gradientStop3.Color = Colors. DarkGray;
brush.GradientStops.Add(gradientStop3);
Copyright 2012 Patrice REY

x_grid_root.Background = brush;

3.3 - Les propriétés attachées

La position et la taille des éléments affichés peuvent être organisées de différentes


façons en fonction du type de panneau qui les contient comme le Canvas, le Grid,
le StackPanel, etc. Les panneaux (ou conteneurs) exploitent le mécanisme des
CHAPITRE 2 □ L’utilisation de XAML 41
propriétés attachées pour gérer la disposition de leurs éléments contenus.
Ici, nous avons un Grid composé de 3 lignes. On positionne un élément sur chaque
ligne. Il faut donc que chaque contrôle ait une propriété qui lui fixe sa position sur
une ligne particulière: le x_text_mot sur la 1ère ligne, le x_btn_trouver sur la 2ème
ligne et le x_text_def sur la 3ème ligne. Pour ce qui concerne le conteneur Grid,
on fixera la propriété Grid.Row à une valeur (0 pour la première ligne, 1 pour la
deuxième ligne, etc.). Cela donne donc:
<TextBox ... Grid.Row= «0» ... />
<Button ... Grid.Row= «1» ... />
<TextBox ... Grid.Row= «2» ... />
Quand le parseur XAML tombe sur une propriété attachée, il se contente en fait
d’appeler la méthode statique correspondante et l’applique au contrôle : ici il
appelle la méthode Grid.SetRow(x_text_mot,0) pour positionner le contrôle x_
text_mot sur la première ligne (indice 0).

3.4 - Les propriétés implicites

En XAML, certaines propriétés peuvent être omises, on les nomme propriété


implicite. Certaine classe Silverlight peut définir une propriété dite de contenu
(content property) qui référence un objet imbriqué.
Par exemple la propriété Content d’un objet Button est une propriété implicite
(propriété de contenu). Cette propriété sert à indiquer ce que le bouton doit
afficher c’est-à-dire son contenu. Si son contenu est du texte, il est alors représenté
sous la forme d’un nœud texte, imbriqué entre les balises ouvrantes et fermantes
de l’élément Button, de la façon suivante:
<Button Height= «30» Width= «50»>mon texte</Button>
Comme autre exemple, la propriété GradientStops d’un objet LinearGradientBrush
est une propriété implicite. A la différence du bouton, cette propriété implicite
reçoit une collection d’objets (la collection GradientStops).
On aurait donc pu écrire la composition du dégradé vue au paragraphe 3.2 de la
façon suivante:
<LinearGradientBrush EndPoint= «0.5,1» StartPoint= «0.5,0»>
<GradientStop Color= «DimGray» Offset= «0» />
<GradientStop Color= «White» Offset= «1» />
<GradientStop Color= «DarkGray» Offset= «0.451» />
</LinearGradientBrush>
42 Développez des applications Internet avec Silverlight 5

3.5 - Les caractères spéciaux

En XAML certains caractères ne peuvent pas être écrits directement comme le


& (et commercial), le guillemet, le chevron ouvrant et le chevron fermant. Pour
exprimer ces caractères, il faut les remplacer par des caractères XML équivalent:
• & remplacé par la chaîne &quot;
• « remplacé par la chaîne &amp;
• < remplacé par la chaîne &lt;
• > remplacé par la chaîne &gt;

4 - Les événements

Pour l’instant nous avons vu que les attributs pouvaient fixer les propriétés des
contrôles. Il en est de même pour attacher les gestionnaires d’événements des
contrôles. On utilise la syntaxe suivante:
nom_evenement = «nom_de_la_methode_pour_la_prise_en_charge»
Comme on l’a vu au chapitre 1, on peut laisser Visual Studio générer la méthode
pour la prise en charge d’un événement, comme ici en attachant le gestionnaire
d’événement Click au bouton:
<Button Content= «donne la définition» Grid.Row= «1» Height= «23»
HorizontalAlignment= «Center» Name= «x_btn_trouver» VerticalAlignment=
«Center» Width= «234»
Click= «x_btn_trouver_Click» />
Et dans le code-behind MainPage.xaml.cs, on implémente le gestionnaire de
l’événement Click sur le bouton en changeant la propriété Text du x_text_def.
public partial class MainPage : UserControl {
public MainPage() {
InitializeComponent();
}
//evenement clic sur bouton
Copyright 2012 Patrice REY

private void x_btn_trouver_Click(object sender, RoutedEventArgs e) {


x_text_def.Text = «Acquérir la connaissance, la pratique de:
Apprendre un métier, les mathématiques.»;
}
}//end class
Dans de nombreuses situations, vous utiliserez les attributs pour fixer les propriétés
et pour attacher les gestionnaires d’événements. Silverlight suit toujours la même
séquence: en premier il fixe la propriété Name (si elle est présente), en deuxième
CHAPITRE 2 □ L’utilisation de XAML 43
il attache tous les gestionnaires d’événements souhaités, et enfin, en troisième,
il fixe les propriétés. Il faut donc faire attention quand l’implémentation d’un
gestionnaire d’événements modifie des propriétés du contrôle (à cause de cet
ordre).

5 - Les ressources

Silverlight intègre un mécanisme de centralisation d’objets sous la forme de


ressources qui peuvent être référencées en XAML. Une ressource est gérée dans
un dictionnaire du conteneur (la propriété Resources), et est identifiée par une clé
définie au moyen d’un attribut XAML x:Key.
Par exemple (dans le sous-dossier UtiliserXamlRessource du dossier chapitre02),
dans le fichier MainPage.xaml, on place une ressource, intitulée r_fond_
dans_usercontrol, dans la propriété Resources de l’UserControl (<UserControl.
Resources>). Cette ressource représente un objet LinearGradientBrush que l’on
se servira pour peindre le fond de la grille.
<UserControl x:Class= «UtiliserXaml.MainPage»
...
mc:Ignorable= «d» d:DesignHeight= «300» d:DesignWidth= «400»>
<UserControl.Resources>
<LinearGradientBrush x:Key= «r_fond_dans_usercontrol»
EndPoint= «0.5,1» StartPoint= «0.5,0»>
<LinearGradientBrush.GradientStops>
<GradientStop Color= «DimGray» Offset= «0» />
<GradientStop Color= «White» Offset= «1» />
<GradientStop Color= «DarkGray» Offset= «0.451» />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</UserControl.Resources>
...
</UserControl>
L’utilisation d’une ressource dans un attribut de propriété peut se faire au moyen
d’une expression {StaticResource}, en spécifiant la clé de la ressource recherchée.
La ressource doit être déclarée dans l’arbre des conteneurs en amont (à n’importe
quel niveau, que ce soit celui du conteneur parent ou celui de ses ancêtres). Pour
peindre le fond de la grille avec la ressource r_fond_dans_usercontrol, on affecte à
la propriété Background de la grille x_grid_root la ressource de la façon suivante:

Background = «{ StaticResource r_fond_dans_usercontrol}»


44 Développez des applications Internet avec Silverlight 5
<UserControl x:Class= «UtiliserXaml.MainPage»
...
mc:Ignorable= «d» d:DesignHeight= «300» d:DesignWidth= «400»>
...
<Grid x:Name= «x_grid_root»
Background= «{StaticResource r_fond_dans_usercontrol}»>
...
</Grid>
</UserControl>
L’expression {StaticResource} entre accolades est une extension markup. Le
système des extensions markup permet d’enrichir la syntaxe XAML au moyen de
classes spécialisées. Un certain nombre d’extensions markup sont fournies avec
Silverlight dont StaticResource.
En plaçant la ressource r_fond_dans_usercontrol dans les ressources du contrôle
UserControl, cette ressource n’est accessible qu’aux contrôles qui sont contenus
dans l’UserControl.
Très souvent, des ressources doivent pouvoir être accessibles à toute l’application,
dans le cadre d’une réutilisation. Des contrôles dans différentes pages peuvent
partager une même ressource. La classe fondamentale FrameworkElement,
ancêtre de tous les éléments visuels de Silverlight, expose une propriété Resources
qui peut référencer un dictionnaire de ressources. Généralement, un dictionnaire
de ressources est défini sur les éléments de type conteneur (comme UserControl,
Canvas, Grid, etc.), et un dictionnaire global est aussi défini au niveau de la
classe Application. De cette façon, des ressources globales utilisables dans toute
l’application peuvent être définies dans le fichier App.xaml.
Dans notre exemple, on place la ressource r_fond_dans_app dans le dictionnaire
des ressources (<ResourceDictionary>) de l’application (<Application.Resources>):
<Application xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/
presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
x:Class= «UtiliserXaml.App»>
<Application.Resources>
<ResourceDictionary>
<LinearGradientBrush x:Key= «r_fond_dans_app»
Copyright 2012 Patrice REY

EndPoint= «0.5,1» StartPoint= «0.5,0»>


<LinearGradientBrush.GradientStops>
<GradientStop Color= «DimGray» Offset= «0» />
<GradientStop Color= «White» Offset= «1» />
<GradientStop Color= «DarkGray» Offset= «0.451» />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</ResourceDictionary>
CHAPITRE 2 □ L’utilisation de XAML 45
</Application.Resources>
</Application>
Et pour utiliser cette ressource pour peindre le fond de la grille x_grid_root, on
utilise la même syntaxe attribut Background = «{StaticResource r_fond_dans_
app}».
<UserControl x:Class= «UtiliserXaml.MainPage»
...
mc:Ignorable= «d» d:DesignHeight= «300» d:DesignWidth= «400»>
<Grid x:Name= «x_grid_root» Background= «{StaticResource r_fond_dans_app}»>
...
</Grid>
</UserControl>
A noter que l’élément ResourceDictionary est implicite, donc il peut être omis en
XAML.
<Application.Resources>
<LinearGradientBrush x:Key= «r_fond_dans_app»
EndPoint= «0.5,1» StartPoint= «0.5,0»>
<LinearGradientBrush.GradientStops>
<GradientStop Color= «DimGray» Offset= «0» />
<GradientStop Color= «White» Offset= «1» />
<GradientStop Color= «DarkGray» Offset= «0.451» />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Application.Resources>

6 - Les dictionnaires de ressources

Un dictionnaire de ressources est un fichier XAML qui contient un ensemble de


ressources. Un dictionnaire de ressources peut aussi agréger plusieurs autres
dictionnaires. Cette caractéristique offre les possibilités de mise en place d’une
gestion de skins, ou la personnalisation des ressources pour une application
donnée dans un pays donné.
Le mécanisme de recherche de ressources parcourt d’abord le dictionnaire principal,
puis les dictionnaires agrégés dans l’ordre inverse de leur déclaration XAML. Une
clé de ressource doit être unique au sein d’un dictionnaire, mais peut exister dans
plusieurs dictionnaires agrégés. En cas de conflit, la première occurrence trouvée
est retenue.
Examinons, dans un premier temps, la procédure de création d’un dictionnaire.
Dans un deuxième temps, nous verrons la fusion de plusieurs dictionnaires et
l’accès aux ressources de ces dictionnaires. Les fichiers XAML se trouvent dans le
46 Développez des applications Internet avec Silverlight 5
sous-dossier UtiliserXamlDictionnaire du dossier chapitre02.

6.1 - Création d’un dictionnaire

La démarche consiste à créer un dictionnaire intitulé DicoDegrade.xaml qui va


contenir des dégradés de couleurs comme ressources. En faisant un clic droit sur
le nom du projet UtiliserXaml (figure 2.3), choisir le sous-menu ajouter puis nouvel
élément. Une fenêtre ajouter un nouvel élément s’ouvre.
FIGURE 2.3

Sélectionnez, dans les modèles installés (repère n°1), le modèle dictionnaire de


ressources Silverlight (repère n°2). Donnez lui le nom de DicoDegrade.xaml, puis
cliquez sur le bouton ajouter.
FIGURE 2.4

1
Copyright 2012 Patrice REY

2
3
CHAPITRE 2 □ L’utilisation de XAML 47
Dans ce dictionnaire, on ajoute une ressource intitulée r_dico_degrade_gris qui
correspond à un dégradé linéaire à base de gris (objet LinearGradientBrush).
<ResourceDictionary
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»>
<LinearGradientBrush x:Key= «r_dico_degrade_gris»
EndPoint= «0.5,1» StartPoint= «0.5,0»>
<LinearGradientBrush.GradientStops>
<GradientStop Color= «DimGray» Offset= «0» />
<GradientStop Color= «White» Offset= «1» />
<GradientStop Color= «DarkGray» Offset= «0.451» />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</ResourceDictionary>
On procède de la même manière pour créer un autre dictionnaire, intitulé
DicoCouleurUnie.xaml dans lequel on va stocker des ressources de couleurs
unies (objet SolidColorBrush). Une couleur grise, avec la propriété Color fixée à
WhiteSmoke, est ajoutée, et elle est nommée par la clé r_dico_gris_uni.
<ResourceDictionary
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»>
<SolidColorBrush x:Key= «r_dico_gris_uni» Color= «WhiteSmoke»>
</SolidColorBrush>
</ResourceDictionary>

6.2 - Fusion des dictionnaires

Dans le fichier App.xaml, on va fusionner les dictionnaires DicoDegrade.xaml et


DicoCouleurUnie.xaml, de façon à les rendre accessibles à toute l’application.
L’élément implicite ResourceDictionary va contenir la fusion des dictionnaires. La
propriété MergedDictionaries de la classe ResourceDictionary permet de spécifier
la liste des dictionnaires agrégés. La propriété Source indique l’URI du fichier XAML
contenant un dictionnaire à fusionner.
<Application xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/
presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
x:Class= «UtiliserXaml.App»>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source= «DicoDegrade.xaml»></ResourceDictionary>
<ResourceDictionary Source= «DicoCouleurUnie.xaml»></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
48 Développez des applications Internet avec Silverlight 5
</ResourceDictionary>
</Application.Resources>
</Application>
Les dictionnaires étant fusionnés, ils sont maintenant accessibles à tous les contrôles
de l’application. Il ne reste plus qu’à spécifier l’utilisation de ces ressources au
moyen de l’extension markup StaticResource avec la clé correspondante (r_dico_
degrade_gris pour le fond de la grille, et r_dico_gris_uni pour le fond du TextBox).
La figure 2.5 visualise le résultat obtenu.
<UserControl x:Class= «UtiliserXaml.MainPage»
...>
<Grid x:Name= «x_grid_root»
Background= «{StaticResource r_dico_degrade_gris}»>
<!-- les elements -->
...
<TextBox Name= «x_text_def» ...
Background= «{StaticResource r_dico_gris_uni}»></TextBox>
</Grid>
</UserControl>

FIGURE 2.5

Background=
"{StaticResource r_dico_degrade_gris}"

Background=
"{StaticResource r_dico_gris_uni}"

7 - Le databinding
Copyright 2012 Patrice REY

Le databinding, terme signifiant liaison de données, est une technique qui


permet d’associer des éléments de l’interface utilisateur à des données sous-
jacentes, issues d’objets simples ou de collections d’objets. Le but du databinding
est d’automatiser l’affichage de données existantes et le stockage de nouvelles
données saisies.
Cette association est construite au moyen d’une extension markup Binding. Elle
CHAPITRE 2 □ L’utilisation de XAML 49
est potentiellement bidirectionnelle et gère:
• la lecture des données (typiquement pour l’affichage) ainsi que le
rafraîchissement quand elles subissent une modification.
• la mise à jour des données modifiées par saisie dans l’interface utilisateur avec
une mise en œuvre de règles de validation.
Le databinding gère également un système de conversion permettant une différence
du format des données entre les valeurs stockées et les valeurs affichées.
L’objet Binding dispose d’une propriété ElementName qui permet de référencer
comme source du binding un élément visuel par son nom (propriété Name ou
x:Name). De plus, il dispose d’une propriété Path qui référence la propriété source
qui fournit les données.
Un contrôle peut être lui-même considéré comme une source de données.
Ouvrez le projet qui se trouve dans le sous-dossier UtiliserXamlBinding du dossier
chapitre02. Un contrôle x_glissiere représentant une glissière, de type Slider, est
positionné sur un Canvas intitulé x_cnv_root. Cette glissière produit des valeurs
entre une valeur minimale 14 (propriété Minimum) et une valeur maximale 30
(propriété Maximum), avec des pas de 1 (propriété SmallChange). On positionne
un contrôle x_text, de type TextBlock, affichant une chaîne mon texte avec la police
d’écriture Verdana. On souhaite que la taille de la police d’écriture (FontSize) soit
liée à la valeur de la glissière (donc une taille qui varie de 14 à 30). Pour réaliser
cela, il faut affecter à la propriété FontSize du TextBlock un objet Binding dont
sa propriété ElementName référence la glissière (x_glissiere) et sa propriété Path
référence la source des données (la propriété Value qui est la valeur de la donnée
fournie par la glissière). On écrira donc:
FontSize = «{Binding ElementName=x_glissiere, Path=Value}»
<UserControl x:Class= «UtiliserXaml.MainPage»
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:d= «http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc= «http://schemas.openxmlformats.org/markup-compatibility/2006»
mc:Ignorable= «d» d:DesignHeight= «200» d:DesignWidth= «600»>
<Canvas x:Name= «x_cnv_root» Height= «200» Width= «600» Background=
«WhiteSmoke»>
<!-- les elements -->
<TextBlock Canvas.Left= «12» Canvas.Top= «12» Height= «37» Name= «x_text»
Text= «mon texte» Width= «576»
FontSize= «{Binding ElementName=x_glissiere, Path=Value}»
FontFamily= «Verdana» />
<Slider Canvas.Left= «12» Canvas.Top= «45» Height= «35»
Name= «x_glissiere»
50 Développez des applications Internet avec Silverlight 5
Width= «576» Minimum= «14» Maximum= «30» SmallChange= «1»
LargeChange= «1» />
</Canvas>
</UserControl>
Comme le montre la figure 2.6, le déplacement de la glissière modifie la taille
d’écriture du texte, passant de 14 (valeur minimale) à 30 (valeur maximale).
FIGURE 2.6

Visual Studio intègre un éditeur d’expression de binding dans le panneau Propriétés


(accessible par le menu contextuel Appliquer la liaison de données...). Cet outil
génère les expressions de binding en mode de conception visuelle.
La figure 2.7 montre la démarche pour lier la propriété FontSize du TextBlock à la
Copyright 2012 Patrice REY

donnée Value du Slider:


• repère n°1: quand on est dans la balise du TextBlock, on se positionne sur la
propriété FontSize, et on choisit appliquer la liaison de données.
• repère n°2: dans l’onglet Source, on choisit dans la liste déroulante la propriété
ElementName, puis le nom x_glissiere.
• repère n°3: dans l’onglet Chemin d’accès, on choisit la source de données
(propriété Value).
CHAPITRE 2 □ L’utilisation de XAML 51
FIGURE 2.7
1

2 2 3

Sur le même principe, on ajoute un TextBox x_text_valeur qui affiche en


permanence la valeur de la glissière, et par conséquent la taille de la police
d’écriture du texte. On fixe la propriété Mode de l’objet Binding à la valeur OneWay
(mode unidirectionnel par défaut).
<TextBox Canvas.Left= «157» Canvas.Top= «86» Height= «23»
Name= «x_text_valeur»
Width= «222» FontFamily= «Verdana»
Text= «{Binding Path=Value, Mode=OneWay, ElementName=x_glissiere}» />
Dès que l’on bouge la glissière (figure 2.8), la valeur courante de la glissière est
inscrite dans le TextBox.
FIGURE 2.8

Dans le mode bidirectionnel (propriété Mode fixée à TwoWay), si on change la


valeur du TextBox par une valeur comprise entre les bornes de la glissière, la
52 Développez des applications Internet avec Silverlight 5
glissière se met à jour toute seule (figure 2.9).
FIGURE 2.9

<TextBox Canvas.Left= «157» Canvas.Top= «86» Height= «23»


Name= «x_text_valeur»
Width= «222» FontFamily= «Verdana»
Text= «{Binding Path=Value, Mode=TwoWay, ElementName=x_glissiere}» />
On ajoute un bouton x_btn_valider de façon à ce que, une fois la valeur changée
dans le TextBox, le fait de cliquer sur le bouton fait perdre le focus au TextBox et
donc la mise à jour de la glissière s’effectue.

Copyright 2012 Patrice REY


CHAPITRE 3 DANS CE CHAPITRE
• La classe Panel
• Le conteneur Canvas
Les panneaux et • Le conteneur
StackPanel

le système de • Le conteneur Grid


• Le conteneur
DockPanel
disposition • Le conteneur
WrapPanel
• Les propriétés
impactant la
disposition
L a réalisation d’une interface utilisateur consiste • Les autres conteneurs:
Border, Viewbox et
à trouver l’organisation du contenu d’une façon la ScrollViewer
plus attractive, la plus pratique et la plus flexible.
D’autant plus que votre application doit très souvent
fonctionner sur des écrans de tailles différentes, et
que vous n’avez pas le contrôle sur la taille de la
fenêtre dans laquelle votre application s’exécute.
Heureusement, Silverlight hérite de la plus grande
partie du système de disposition de WPF. Grâce
à cela, vous pouvez organiser la disposition de
votre contenu en utilisant une grande variété de
panneaux conteneurs.
Chaque conteneur a ses propres éléments, ses
propres caractéristiques et sa propre logique.
Dans ce chapitre, nous allons apprendre à utiliser
les différents conteneurs, avec notamment le Grid
et le Canvas.
Nous verrons en détail comment fonctionne le
système de disposition qui représente le mécanisme
qui permet d’organiser selon différentes approches
la position et la taille des éléments affichés.
Nous aurons l’occasion d’aborder ce qui concerne
l’imbrication des conteneurs.
54 Développez des applications Internet avec Silverlight 5

Le plug-in Silverlight définit la zone dans laquelle l’application Silverlight s’affiche.


Vous pouvez incorporer le plug-in dans une page HTML hôte. Vous avez le choix
entre positionner le plug-in à un emplacement à l’intérieur de la page HTML ou
faire en sorte que le plug-in occupe l’intégralité de la page HTML. De ce fait, deux
cadres de référence sont proposés lors du positionnement d’objets Silverlight :
• dans le plug-in : les objets sont positionnés sur la surface Silverlight, dans le
cadre englobant du plug-in. La plupart des vues d’ensemble des dispositions
décrivent ce type de positionnement.
• dans le HTML : l’ensemble du plug-in et tous les objets qui ont été positionnés
dans le plug-in dépendent de l’endroit où vous placez le plug-in dans le HTML.

1 - La classe Panel

Le système de disposition (ou layout) est un mécanisme qui permet d’organiser


selon différentes approches la position et la taille des éléments affichés, c’est-
à-dire leur agencement. Le terme disposition décrit le processus consistant à
dimensionner et positionner des objets dans votre application Silverlight. Pour
positionner des objets visuels, vous devez les mettre dans un conteneur , de type
Panel, ou un autre objet conteneur.
Le système de disposition utilise des panneaux conteneurs d’éléments visuels, dont
la classe hérite de Panel (classe de base abstraite). Ces panneaux conteneurs sont:
• Canvas: conteneur avec une organisation en coordonnées x et y.
• Grid: conteneur avec une organisation tabulaire automatique (avec des lignes
et des colonnes).
• StackPanel: conteneur de type pile avec une orientation verticale ou horizontale
automatique.
• DockPanel: conteneur avec un alignement automatique sur l’un des cotés
(haut, gauche, bas, droit).
• WrapPanel: conteneur avec un empilement des éléments contenus, sur
plusieurs lignes ou colonnes si nécessaire.
Copyright 2012 Patrice REY

Comme le système de disposition est extensible, il sera possible de définir des


types de panneaux personnalisés.
La figure 3.1 montre l’arbre d’héritage de la classe Panel. Les éléments contenus
doivent hériter de UIElement. Les objets FrameworkElement sont cependant
mieux adaptés de par leurs propriétés supplémentaires impactant la disposition.
CHAPITRE 3 □ Les panneaux et le système de disposition 55
FIGURE 3.1

Le panneau détermine une zone rectangulaire dans laquelle il positionne chaque


élément, éventuellement après l’avoir redimensionné. Le Panel parent défini
le comportement de disposition qui détermine la façon dont les membres de
la collection Children d’un élément Panel sont dessinés à l’écran. Ce processus
est intensif : plus la collection Children est importante, plus le nombre de calculs
effectués est élevé. Il est également possible de définir une complexité en
fonction du comportement de disposition défini par l’élément Panel qui possède
la collection. Une disposition relativement simple telle que Canvas peut générer
d’excellentes performances lorsqu’un Panel plus complexe tel que Grid n’est pas
requis.
Il est important de comprendre qu’un cadre englobant entoure tous les éléments
d’une application Silverlight. Cela permet de mieux comprendre le comportement
du système de disposition. Lorsque le système de disposition positionne un
FrameworkElement, il positionne en réalité un rectangle, ou emplacement de
disposition, qui contient cet élément. L’agencement est calculé en deux passes
pendant lesquelles chaque élément est interrogé.
La première est une passe de mesure (méthode Measure) dans laquelle chaque
élément indique la taille qu’il souhaiterait avoir. La deuxième est une passe de
réorganisation (méthode Arrange) dans laquelle le système indique à chaque
élément l’espace qui lui est finalement mis à disposition. Le système est récursif.
Plusieurs panneaux peuvent être imbriqués car la classe Panel hérite de
FrameworkElement. La modification de la taille ou de la position d’un élément
peut déclencher un cycle de disposition sur toute l’arborescence visuelle.
56 Développez des applications Internet avec Silverlight 5

2 - Le conteneur Canvas

Un Canvas est l’un des éléments Panel qui activent la disposition. Chaque objet
enfant est restitué dans la zone Canvas. Vous contrôlez le positionnement d’objets
au sein du Canvas en spécifiant des coordonnées x et y. Ces coordonnées sont
exprimées en pixels. L’origine du repère correspond au coin supérieur gauche du
Canvas. Les coordonnées x et y sont souvent spécifiées à l’aide des propriétés
attachées Canvas.Top et Canvas.Left:
• Canvas.Left spécifie la distance entre l’objet et le côté gauche du Canvas
conteneur (coordonnée x).
• Canvas.Top spécifie la distance entre l’objet et le haut du Canvas conteneur
(coordonnée y).
Un élément situé au-delà des limites du Canvas s’affiche quand même. Le Canvas
est un conteneur peu consommateur de ressources, qui n’impacte pas la taille des
éléments contenus.
Vous pouvez imbriquer des objets Canvas. Lorsque vous imbriquez des objets, les
coordonnées utilisées par chacun d’eux sont relatives à leur Canvas conteneur
immédiat. Chaque objet enfant doit être un UIElement. En XAML, vous déclarez
des objets enfants comme des éléments objet qui sont le XML interne d’un élément
objet Canvas. Dans le code, vous pouvez manipuler la collection d’objets enfants
Canvas en obtenant la collection accessible à la propriété Children. Vous pouvez
imbriquer des objets Canvas parce qu’un Canvas est un type de UIElement.
La figure 3.2 montre un Canvas x_cnv_root sur lequel sont positionnés deux
boutons (projet PanneauxCanvas.sln dans le dossier chapitre03).
FIGURE 3.2

Copyright 2012 Patrice REY


CHAPITRE 3 □ Les panneaux et le système de disposition 57
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Height= «148»>
<Button Canvas.Left= «35» Canvas.Top= «24» Content= «Left=35 Top=24»
Height= «30» Name= «x_btn1» Width= «169» />
<Button Canvas.Left= «177» Canvas.Top= «71» Content= «Left=177 Top=71»
Height= «30» Name= «x_btn2» Width= «169» />
</Canvas>

3 - Le conteneur StackPanel

StackPanel est l’un des éléments Panel qui activent la disposition. StackPanel
est utile si vous souhaitez réorganiser un jeu d’objets dans une liste verticale ou
horizontale (par exemple, un menu d’éléments horizontal ou vertical). C’est un
conteneur très pratique pour positionner rapidement des éléments. La pile est
verticale par défaut, mais peut être rendue horizontale au moyen de la propriété
Orientation.
Le projet PanneauxStackPanel.sln dans le dossier chapitre03 (figure 3.3), montre
quatre rectangles avec leurs dimensions explicites dans un StackPanel.
FIGURE 3.3

<StackPanel x:Name= «x_stack_root» Background= «WhiteSmoke»


Orientation= «Horizontal» Height= «152» Width= «556»>
<Rectangle Height= «33» Name= «x_rect1» Stroke= «Black» StrokeThickness= «1»
Width= «63» Fill= «DarkGray» Margin= «0,0,10,0» />
<Rectangle Fill= «LightGray» Height= «33» Name= «x_rect2» Stroke= «Black»
StrokeThickness= «1» Width= «63» Margin= «0,0,10,0» />
<Rectangle Fill= «SlateGray» Height= «33» Name= «x_rect3» Stroke= «Black»
StrokeThickness= «1» Width= «63» Margin= «0,0,10,0» />
<Rectangle Fill= «Silver» Height= «33» Name= «x_rect4» Stroke= «Black»
StrokeThickness= «1» Width= «63» Margin= «0,0,10,0» />
</StackPanel>
58 Développez des applications Internet avec Silverlight 5
La valeur par défaut des propriétés HorizontalAlignment et VerticalAlignment du
contenu d’un StackPanel est Stretch. Si les éléments n’ont pas une taille explicite, ils
subissent un étirement (stretching) correspondant aux dimensions du conteneur,
en largeur ou en hauteur. La figure 3.4 montre l’étirement avec une orientation
verticale.
FIGURE 3.4

<StackPanel x:Name= «x_stack_root» Background= «WhiteSmoke»


Orientation= «Horizontal» Height= «152» Width= «556»>
<Button Content= «Button» Name= «button1» />
<Button Content= «Button» Name= «button2» />
<Button Content= «Button» Name= «button3» />
<Button Content= «Button» Name= «button4» />
</StackPanel>

4 - Le conteneur Grid

Un conteneur de type Grid positionne les éléments qu’il contient de façon tabulaire.
La grille de positionnement est définie au moyen d’objets ColumnDefinition
(pour la définition des colonnes) et RowDefinition (pour la définition des lignes),
respectivement regroupés dans les propriétés ColumnDefinitions et RowDefinitions.
Par défaut, une grille est composée d’une cellule.
Chaque élément est positionné dans une cellule au moyen des propriétés
Copyright 2012 Patrice REY

attachées Grid.Column (numéro de la colonne) et Grid.Row (numéro de la ligne).


La numérotation commence à zéro. La valeur par défaut de ces propriétés est zéro.
Un élément peut occuper plusieurs cellules. Les propriétés attachées Grid.
ColumnSpan et Grid.RowSpan indiquent respectivement le nombre de colonnes et
de lignes à occuper à partir de la cellule courante. A noter que plusieurs éléments
peuvent partager la même cellule, et dans ce cas, ils se superposent.
Si les éléments dans une cellule n’ont pas de taille explicite (propriétés Width et
CHAPITRE 3 □ Les panneaux et le système de disposition 59
Height), ils subissent un étirement (stretch) au sein de leur cellule.
Pour matérialiser les cellules pendant la phase de mise au point, il faut fixer la
propriété booléenne ShowGridLines à true. Cette représentation de quadrillage
des cellules n’est pas destinée à être utilisée en production.
La figure 3.5 visualise le résultat obtenu (projet PanneauxGrid.sln dans le dossier
chapitre03) avec par exemple le bouton x_btn2 dont les propriétés Width et
Height sont explicites, le bouton x_btn5 avec une propriété RowSpan fixée à 2, et
le bouton x_btn4 avec une propriété ColumnSpan fixée à 2.
FIGURE 3.5

RowSpan de 2

ColumnSpan de 2
Width et
Height
explicites

<Grid x:Name= «x_grid_root» Background= «WhiteSmoke» ShowGridLines= «True»>


<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Content= «(0,0)» Name= «x_btn1» Grid.Row= «0» Grid.Column= «0» />
<Button Content= «(3,0) LxH» Name= «x_btn2» Grid.Row= «3» Grid.Column= «0»
Width= «60» Height= «30» />
<Button Content= «(1,1)» Name= «x_btn3» Grid.Row= «1» Grid.Column= «1» />
<Button Content= «(2,2) CS 2» Name= «x_btn4» Grid.Row= «2» Grid.Column= «2»
Grid.ColumnSpan= «2» />
60 Développez des applications Internet avec Silverlight 5
<Button Content= «(0,4) RS 2» Name= «x_btn5» Grid.Row= «0» Grid.Column= «4»
Grid.RowSpan= «2» />
<Button Content= «(4,4)» Name= «x_btn6» Grid.Row= «4» Grid.Column= «4» />
</Grid>
Les dimensions d’une cellule peuvent être exprimées de différentes façons.
La propriété Width de la classe ColumDefinition et la propriété Height de la
classe RowDefinition sont de type GridLength, type grâce auquel ces propriétés
supportent les formats suivants en XAML:
• une valeur double pour une dimension en pixels.
• la valeur Auto pour indiquer que la dimension est déterminée par l’élément
contenu.
• la valeur * (étoile) pour indiquer une répartition automatique des dimensions
en fonction de l’espace disponible; toutes les colonnes qui utilisent l’étoile se
voient affecter des dimensions identiques; un chiffre peut préfixer l’étoile;
dans ce cas, il joue le rôle d’un coefficient pondérateur (une colonne avec une
largeur 2* aura une largeur double de celle avec une étoile).
La figure 3.6 montre la première colonne avec une largeur de 100 pixels, la
deuxième colonne avec une largeur de 50 pixels, et les colonnes 3 à 5 avec des
largeurs respectives de *, 2* et 3*.
FIGURE 3.6

largeur de largeur de *, 2* et
100 et 50 3*
pixels

Copyright 2012 Patrice REY

<Grid x:Name= «x_grid_root» Background= «WhiteSmoke» ShowGridLines= «True»>


<Grid.RowDefinitions>
...
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width= «100» />
CHAPITRE 3 □ Les panneaux et le système de disposition 61
<ColumnDefinition Width= «50» />
<ColumnDefinition Width= «*» />
<ColumnDefinition Width= «2*» />
<ColumnDefinition Width= «3*» />
</Grid.ColumnDefinitions>
...
</Grid>

5 - Le conteneur DockPanel

Le conteneur DockPanel fait partie du Silverlight ToolKit. Il faut donc ajouter une
référence (figure 3.7) à cet espace de noms en faisant un clic droit sur le dossier
références et en choisissant le sous-menu ajouter une référence (repère n°1). Dans
la fenêtre intitulée ajouter une référence, sélectionnez l’espace System.Windows.
Controls.Toolkit (repère n°2), et appuyer sur OK.
Pour autoriser l’insertion dans un fichier XAML d’un élément contenu dans la
bibliothèque de classes de cet espace de noms, il faut déclarer un espace de noms
XML correspondant à l’espace de noms .NET de l’élément. Cela se fait au moyen
d’un mapping XAML. Dans les attributs de la balise <UserControl>, on ajoute
une référence xmlns intitulée toolkit (xmlns:toolkit) et on choisit la référence
correspondante qui est proprosée par l’intellisense de Visual Studio:
xmlns:toolkit =
«http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit»
Le conteneur DockPanel est désormais accessible en XAML par la balise
<toolkit:DockPanel>.
<UserControl x:Class= «PanneauxDockPanel.MainPage»
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:d= «http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc= «http://schemas.openxmlformats.org/markup-compatibility/2006»
xmlns:toolkit=
«http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit»
mc:Ignorable= «d» d:DesignHeight= «300» d:DesignWidth= «400» >
<toolkit:DockPanel Name= «x_dockpanel_root»/>
</UserControl>
Le DockPanel est un conteneur qui répartit les éléments qu’il contient sur ses
quatre côtés au moyen de la propriété attachée DockPanel.Dock. Celle-ci peut
prendre quatre valeurs: Left (valeur par défaut), Top, Right et Bottom. Sur chaque
côté, les éléments sont empilés en fonction de l’ordre dans lequel ils ont été
ajoutés au panneau.
62 Développez des applications Internet avec Silverlight 5
FIGURE 3.7

Par défaut, le dernier élément ajouté au DockPanel occupe tout l’espace restant.
La propriété LastChildFill peut être basculée à false pour inhiber ce comportement.
Le DockPanel est très souvent employé pour définir l’organisation du contrôle
principal d’une application: barre d’outils en haut, panneau de commande sur le
côté et fonctionnalité principale dans l’espace restant.
La figure 3.8 visualise le projet PanneauxDockPanel.sln dans le dossier chapitre03,
avec le positionnement d’un bouton dans les cinq parties qui composent le
DockPanel x_dockpanel_root.
<toolkit:DockPanel HorizontalAlignment= «Left» Name= «x_dockpanel_root»
VerticalAlignment= «Top» Background= «WhiteSmoke» LastChildFill= «True»
Width= «400» Height= «200»>
<Button Content= «1 - TOP» Name= «x_btn1» toolkit:DockPanel.Dock= «Top» />
Copyright 2012 Patrice REY

<Button Content= «2 - LEFT» Name= «x_btn2» toolkit:DockPanel.Dock= «Left» />


<Button Content= «3 - RIGHT» Name= «x_btn3» toolkit:DockPanel.Dock= «Right»
/>
<Button Content= «4 - BOTTOM» Name= «x_btn4»
toolkit:DockPanel.Dock= «Bottom»
/>
<Button Content= «5 - le dernier» Name= «x_btn5» />
</toolkit:DockPanel>
CHAPITRE 3 □ Les panneaux et le système de disposition 63
FIGURE 3.8

LastChildFill = true

La figure 3.9 illustre le résultat obtenu si la propriété booléenne LastChildFill est


fixée à false.
FIGURE 3.9

LastChildFill = false

6 - Le conteneur WrapPanel

Le WrapPanel est aussi un conteneur de la bibliothèque de classes Silverlight


Toolkit. Ce conteneur empile visuellement les éléments qu’il contient. La
différence du WrapPanel par rapport au StackPanel c’est que la pile constituée
continue automatiquement sur une nouvelle rangée quand elle atteint les limites
du conteneur. La pile est horizontale par défaut, mais peut être rendue verticale au
moyen de la propriété Orientation.
En orientation horizontale, la hauteur d’une ligne est déterminée par l’élément
le plus haut, et la longueur d’une ligne par l’addition des longueurs des différents
64 Développez des applications Internet avec Silverlight 5
éléments contenus.
En orientation verticale, la largeur d’une colonne est déterminée par l’élément le
plus large, et la hauteur d’une colonne par l’addition des hauteurs des différents
éléments contenus.
La figure 3.10 (projet PanneauxWrapPanel.sln dans le dossier chapitre03) illustre
les effets de l’orientation horizontale (repère n°1) et de l’orientation verticale
(repère n°2).
<toolkit:WrapPanel Name= «x_wrappanel_root» Background= «WhiteSmoke»
Orientation= «Horizontal» Height= «184»>
<Button Content= «Bouton 1» Height= «27» Name= «x_btn1» Width= «139» />
<Button Content= «Bouton 2» Height= «27» Name= «x_btn2» Width= «83» />
<Button Content= «Bouton 3» Height= «27» Name= «x_btn3» Width= «156» />
<Button Content= «Bouton 4» Height= «53» Name= «x_btn4» Width= «156» />
<Button Content= «Bouton 5» Height= «27» Name= «x_btn5» Width= «94» />
<Button Content= «Bouton 6» Height= «27» Name= «x_btn6» Width= «138» />
</toolkit:WrapPanel>

FIGURE 3.10

1 2

7 - Les propriétés impactant la disposition

Tous les objets FrameworkElement disposent de propriétés que le panneau


conteneur prend en compte lors des calculs d’agencement: les dimensions, les
Copyright 2012 Patrice REY

marges, l’alignement, la profondeur et la visibilité.

7.1 - Les dimensions

Les dimensions souhaitées pour un élément sont la largeur (propriété Width) et la


hauteur (propriété Height). Pour indiquer des contraintes de taille au système de
disposition, selon l’horizontale et la verticale, il est possible d’utiliser les propriétés
CHAPITRE 3 □ Les panneaux et le système de disposition 65
MinWidth, MinHeight, MaxWidth et MaxHeight.
Les dimensions finalement adoptées par le système de disposition sont reflétées
dans les propriétés ActualWidth et ActualHeight (propriétés accessibles en lecture
seule).

7.2 - Les marges

La propriété Margin définit les marges autour d’un élément. Si une seule valeur est
assignée à la propriété, une marge de cette dimension est appliquée tout autour
de l’élément. Si deux valeurs sont spécifiées, la première valeur est affectée aux
marges gauche et droite, et la deuxième valeur est affectée aux marges haute et
basse. Si quatre valeurs sont spécifiées, elles sont assignées respectivement aux
marges gauche, haute, droite et basse.
La figure 3.11 illustre les différents cas de positionnement en fonction des marges
sur un Grid composé de quatre cellules (projet ProprieteDisposition.sln dans le
dossier chapitre03).
FIGURE 3.11

<Grid x:Name= «x_grid_root» Background= «WhiteSmoke» ShowGridLines= «True»


Width= «400» Height= «300»>
<Grid.RowDefinitions>
<RowDefinition Height= «150» />
<RowDefinition Height= «150» />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
66 Développez des applications Internet avec Silverlight 5
<ColumnDefinition Width= «200» />
<ColumnDefinition Width= «200» />
</Grid.ColumnDefinitions>
<Button Content= «bouton sans marge» Name= «x_btn1» Grid.Row= «0»
Grid.Column= «0» />
<Button Content= «marge = 20» Name= «x_btn2» Grid.Row= «1» Grid.Column= «0»
Margin= «20» />
<Button Content= «marge = 20,50» Name= «x_btn3» Grid.Row= «0»
Grid.Column= «1» Margin= «20,50» />
<Button Content= «marge = 10,20,50,60» Name= «x_btn4» Grid.Row= «1»
Grid.Column= «1» Margin= «10,20,50,60» />
</Grid>
La propriété Padding existe sur les éléments qui disposent d’un contenu, pour
indiquer la marge à appliquer autour de ce contenu à l’intérieur de l’élément.
Les classes Border, TextBlock, et celles héritant de Control, disposent de cette
propriété. La propriété Padding utilise rigoureusement la même syntaxe que
Margin.

7.3 - L’alignement

La propriété HorizontalAlignment contient une valeur énumérée indiquant la


disposition horizontale de l’élément dans sa zone englobante (Left, Center, Right
ou Stretch). La valeur par défaut est l’étirement (Stretch), mais cette valeur est
ignorée si la propriété Width est définie (avec une valeur différente de Auto).
La propriété VerticalAlignment contient une valeur énumérée indiquant la
disposition verticale de l’élément dans sa zone englobante (Top, Center, Bottom
ou Stretch). La valeur par défaut est l’étirement (Stretch), mais cette valeur est
ignorée si la propriété Height est définie (avec une valeur différente de Auto).
La figure 3.12 illustre les différents cas d’alignement sur un Grid composé de neuf
cellules (projet ProprieteDisposition.sln dans le dossier chapitre03).
<Grid x:Name= «x_grid_root» Background= «WhiteSmoke» ShowGridLines= «True»
Width= «400» Height= «300»>
...
<Button Content= «H Left - V Top» Name= «x_btn1» VerticalAlignment= «Top»
Copyright 2012 Patrice REY

HorizontalAlignment= «Left» Grid.Column= «0» Grid.Row= «0» />


<Button Content= «H Center - V Top» HorizontalAlignment= «Center»
Name= «x_btn2»
VerticalAlignment= «Top» Grid.Column= «1» Grid.Row= «0» />
<Button Content= «H Right - V Top» HorizontalAlignment= «Right»
Name= «x_btn3»
VerticalAlignment= «Top» Grid.Column= «2» Grid.Row= «0» />
<Button Content= «H Left - V Center» Name= «x_btn4»
CHAPITRE 3 □ Les panneaux et le système de disposition 67
VerticalAlignment= «Center»
HorizontalAlignment= «Left» Grid.Column= «0» Grid.Row= «1» />
<Button Content= «H Center - V Center» HorizontalAlignment= «Center»
Name= «x_btn5»
VerticalAlignment= «Center» Grid.Column= «1» Grid.Row= «1» />
<Button Content= «H Right - V Center» HorizontalAlignment= «Right»
Name= «x_btn6»
VerticalAlignment= «Center» Grid.Column= «2» Grid.Row= «1» />
<Button Content= «H Left - V Bottom» Name= «x_btn7»
VerticalAlignment= «Bottom»
HorizontalAlignment= «Left» Grid.Column= «0» Grid.Row= «2» />
<Button Content= «H Center - V Bottom» HorizontalAlignment= «Center»
Name= «x_btn8»
VerticalAlignment= «Bottom» Grid.Column= «1» Grid.Row= «2» />
<Button Content= «H Right - V Bottom» HorizontalAlignment= «Right»
Name= «x_btn9»
VerticalAlignment= «Bottom» Grid.Column= «2» Grid.Row= «2» />
</Grid>

FIGURE 3.12

7.4 - La profondeur

La profondeur est gérée par la propriété ZIndex. ZIndex est une propriété attachée
de la classe Canvas qui détermine l’ordre de recouvrement de plusieurs éléments
se chevauchant. Plus cette valeur est élevée, et plus l’élément sera mis à l’avant-
plan. Cette propriété attachée a pour valeur zéro par défaut, et le dernier élément
ajouté au conteneur recouvre les autres.
La figure 3.13 illustre différents cas de profondeur (projet ProprieteDisposition.sln
68 Développez des applications Internet avec Silverlight 5
dans le dossier chapitre03): le cas où aucun ZIndex n’est pas spécifié (repère n°1)
et le cas où un bouton (button8) sur les cinq a sa propriété ZIndex spécifiée à 5
(repère n°2).
FIGURE 3.13

1 2

<Canvas Name= «x_cnv_root» Background= «WhiteSmoke»>


<Button Canvas.Left= «20» Canvas.Top= «12» Content= «bouton 1» Height= «43»
Name= «button1» Width= «59» />
<Button Canvas.Left= «49» Canvas.Top= «44» Content= «bouton 2» Height= «43»
Name= «button2» Width= «59» />
<Button Canvas.Left= «78» Canvas.Top= «77» Content= «bouton 3» Height= «43»
Name= «button3» Width= «59» />
<Button Canvas.Left= «49» Canvas.Top= «108» Content= «bouton 4» Height= «43»
Name= «button4» Width= «59» />
<Button Canvas.Left= «20» Canvas.Top= «139» Content= «bouton 5» Height= «43»
Name= «button5» Width= «59» />
<Button Canvas.Left= «230» Canvas.Top= «12» Content= «bouton 1» Height= «43»
Name= «button6» Width= «59» />
<Button Canvas.Left= «259» Canvas.Top= «44» Content= «bouton 2» Height= «43»
Name= «button7» Width= «59» />
<Button Canvas.Left= «288» Canvas.Top= «77» Content= «bouton 3» Height= «43»
Name= «button8» Width= «59» Canvas.ZIndex= «5»/>
<Button Canvas.Left= «259» Canvas.Top= «108» Content= «bouton 4» Height= «43»
Name= «button9» Width= «59» />
<Button Canvas.Left= «230» Canvas.Top= «139» Content= «bouton 5» Height= «43»
Name= «button10» Width= «59» />
</Canvas>
Copyright 2012 Patrice REY

7.5 - La visibilité

La visibilité d’un élément est définie par sa propriété Visibility qui peut prendre
deux valeurs: Visible (valeur par défaut) pour un élément visible, et Collapsed pour
un élément qui n’est pas affiché et que le système de disposition ignore.
CHAPITRE 3 □ Les panneaux et le système de disposition 69

8 - Les autres conteneurs

Les autres conteneurs sont Border, Viewbox et ScrollViewer. Border et


Viewbox héritent directement de FrameworkElement, et ScrollViewer hérite de
ContentControl qui hérite de Control qui hérite de FrameworkElement. La figure
3.14 visualise l’arbre d’héritage.
FIGURE 3.14

8.1 - Le conteneur Border

La classe Border est un conteneur visuel qui matérialise une bordure au moyen de
ses propriétés:
• BorderBrush pour la couleur de la bordure,
• BorderThickness pour l’épaisseur de la bordure autour d’un élément enfant.
L’objet Border peut définir aussi une couleur de fond par sa propriété Background.
Les angles peuvent être arrondis au moyen de sa propriété CornerRadius. Il dispose
d’une propriété Padding qui définit les marges autour de l’élément enfant. Il peut
être utilisé pour matérialiser un conteneur non visuel comme un StackPanel.

8.2 - Le conteneur Viewbox

La classe Viewbox est un conteneur qui applique à son élément enfant un


étirement. Ce conteneur est très pratique pour mettre à l’échelle automatiquement
un contenu en fonction de l’espace disponible.
70 Développez des applications Internet avec Silverlight 5
L’étirement peut être contrôlé par la propriété Stretch (sa valeur par défaut est
Uniform). La propriété énumérée StretchDirection indique si l’élément enfant est
agrandi seulement quand il est plus petit que le parent (UpOnly), s’il est réduit
quand il est plus grand que le parent (DownOnly), ou bien les deux (Both, valeur
par défaut).
La figure 3.15 (projet ConteneurViewbox.sln dans le dossier chapitre03) illustre les
effets du Viewbox.
FIGURE 3.15

quand la fenêtre est agrandie, le


Viewbox met à l’échelle
automatiquement son contenu

<Grid x:Name= «x_grid_root» Background= «WhiteSmoke»>


<Viewbox>
<Border BorderBrush= «Black» BorderThickness= «1» Height= «139»
Name= «border1» Width= «376» CornerRadius= «10»>
<TextBlock Height= «47» Name= «textBlock1»
Text= «un contenu écrit à définir»
Copyright 2012 Patrice REY

FontFamily= «Verdana» FontSize= «12»


HorizontalAlignment= «Left» VerticalAlignment= «Top» Width= «375» />
</Border>
</Viewbox>
</Grid>
CHAPITRE 3 □ Les panneaux et le système de disposition 71

8.3 - Le conteneur ScrollViewer

La classe ScrollViewer permet de faire défiler son élément enfant, tel qu’un
conteneur Panel disposant de davantage d’éléments qu’il ne peut en afficher
simultanément. Des ascenseurs apparaissent automatiquement si besoin est.
Leur affichage peut être contrôlé par les propriétés HorizontalScrollBarVisibility et
VerticalScrollBarVisibility, conditionnant le sens de défilement.
La figure 3.16 (projet Scrollviewer.sln dans le dossier chapitre03) illustre deux
ScrollViewer qui contiennent chacun un Canvas de hauteur différente.
FIGURE 3.16

<Grid x:Name= «x_grid_root» Background= «WhiteSmoke»>


<Grid.ColumnDefinitions>
<ColumnDefinition Width= «180» />
<ColumnDefinition Width= «220» />
</Grid.ColumnDefinitions>
<ScrollViewer Height= «300» HorizontalAlignment= «Left» Name= «scrollViewer1»
VerticalAlignment= «Top» Width= «180»>
<Canvas Height= «314» Name= «canvas1» Width= «152»
HorizontalAlignment= «Left» VerticalAlignment= «Top»
Background= «Silver» />
</ScrollViewer>
<ScrollViewer Grid.Column= «1» Height= «300» HorizontalAlignment= «Left»
Name= «scrollViewer2»
VerticalAlignment= «Top» Width= «220»>
<Canvas Height= «395» Name= «canvas2» Width= «189»
HorizontalAlignment= «Left» VerticalAlignment= «Top»
Background= «DarkGray» />
72 Développez des applications Internet avec Silverlight 5
</ScrollViewer>
</Grid>

Copyright 2012 Patrice REY


CHAPITRE 4 DANS CE CHAPITRE
• Définition d’une

Propriétés de
propriété de
dépendance
• Implémentation
dépendances et d’une propriété de
dépendance simple
• Implémentation
événements routés d’une propriété de
dépendance attachée
• Les événements
routés
• Le routage de type
La conception d’une interface utilisateur se fait bubbling
• Les événements liés
par l’ajout de contrôles à personnaliser. Le fait au clavier
d’imbriquer des contrôles dans d’autres contrôles • Le survol et le
positionnement de la
permet de réaliser des interfaces utilisateur riches, souris
dotées de fonctionnalités spécifiques. • La gestion du clic de
Concevoir un contrôle necéssite de lui fournir la souris
• La gestion de la
des propriétés de dépendances de façon à le molette de la souris
personnaliser et à lui apporter des fonctionnalités • La capture de la souris
dans un contexte donné. • Le focus
• Les commandes
Nous allons voir dans ce chapitre la création et
l’implémentation des propriétés de dépendances
au travers de la propriété de dépendance simple et
la propriété de dépendance attachée.
Un contrôle est interactif s’il réagit à des
événements spécifiques dans un contexte donné.
Nous verrons le principe des événements routés
qui sont implémentés dans Silverlight.
Nous aborderons la gestion des événements liés
au clavier et à la souris. La souris permet de faire
beaucoup de choses grâce à son déplacement, son
positionnement, ses boutons et sa molette. Nous
verrons l’implémentation du clic souris, la capture
de la souris et la gestion de sa molette.
74 Développez des applications Internet avec Silverlight 5

La conception d’une interface utilisateur se fait par l’ajout de contrôles à


personnaliser. Le fait d’imbriquer des contrôles dans d’autres contrôles permet
de réaliser des interfaces utilisateur riches, dotées de fonctionnalités spécifiques.

1 - Les propriétés de dépendances

Nous avons vu dans les précédents chapitres que, lors de l’ajout d’un contrôle donné,
il fallait personnaliser ce contrôle par l’affectation de valeurs à ses propriétés (par
exemple un nombre pour fixer une largeur, une chaîne de caractères pour fixer une
couleur, etc.). Ces propriétés qui personnalisent le contrôle sont des propriétés
de dépendances (dependency property). Il y a principalement deux sortes de
propriétés de dépendances: les propriétés simples et les propriétés attachées.

1.1 - L’exemple du rectangle

En XAML, quand on ajoute un objet Rectangle sur un Canvas (figure 4.1), on utilise
la balise <Rectangle> et on personnalise les attributs de cette balise pour indiquer:
• le nom attribué au rectangle par sa propriété Name (Name = «x_rect»),
• la largeur du rectangle par sa propriété Width (Width = «376»),
• la hauteur du rectangle par sa propriété Height (Height = «31»),
• l’épaisseur de sa bordure par la propriété StrokeThickness (StrokeThickness =
«1»),
• la couleur de remplissage du rectangle par sa propriété Fill (Fill = «Gainsboro»),
• son positionnement horizontal, à partir de la gauche, sur le Canvas par la
propriété Canvas.Left (Canvas.Left = «12»),
• son positionnement vertical, à partir du haut, sur le Canvas par la propriété
Canvas.Top (Canvas.Top = «12»),
• etc.
FIGURE 4.1
Copyright 2012 Patrice REY

Rectangle x_rect
Canvas x_cnv_root
CHAPITRE 4 □ Propriétés de dépendances et événements routés 75
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
<Rectangle Height= «31» HorizontalAlignment= «Left» Name= «x_rect»
Stroke= «Black»
StrokeThickness= «1» VerticalAlignment= «Top» Width= «376»
Canvas.Left= «12»
Canvas.Top= «12» Fill= «Gainsboro» />
</Canvas>
La personnalisation du rectangle se fait par l’affectation de valeurs à ses propriétés.
Autrement dit, l’élément Rectangle expose des propriétés qui sont des propriétés
de dépendances. La propriété Width est qualifiée de propriété de dépendance
simple (elle fixe la largeur propre au rectangle), et la propriété Canvas.Left est
qualifiée de propriété de dépendance attachée (elle permet de positionner le
rectangle dans son conteneur parent, puisque le rectangle se trouve ici dans un
conteneur Canvas).
Si on visualise le graphe d’héritage de la classe Rectangle (figure 4.2), on
voit que la classe Rectangle hérite de la classe Shape, puis Shape hérite de
FrameworkElement. La propriété Width du rectangle est en fait une propriété
héritée de FrameworkElement et la propriété StrokeThickness est une propriété
héritée de Shape.
FIGURE 4.2

Dans l’éditeur de code, si on atteint la définition de la propriété Width, on obtient


le code suivant dans Visual Studio:
namespace System.Windows {
public abstract class FrameworkElement : UIElement {
...
// Résumé :
// Identifie la propriété de dépendance
System.Windows.FrameworkElement.Width.
// Retourne :
// Identificateur de la propriété de dépendance
System.Windows.FrameworkElement.Width.
76 Développez des applications Internet avec Silverlight 5
public static readonly DependencyProperty WidthProperty;
...
// Résumé :
// Obtient ou définit la largeur d’un System.Windows.FrameworkElement.
// Retourne :
// Largeur de l’objet, en pixels. La valeur par défaut est
System.Double.NaN.
// À l’exception de la valeur System.Double.NaN particulière, cette
valeur doit
// être égale ou supérieure à 0. Consultez la section Notes pour plus
d’informations
// sur les limites supérieures.
public double Width { get; set; }
...
}
}
On voit que la propriété Width, de type double, possède des accesseurs (get et
set). Ces accesseurs permettent de lire et d’écrire une valeur correspondant à la
largeur. WidthProperty est la donnée membre utilisée par les accesseurs get et
set de Width. Elle est déclarée avec un type DependencyProperty (propriété de
dépendance) et elle est précédée du mot clé static (car c’est une donnée statique)
et du mot clé readonly (car elle ne peut être modifiée que dans le constructeur
statique).

1.2 - Implémentation d’une propriété simple

Dans le projet ProprieteDependance01.sln dans le dossier chapitre04, nous


allons réaliser un contrôle personnalisé de façon à l’instancier en XAML avec ses
propriétés de dépendances.
Commençons par ajouter un contrôle Silverlight intitulé Bandeau.xaml. Pour
ajouter un contrôle, il faut faire un clic droit sur le nom du projet, choisir ajouter
puis ajouter un nouvel élément dans le sous-menu. Dans la fenêtre qui s’ouvre,
sélectionnez contrôle Silverlight et donnez son nom (Bandeau.xaml).
On aura besoin de trois images pour la personnalisation (figure 4.3). Ajoutez au
Copyright 2012 Patrice REY

projet un nouveau dossier intitulé images, puis ajoutez les trois images suivantes au
format PNG: chapitre.png, livre.png et non_defini.png. Pour ajouter des éléments
existants, faire un clic droit sur le nom du dossier, choisir ajouter puis ajouter un
élément existant dans le sous-menu. Dans la fenêtre qui s’ouvre, localisez les trois
images puis cliquez sur le bouton ajouter (les images se trouvent dans le dossier
chapitre04).
CHAPITRE 4 □ Propriétés de dépendances et événements routés 77
FIGURE 4.3
pour ajouter un nouveau dossier: pour ajouter un élément existant:

Le contrôle Bandeau.xaml se compose d’un conteneur Grid avec 2 colonnes. Dans


la première colonne, on positionne une image x_logo, de type Image, dont la
propriété Source pointe sur l’image non_defini.png (située dans le dossier images).
Dans la deuxième colonne, on positionne un texte x_titre, de type TextBlock, avec
une police d’écriture Verdana de taille 18. La figure 4.4 visualise le contrôle, dans
l’éditeur graphique, tel qu’il est au départ. On compile par la touche F6.
FIGURE 4.4

<UserControl x:Class= «ProprieteDependance01.Bandeau»


xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:d= «http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc= «http://schemas.openxmlformats.org/markup-compatibility/2006»
mc:Ignorable= «d» d:DesignHeight= «50» d:DesignWidth= «400»>
<Grid x:Name= «x_grid_root» Background= «LightGray» Height= «50»
Width= «400»>
<Grid.ColumnDefinitions>
<ColumnDefinition Width= «50» />
<ColumnDefinition Width= «350» />
</Grid.ColumnDefinitions>
<Image Grid.ColumnSpan= «2» HorizontalAlignment= «Left» Name= «x_logo»
Stretch= «Fill» VerticalAlignment= «Top» Width= «50» Height= «50»
Source= «/ProprieteDependance01;component/images/non_defini.png» />
<TextBlock Grid.Column= «1» Height= «25» HorizontalAlignment= «Right»
Name= «x_titre» Text= «le titre» VerticalAlignment= «Center» Width= «340»
FontFamily= «Verdana» FontSize= «18» />
</Grid></UserControl>
78 Développez des applications Internet avec Silverlight 5
On passe dans le fichier MainPage.xaml. Pour pouvoir utiliser ce contrôle
Bandeau.xaml, il faut ajouter une référence à l’espace de noms qui le contient.
Cette référence, intitulée visuel, s’exprime par l’attribut xmlns de l’UserControl:
xmlns:visuel = «clr-namespace:ProprieteDependance01». Pour ajouter sur le
Canvas x_cnv_root un bandeau intitulé x_bandeau1, on écrit <visuel:Bandeau
x:Name = «x_bandeau1»> </visuel:Bandeau>. La figure 4.5 visualise le résultat
obtenu dans l’éditeur graphique.
FIGURE 4.5

<UserControl x:Class= «ProprieteDependance01.MainPage»


xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:d= «http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc= «http://schemas.openxmlformats.org/markup-compatibility/2006»
xmlns:visuel= «clr-namespace:ProprieteDependance01»
mc:Ignorable= «d» d:DesignHeight= «300» d:DesignWidth= «400»>
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
<visuel:Bandeau x:Name= «x_bandeau1»></visuel:Bandeau>
</Canvas>
</UserControl>
Ce que nous voulons maintenant, c’est ajouter des propriétés de dépendances de
façon à personnaliser le contrôle en mentionnant une valeur à un attribut donné.
Par exemple, on souhaiterait avoir un attribut intitulé BandeauTexte auquel on
affecterait une valeur qui modifierait le contenu du titre (on remplacerait la chaîne
le titre par la chaîne inscrite). Pour effectuer cette démarche, il faut créer une
propriété de dépendance BandeauTexte au contrôle Bandeau.
Pour pouvoir implémenter une propriété de dépendance, il faut que la classe
considérée hérite de la classe DependencyObject. Comme notre classe Bandeau
hérite de UserControl, elle hérite aussi de DependencyObject comme l’illustre le
Copyright 2012 Patrice REY

graphe d’héritage (figure 4.6).


La valeur d’une propriété de dépendance est stockée automatiquement au sein de
l’instance héritée de DependencyObject dans un dictionnaire interne dont les clés
sont les identifiants des propriétés. Une valeur d’une propriété de dépendance
est en fait gérée sous la forme d’un objet complexe, défini par la structure
EffectiveValueEntry.
CHAPITRE 4 □ Propriétés de dépendances et événements routés 79
FIGURE 4.6

Les caractéristiques de la structure EffectiveValueEntry sont:


• PropertyIndex qui est l’identifiant de la propriété de dépendance,
• Value qui est la valeur de la propriété (de type Object),
• IsExpression qui est un booléen indiquant si la valeur stockée est une expression,
• IsAnimated qui est un booléen indiquant si une animation est en cours.
Dans l’objet DependencyObject, un dictionnaire interne stocke sous la forme
d’objets EffectiveValueEntry les valeurs des propriétés de dépendances de
l’instance courante. La démarche de création d’une propriété de dépendance est
identique quelque soit le type de cette propriété.
La création d’un identifiant de la propriété de dépendance passe par la création
d’une instance de la classe DependencyProperty (ici BandeauTextePropriete). Cette
instance doit être référencée comme champ statique public de la classe supportant
la propriété (mot clé static). Il doit aussi être enregistré dans le système de propriété
au moyen de la méthode statique Register de la classe DependencyProperty. Cet
appel inscrit la propriété dans un dictionnaire statique privé de la même classe, et
lui affecte un identifiant de type entier.
//propriete de dependance: BandeauTexte
public static readonly DependencyProperty BandeauTextePropriete =
DependencyProperty.Register(
...
);
80 Développez des applications Internet avec Silverlight 5
La méthode Register prend en paramètres:
• Le nom de la propriété: ici «BandeauTexte»,
• Son type: ici string car c’est une chaîne que l’on inscrit: typeof(string),
• Le type de la classe qui expose la propriété: ici c’est Bandeau,
• Un objet PropertyMetadata représentant les métadonnées: on passe la valeur
null si les métadonnées sont inutiles; ici nous passerons la chaîne «mon titre».
//propriete de dependance: BandeauTexte
public static readonly DependencyProperty BandeauTextePropriete =
DependencyProperty.Register(
«BandeauTexte»,
typeof(string),
typeof(Bandeau),
new PropertyMetadata(«mon titre»)
);
Pour exposer une propriété de dépendance au travers d’un wrapper CLR, il faut
coder dans le code de ses accesseurs get et set un appel aux méthodes GetValue et
SetValue de DependencyObject. Ces méthodes permettent d’accéder au stockage
de la valeur dans le dictionnaire interne, respectivement en lecture et en écriture.
L’objet statique de type DependencyProperty leur est passé en argument car il sert
de clé dans le dictionnaire de stockage interne.
public string BandeauTexte {
get { return (string)GetValue(BandeauTextePropriete); }
set { SetValue(BandeauTextePropriete, value); }
}
Les mécanismes de Silverlight n’exploitant pas tous le wrapper CLR d’une propriété
de dépendance, les opérations réalisées habituellement dans les accesseurs get et
set d’une propriété CLR doivent être gérées autrement.
La méthode statique DependencyProperty.Register permet de les définir sous la
forme d’informations qui consistent en une valeur par défaut et une référence de
méthode callback statique servant à gérer une réponse au changement de valeur.
Ces informations sont gérées par un objet de type PorpertyMetadata passé à la
méthode Register.
Copyright 2012 Patrice REY

Le constructeur de PropertyMetadata attend les arguments suivants:


• defaultValue: une valeur par défaut,
• propertyChangedCallback: une méthode callback statique de type
PropertyChangedCallback, appelée lors du changement de valeur de la
propriété.
Trois surcharges du constructeur sont proposées pour mixer ces arguments.
PropertyChangedCallback est un délégué dont la signature est la suivante:
CHAPITRE 4 □ Propriétés de dépendances et événements routés 81
public delegate void PropertyChangedCallback(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
Le callback reçoit en premier argument l’instance d de l’objet qui expose
la propriété de dépendance. Le deuxième argument, une structure
DependencyPropertyChangedEventArgs, transporte la valeur initiale et la nouvelle
valeur de la propriété de dépendance, respectivement dans les propriétés OldValue
et NewValue.
Ici le callback est défini par la méthode statique InscrireLeTexte. Elle consiste à
récupérer l’instance de Bandeau, puis elle modifie la propriété Text de x_titre en
lui affectant la valeur de la propriété BandeauTexte
//propriete de dependance: BandeauTexte
public static readonly DependencyProperty BandeauTextePropriete =
DependencyProperty.Register(
«BandeauTexte»,
typeof(string),
typeof(Bandeau),
new PropertyMetadata(«mon titre», InscrireLeTexte)
);
private static void InscrireLeTexte(DependencyObject dp_obj,
DependencyPropertyChangedEventArgs e) {
Bandeau le_bandeau = dp_obj as Bandeau;
le_bandeau.x_titre.Text = le_bandeau.BandeauTexte;
}
Dans l’éditeur graphique de MainPage.xaml (figure 4.7), on a la propriété
BandeauTexte qui apparait avec une valeur par défaut (repère n°1 avec la valeur
mon titre). Pour personnaliser cette propriété par un attribut en XAML, on écrira
par exemple BandeauTexte = «Livre: les conjugaisons» (repère n°2).
On procède de la même manière pour créer une propriété de dépendance qui
nous permet de changer l’image de x_logo au travers de sa propriété Source. Pour
cela on utilisera une énumération TypeBandeau pour représenter les trois images
possibles.
public enum TypeBandeau { non_defini, livre, chapitre };
//propriete de dependance: BandeauTypePropriete
public static readonly DependencyProperty BandeauTypePropriete =
DependencyProperty.Register(«BandeauType», typeof(TypeBandeau),
typeof(Bandeau),
new PropertyMetadata(Bandeau.TypeBandeau.non_defini, InscrireLogo));
public TypeBandeau BandeauType {
get { return (TypeBandeau)GetValue(BandeauTypePropriete); }
set { SetValue(BandeauTypePropriete, value); }
}
82 Développez des applications Internet avec Silverlight 5
FIGURE 4.7

private static void InscrireLogo(DependencyObject dp_obj,


DependencyPropertyChangedEventArgs e) {
Bandeau le_bandeau = dp_obj as Bandeau;
BitmapImage bi3 = new BitmapImage();
switch (le_bandeau.BandeauType) {
case TypeBandeau.non_defini:
bi3.UriSource = new Uri(«images/non_defini.png», UriKind.Relative);
break;
case TypeBandeau.livre:
bi3.UriSource = new Uri(«images/livre.png», UriKind.Relative);
break;
case TypeBandeau.chapitre:
bi3.UriSource = new Uri(«images/chapitre.png», UriKind.Relative);
break;
}
le_bandeau.x_logo.Source = bi3;
}
Le changement de la couleur du fond du Grid se fera par la modification de sa
Copyright 2012 Patrice REY

propriété Background. On utilisera la possibilité d’une couleur unie avec un objet


SolidColorBrush. La propriété de dépendance BandeauCouleurFond procédera à
cette modification.
//propriete de dependance: BandeauCouleurFond
public static readonly DependencyProperty BandeauCouleurFondPropriete =
DependencyProperty.Register(«BandeauCouleurFond», typeof(SolidColorBrush),
typeof(Bandeau),
CHAPITRE 4 □ Propriétés de dépendances et événements routés 83
new PropertyMetadata(new SolidColorBrush(Colors.LightGray),
InscrireCouleurFond));
public SolidColorBrush BandeauCouleurFond {
get { return (SolidColorBrush)GetValue(BandeauCouleurFondPropriete); }
set { SetValue(BandeauCouleurFondPropriete, value); }
}
private static void InscrireCouleurFond(DependencyObject dp_obj,
DependencyPropertyChangedEventArgs e) {
Bandeau le_bandeau = dp_obj as Bandeau;
le_bandeau.x_grid_root.Background = le_bandeau.BandeauCouleurFond;
}
La figure 4.8 illustre l’ajout d’un Bandeau x_bandeau1 dont la propriété
BandeauTexte est Livre: les conjugaisons, la propriété BandeauType est livre, et la
propriété BandeauCouleurFond est GhostWhite.
FIGURE 4.8

La figure 4.9 illustre l’ajout de deux bandeaux, le premier concernant un livre, et le


deuxième concernant un chapitre.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
<visuel:Bandeau x:Name= «x_bandeau1» BandeauTexte= «Livre: les conjugaisons»
BandeauType= «livre» BandeauCouleurFond= «GhostWhite»>
</visuel:Bandeau>
<visuel:Bandeau x:Name= «x_bandeau2» BandeauTexte= «Chapitre: le présent»
BandeauType= «chapitre» BandeauCouleurFond= «Silver» Canvas.Left= «0»
Canvas.Top= «56»>
</visuel:Bandeau>
</Canvas>

FIGURE 4.9
84 Développez des applications Internet avec Silverlight 5

1.3 - Implémentation d’une propriété attachée

Le mécanisme de propriété attachée permet d’enrichir dynamiquement un objet


avec des propriétés spécifiques à un contexte donné. Nous avons déjà vu ce
concept dans l’utilisation, par exemple, de la propriété attachée Canvas.Top pour
positionner un élément dans un conteneur Canvas. Nous allons étudier ici la façon
d’exposer une propriété attachée.
Le projet ProprieteDependanceAttache.sln, dans le dossier chapitre04, montre
comment implémenter une propriété attachée au travers de la réalisation d’un
contrôle personnalisé.
Nous allons créer un contrôle personnalisé WrapPanelModif qui est un conteneur
comme le WrapPanel de la bibliothèque de classes Silverlight Toolkit, au détail
près qu’il empile uniquement les objets à l’horizontale. Nous allons doter ce
WrapPanelModif d’une propriété de dépendance attachée de façon à forcer un
retour à la ligne d’un élément lors de l’empilement.
On commence par déclarer une classe WrapPanelModif qui hérite de la classe de
base abstraite Panel.
namespace ProprieteDependanceAttache {
public class WrapPanelModif : Panel {
...
}//end class
}
On ajoute une propriété de dépendance simple qui stocke l’orientation de
l’empilement. On utilise pour cela l’énumération System.Windows.Controls.
Orientation qui peut prendre les valeurs Horizontal et Vertical. On fixe la propriété
Orientation, par défaut, à la valeur Orientation.Horizontal.
//propriete de dependance simple: Orientation
public Orientation Orientation {
get { return (Orientation)GetValue(OrientationPropriete); }
set { SetValue(OrientationPropriete, value); }
}
Copyright 2012 Patrice REY

public static readonly DependencyProperty OrientationPropriete =


DependencyProperty.Register(
«Orientation»,
typeof(Orientation),
typeof(WrapPanelModif),
new PropertyMetadata(Orientation.Horizontal));
Maintenant, nous allons implémenter une propriété attachée, intitulée
ForcerRetourALaLigne, de façon à permettre lors de l’insertion d’un élément, de
CHAPITRE 4 □ Propriétés de dépendances et événements routés 85
forcer le retour à la ligne dans l’ordre de l’empilement en cours.
Une propriété attachée est une propriété de dépendance définie au sein d’un
élément qui décrit un contexte. Elle permet à chaque élément de définir une
propriété qui sera exploitée dans ce contexte. Elle s’implémente selon le même
principe qu’une propriété de dépendance simple, mais:
• l’enregistrement doit se faire au moyen de la méthode statique DependencyPr
operty.RegisterAttached.
• la classe qui expose la propriété attachée doit implémenter des accesseurs
statiques Get et Set suffixés du nom de la propriété, mais pas de wrapper CLR
classiques comme pour la propriété de dépendance simple.
La signature de la méthode Get doit être la suivante (avec cible représentant
l’élément enfant ciblé): public static object GetNomPropriete(object cible).
La signature de la méthode Set doit être la suivante: public static void
SetNomPropriete(object cible, object value).
La valeur ou l’élément enfant peuvent être typés plus précisément comme par
exemple dans le cas de Canvas.Top, la valeur est de type double et l’élément ciblé
est de type FrameworkElement.
Ici nous déclarons une propriété attachée ForcerRetourALaLignePropriete, de type
DependencyProperty, par la méthode statique RegisterAttached. Il s’agit d’une
propriété booléenne appartenant à WrapPanelModif. Les accesseurs statiques
seront SetForcerRetourALaLigne et GetForcerRetourALaLigne.
//propriete de dependance attachee: ForcerRetourALaLigne
public static DependencyProperty ForcerRetourALaLignePropriete =
DependencyProperty.RegisterAttached(
«ForcerRetourALaLigne»,
typeof(bool),
typeof(WrapPanelModif),
null);
public static void SetForcerRetourALaLigne(UIElement element, Boolean value) {
element.SetValue(ForcerRetourALaLignePropriete, value);
}
public static Boolean GetForcerRetourALaLigne(UIElement element) {
return (bool)element.GetValue(ForcerRetourALaLignePropriete);
}
Lorsque des éléments doivent être restitués à l’écran ou que la taille d’un élément
change, le système de disposition est appelé. La première passe de disposition est
une passe de mesure (Measure) qui permet de déterminer la taille souhaitée de
chaque élément enfant. La seconde passe est une passe de réorganisation (Arrange)
qui permet de déterminer la taille et la position finales du cadre englobant de
chaque élément enfant.
86 Développez des applications Internet avec Silverlight 5
Pour effectuer cette mise à jour de la disposition, il faut redéfinir les méthodes
MeasureOverride (la passe de mesure) et ArrangeOverride (la passe de
réorganisation).
//redefinition:
protected override Size MeasureOverride(Size tailleDisponible) {
...
}
//redefinition
protected override Size ArrangeOverride(Size tailleFinale) {
...
}
Au cours de la passe de mesure, le système de disposition indique au Panel sa
valeur tailleDisponible. Il s’agit de la zone que le parent du Panel met à disposition
pour disposer ses enfants. Le Panel évalue les propriétés de taille natives de chacun
de ses enfants.
Les propriétés de FrameworkElement définies sur chaque enfant sont
ensuite traitées. Ces propriétés ont tendance à décrire les caractéristiques de
dimensionnement du UIElement sous-jacent. Chacune de ces propriétés peut
modifier l’espace nécessaire à l’affichage de l’élément. Le Panel appelle ensuite
la méthode Measure sur chacun de ses enfants, en passant la taille disponible de
l’enfant en question. La taille disponible peut correspondre à la taille demandée
pour l’enfant, mais le parent peut également choisir de limiter la taille de l’enfant
en fonction du nombre d’éléments à disposer et de son propre tailleDisponible.
Lors de la passe de mesure, le système de disposition a pour objectif final de
déterminer le DesiredSize de chaque enfant, lequel est généré en interne après
l’appel de Measure. Cette valeur est stockée et est utilisée lors du processus de
réorganisation.
Lors de la passe de réorganisation, le système de disposition indique au Panel la
valeur tailleFinale qui est disponible pour lui et ses enfants. Au cours de la passe
de réorganisation, l’élément Panel parent évalue le DesiredSize de l’enfant, ainsi
que toutes les autres marges pouvant influer sur la taille rendue de l’élément, et
détermine le cadre englobant de chaque enfant. Le cadre englobant détermine les
Copyright 2012 Patrice REY

dimensions de l’emplacement de disposition de l’enfant. Le Panel parent appelle


ensuite la méthode Arrange de chaque enfant, en passant le Rect qui définit le
point d’origine de l’enfant dans le panneau, sa largeur et sa hauteur.
Le système de disposition effectue une dernière évaluation des propriétés
de décalage (marge et alignement par exemple) et place l’enfant dans son
emplacement de disposition. L’enfant n’a pas besoin de remplir intégralement
l’espace alloué (et ne le remplit souvent pas). Le contrôle est alors rendu au Panel
CHAPITRE 4 □ Propriétés de dépendances et événements routés 87
parent et le processus de disposition est terminé.
Dans MainPage.xaml, pour ajouter un conteneur WrapPanelModif, on ajoute un
attribut supplémentaire à l’UserControl pour référencer ce contrôle (xmlns:visuel
= «clr-namespace:ProprieteDependanceAttache»). Sur le Canvas x_cnv_root, on
positionne un WrapPanelModif intitulé x_wrappanelmodif. La valeur par défaut
de sa propriété Orientation est Horizontal.
On insère une série de boutons: le bouton dit classique a une largeur de 80 pixels et
le bouton dit large a une largeur de 140 pixels. Le bouton x_btn3, dont le contenu
est large avec RC, est un bouton de type large dont on fixe la propriété attachée
visuel:WrapPanelModif.ForcerRetourALaLigne à false.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Height= «100»>
<TextBlock Height= «23» Name= «x_titre» Text= «un WrapPanel personnalisé»
Width= «376» FontFamily= «Verdana» FontSize= «16» TextAlignment= «Center»
Canvas.Left= «15» Canvas.Top= «2» />
<visuel:WrapPanelModif Canvas.Top= «29» Width= «400» Height= «49»
Canvas.Left= «0» Background= «Gainsboro»
Orientation= «Horizontal» Visibility= «Visible» x:Name= «x_wrappanelmodif»>
<Button Content= «classique» Height= «23» Name= «x_btn1» Width= «80»
Visibility= «Visible» />
<Button Content= «large» Height= «23» Name= «x_btn2» Width= «140» />
<Button Content= «large avec RC» Height= «23» Name= «x_btn3» Width= «140»
visuel:WrapPanelModif.ForcerRetourALaLigne= «False»/>
<Button Content= «classique» Height= «23» Name= «x_btn4» Width= «80» />
<Button Content= «classique» Height= «23» Name= «x_btn5» Width= «80» />
</visuel:WrapPanelModif>
</Canvas>
La figure 4.10 illustre le résultat obtenu. Comme l’empilement est horizontal, les
boutons sont positionnés les uns à côté des autres tant qu’il y a de la place. Comme
il reste assez de place en première ligne, le bouton x_btn3 est ajouté.
FIGURE 4.10

Si on fixe la propriété attachée visuel:WrapPanelModif.ForcerRetourALaLigne à


true, on force le positionnement du bouton x_btn3 sur une autre ligne (comme
l’illustre la figure 4.11) malgré qu’il y avait la place sur la première ligne.
FIGURE 4.11
88 Développez des applications Internet avec Silverlight 5

2 - La gestion de l’interactivité

En réponse aux interactions de l’utilisateur (par le clavier, par la souris et par le


toucher), les contrôles émettent des événements. Un mécanisme particulier est
mis en œuvre pour propager un événement aux différents niveaux de l’arbre des
contrôles: le mécanisme des événements routés.

2.1 - Les événements routés

La notion d’arbre visuel induit la notion particulière d’événement routé. Les


événements routés forment un modèle de gestion d’événements propre à
Silverlight. On les exploite au moyen d’un gestionnaire d’événements de type
RoutedEventHandler, recevant un argument de type RoutedEventArgs (ou d’une
classe héritée).
Quand un événement est émis par un élément de l’arbre visuel, il est traité au
niveau de l’élément source puis successivement par chaque niveau de conteneur,
dans le sens montant, jusqu’à la racine de l’arbre. Ce mode de propagation, ou de
routage, s’appelle le bubbling par analogie avec une bulle (bubble) qui remonte
vers la surface.
Les événements routés sont notamment:
• l’événement Click de la classe Button.
• les événements de la souris MouseLeftButtonDown, MouseLeftButtonUp,
MouseRightButtonDown, MouseRightButtonUp et MouseMove.
• les événements de clavier KeyDown et KeyUp.
• les événements de gestion du focus GotFocus et LostFocus.
La figure 4.12 visualise l’arbre d’héritage des classes DependencyObject, UIElement
et FrameworkElement.
La classe DependencyObject implémente le système des propriétés de dépendances
de Silverlight. La classe UIElement, qui dérive de DependencyObject, représente
Copyright 2012 Patrice REY

un objet visuel. Elle introduit les bases du système de disposition visuelle des
éléments, l’interactivité évoluée avec les périphériques de saisie, et la notion de
focus. La classe FrameworkElement, qui dérive de UIElement, offre les principales
fonctionnalités de haut niveau du framework avec la gestion de la disposition
visuelle, la liaison de données, les styles, etc.
Tous les contrôles héritent de FrameworkElement, et par conséquent de UIElement,
donc ils peuvent implémenter les événements hérités de ces deux classes.
CHAPITRE 4 □ Propriétés de dépendances et événements routés 89
FIGURE 4.12

La classe UIElement apporte un ensemble d’événements qui sont:


• DoubleTap: se produit lorsque le système d’entrée signale un double tapement
du doigt sur l’élément considéré comme cible.
• DragEnter: se produit lorsque le système d’entrée signale un événement de
glissement sous-jacent vers l’élément considéré comme cible.
• DragLeave: se produit lorsque le système d’entrée signale un événement de
glissement sous-jacent de l’élément considéré comme source.
• DragOver: se produit lorsque le système d’entrée signale un événement de
glissement sous-jacent vers l’élément considéré comme cible potentielle.
• Drop: se produit lorsque le système d’entrée signale un événement de
lâchement sous-jacent sur l’élément considéré comme la cible.
• GotFocus: se produit lorsqu’un UIElement reçoit le focus.
• Hold: se produit lorsque le système d’entrée signale le maintien du doigt sur
l’élément considéré comme cible.
• KeyDown: se produit lorsqu’une touche du clavier est enfoncée alors que le
UIElement a le focus.
• KeyUp: se produit lorsqu’une touche du clavier est relâchée alors que le
UIElement a le focus.
• LostFocus: se produit lorsqu’un UIElement perd le focus.
• LostMouseCapture: se produit lorsque UIElement perd la capture de la souris.
• ManipulationCompleted: se produit lorsqu’une manipulation et l’inertie sur
UIElement sont terminées.
• ManipulationDelta: se produit lorsque le périphérique d’entrée change de
position pendant une manipulation.
• ManipulationStarted: se produit lorsqu’un périphérique d’entrée commence
une manipulation sur UIElement.
• MediaCommand: se produit quand un bouton est pressé sur une télécommande.
90 Développez des applications Internet avec Silverlight 5
• MouseEnter: se produit lorsque la souris (ou un stylet) entre dans la zone
englobante d’un UIElement.
• MouseLeave: se produit lorsque la souris (ou un stylet) quitte dans la zone
englobante d’un UIElement.
• MouseLeftButtonDown: se produit lorsque vous appuyez sur le bouton gauche
de la souris (ou lorsque la pointe du stylet touche la tablette) tandis que le
pointeur de la souris se trouve au-dessus d’un UIElement.
• MouseLeftButtonUp: se produit lorsque vous relâchez le bouton gauche de la
souris (ou lorsque vous retirez la pointe du stylet de la tablette) tandis que le
pointeur de la souris se trouve au-dessus d’un UIElement (ou qu’un UIElement
détient la capture de la souris).
• MouseMove: se produit lorsque la position des coordonnées de la souris (ou
du stylet) change tandis que le pointeur se trouve sur un UIElement (ou tandis
qu’un UIElement détient la capture de la souris).
• MouseRightButtonDown: se produit lorsque le bouton droit de la souris est
enfoncé alors que le pointeur de la souris se trouve au-dessus d’un UIElement.
• MouseRightButtonUp: se produit lorsque le bouton droit de la souris est
relâché alors que le pointeur de la souris se trouve au-dessus d’un UIElement.
Cependant, cet événement ne sera déclenché que si un appelant marque
l’événement MouseRightButtonDown précédent comme étant géré.
• MouseWheel: se produit lorsque l’utilisateur fait tourner la roulette de la
souris pendant que le pointeur se trouve au-dessus d’un UIElement, ou que
UIElement a le focus.
• Tap: se produit quand un simple tapement du doigt est signalé sur l’élément
considéré.
• TextInput: se produit lorsqu’un élément de l’interface utilisateur obtient du
texte indépendamment du périphérique.
• TextInputStart: se produit lorsqu’un élément de l’interface utilisateur obtient
initialement du texte indépendamment du périphérique.
• TextInputUpdate: se produit lorsque du texte continue d’être composé via un
éditeur de méthode d’entrée
Copyright 2012 Patrice REY

La classe FrameworkElement apporte un ensemble d’événements qui sont:


• BindingValidationError: se produit lorsqu’une erreur de validation des données
est signalée par une source de liaison.
• DataContextChanged: se produit quand le contexte de données de cet élément
change.
• LayoutUpdated: se produit lorsque la disposition de l’arborescence d’éléments
CHAPITRE 4 □ Propriétés de dépendances et événements routés 91
visuels Silverlight change.
• Loaded: se produit lorsqu’un FrameworkElement a été construit et ajouté à
l’arborescence d’objets.
• SizeChanged: se produit lorsque les propriétés ActualHeight ou ActualWidth
voient leur valeur se modifier sur un FrameworkElement.
• Unloaded: se produit lorsque cet objet n’est plus connecté à l’arborescence
d’objets principale.

2.2 - Le mode de routage de type bubbling

Le projet RoutageBubbling.sln, dans le dossier chapitre04, illustre le mode de


routage de type bubbling. Un bouton x_btn_contenu contient dans sa propriété
implicite Content, un StackPanel x_stackpanel. On ajoute dans le StackPanel un
contrôle Image x_image et un contrôle TextBlock x_text.
Les contrôles x_btn_contenu, x_stackpanel, x_image, x_text et le Grid x_
grid_root possèdent chacun un gestionnaire d’événements pour l’événement
MouseLeftButtonDown.
<Button Margin= «5» Grid.Row= «0»
MouseLeftButtonDown= «x_btn_contenu_MouseLeftButtonDown»
Name= «x_btn_contenu»>
<Button.Content>
<StackPanel MouseLeftButtonDown= «x_stackpanel_MouseLeftButtonDown»
Background= «Gainsboro» Width= «200» Name= «x_stackpanel»>
<Image Source= «/RoutageBubbling;component/images/chapitre.png»
Stretch= «None»
MouseLeftButtonDown= «x_image_MouseLeftButtonDown» Name= «x_image» />
<TextBlock Margin= «3» HorizontalAlignment= «Center»
MouseLeftButtonDown= «x_text_MouseLeftButtonDown»
Text= «la grammaire française» FontFamily= «Verdana» FontSize= «16»
Name= «x_text»></TextBlock>
</StackPanel>
<Button.Content>
</Button>
Une méthode VisualiserPriseEnChargeEvenement permet de relever les
caractéristiques du contrôle qui propage l’événement clic gauche enfoncé avec la
souris. Ces caractéristiques sont ajoutées dans un ListBox x_listbox pour avoir le
détail. Un bouton x_btn_raz permet de vider cette liste.
private void VisualiserPriseEnChargeEvenement(string nom_evenement,
object sender, MouseButtonEventArgs e) {
eventCounter++;
string message = «#» + eventCounter.ToString() + «:\r\n» +
92 Développez des applications Internet avec Silverlight 5
« Sender: « + sender.ToString() + «\r\n» +
« Handled: « + e.Handled + «\r\n»;
x_listbox.Items.Add(message);
}
Comme l’illustre la figure 4.13, un clic gauche sur le bouton ne provoque rien
(repère n°1). Par contre, un clic gauche sur l’image (repère n°2) et sur le texte
(repère n°3) provoque un routage de type bubbling.
FIGURE 4.13

2 3

Le fonctionnement de routage de type bubbling n’est pas systématique. Certains


événements de haut niveau, tels Button.Click, renvoient systématiquement
l’élément principal. Certains événements disposent d’un mécanisme permettant
d’interrompre le routage. La classe héritée RoutedEventArgs qui transporte
leurs arguments dispose dans ce cas d’une propriété Handled. Si la valeur true
Copyright 2012 Patrice REY

est assignée à la propriété Handled, le routage cesse et l’évènement n’est plus


propagé aux autres éléments de l’arbre.

2.3 - Les événements liés au clavier

Lorsqu’une touche du clavier est enfoncée, le contrôle qui a le focus émet


l’événement routé KeyDown. Un élément individuel obtient le focus lorsque
CHAPITRE 4 □ Propriétés de dépendances et événements routés 93
l’utilisateur clique directement sur cet élément dans la disposition, ou qu’il utilise
la touche TABULATION pour effectuer un pas à pas détaillé dans un ordre d’accès
par tabulation Silverlight dans la zone de contenu Silverlight. Cet événement est
réémis régulièrement tant que la touche reste enfoncée. Lors que la touche est
relâchée, il émet l’événement routé KeyUp.
Ces événements propagent un argument de type KeyEventArgs qui permet
d’obtenir les informations relatives à la touche enfoncée. La propriété Key indique
la touche ayant déclenché l’événement. Il s’agit d’une valeur énumérée. Quand
elle prend la valeur Unknown, la touche est spécifique au système d’exploitation
(Mac OS X et Windows), et peut être déterminée au moyen d’une valeur entière
renvoyée par la propriété PlatformKeyCode. Par exemple, la touche «:» d’un clavier
sur Windows retourne la valeur 191.
La classe statique Keyboard expose une propriété énumérée binaire Modifiers qui
permet de tester conjointement l’état des touches Alt, Ctrl, Shift, Windows, et sous
Mac OS X l’état de la touche Commande.
Le projet EvenementClavier.sln, dans le dossier chapitre04, illustre le relevé des
événements KeyDown et KeyUp. On positionne un TextBox x_text dans lequel
on écrit avec les touches du clavier. Un ListBox x_listbox ajoutera en haut de
liste l’événement KeyDown relevé pour toutes les touches (sauf «Entrée»), et
l’événement KeyUp relevé uniquement pour la touche «Entrée».
<Grid x:Name= «x_grid_root» Background= «WhiteSmoke»>
<Grid.RowDefinitions>
<RowDefinition Height= «25» />
<RowDefinition />
<RowDefinition Height= «30» />
</Grid.RowDefinitions>
<TextBox HorizontalAlignment= «Stretch» Name= «x_text»
VerticalAlignment= «Stretch» Text= «touches»
FontFamily= «Verdana» FontSize= «14» KeyDown= «x_text_KeyDown»
KeyUp= «x_text_KeyUp»/>
<ListBox Grid.Row= «1» Name= «x_listbox» />
<Button Content= «vider la liste» Grid.Row= «2» Name= «x_btn_raz»
Click= «x_btn_raz_Click»/>
</Grid>
La propriété Keyboard.Modifiers obtient l’ensemble de ModifierKeys qui est
actuellement enfoncé. ModifierKeys est une énumération d’indicateurs. Si plusieurs
touches de modification sont enfoncées, les comparaisons de bits par rapport à la
valeur retournent true et vous pouvez également directement vérifier les égalités
de combinaisons de touches dans des opérations en ajoutant les valeurs (pour
vérifier si les touches CTRL+ALT sont enfoncées). Par exemple, l’appui sur la touche
94 Développez des applications Internet avec Silverlight 5
«Ctrl» d’un clavier Windows se fera par le test Keyboard.Modifiers & ModifierKeys.
Control > 0. L’énumération Key spécifie les valeurs de clés possibles sur un clavier. La
valeur Key.Unknown est une valeur spéciale indiquant que la touche est en dehors
des limites de l’énumération Key. La propriété PlatformKeyCode obtient une valeur
entière qui représente la touche qui est enfoncée ou relâchée (selon l’événement
déclenché). Cette valeur est le code de touche non portable, qui est spécifique au
système d’exploitation. La touche «:» sur Windows donne la valeur 191.
private void x_text_KeyDown(object sender, KeyEventArgs e) {
string s_touche_enfonce = «»;
if ((Keyboard.Modifiers & ModifierKeys.Control) > 0) {
s_touche_enfonce += «Ctrl-»;
}
if ((Keyboard.Modifiers & ModifierKeys.Alt) > 0) {
s_touche_enfonce += «Alt-»;
}
if ((Keyboard.Modifiers & ModifierKeys.Shift) > 0) {
s_touche_enfonce += «Maj-»;
}
if ((Keyboard.Modifiers & ModifierKeys.Windows) > 0) {
s_touche_enfonce += «Windows-»;
}
if ((Keyboard.Modifiers & ModifierKeys.Apple) > 0) {
s_touche_enfonce += «Cmd-»;
}
if ((e.Key != Key.CapsLock)
&& (e.Key != Key.Enter)) {
String keyString;
switch (e.Key) {
case Key.Space:
keyString = « «;
break;
case Key.Unknown:
keyString = e.PlatformKeyCode.ToString();
break;
default:
keyString = e.Key.ToString();
break;
}
Copyright 2012 Patrice REY

s_touche_enfonce += keyString;
}
x_listbox.Items.Insert(0, s_touche_enfonce);
}
A noter que la touche «Entrée» est gérée sur l’événement KeyUp. Dans l’événement
KeyDown, on ajoutera une restriction liée à la touche Key.Enter.
CHAPITRE 4 □ Propriétés de dépendances et événements routés 95
private void x_text_KeyUp(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
x_listbox.Items.Insert(0, «Entree»);
}
}
La figure 4.14 illustre le résultat obtenu lors de l’appui sur des touches: la touche
«p», la combinaison de touches «Maj+T», la touche «:» et la touche «Entrée».
FIGURE 4.14

on clique dans le champ texte


pour lui donner le focus

la touche «Entrée»
enfoncée
la touche «Maj+T» la touche «:» enfoncée
enfoncée
la touche «p»
enfoncée

2.4 - Le survol et le positionnement de la souris

La classe UIElement expose des événements qui permettent de coder une réponse
au clic, au déplacement et à la rotation de la molette de la souris.
Lorsque la souris entre dans la surface d’un élément, celui-ci émet un événement
MouseEnter. Lors des déplacements pendant le survol, des événements MouseMove
sont émis. Lorsque la souris quitte la surface de l’élément, l’événement MouseLeave
est émis. Tous ces événements propagent un argument de type MouseEventArgs,
qui dispose notamment d’une méthode GetPosition permettant de retrouver les
coordonnées de la souris. Les coordonnées en pixels sont renvoyées dans un objet
Point.
Le projet EvenementSouris.sln, dans le dossier chapitre04, montre une utilisation
de la capture du positionnement de la souris sur un élément.
96 Développez des applications Internet avec Silverlight 5
<Canvas x:Name= «x_cnv_pos» Background= «WhiteSmoke» Width= «550»
Height= «160» Grid.Row= «0» MouseMove= «x_cnv_pos_MouseMove»>
<TextBlock Canvas.Left= «12» Canvas.Top= «12» Height= «23»
Name= «x_text_titre_pos» Text= «un rectangle de 200x100 pixels et un canvas
de 550x160 pixels:» Width= «526»
FontFamily= «Verdana» FontSize= «14» />
<Rectangle Canvas.Left= «12» Canvas.Top= «41» Height= «100»
Name= «x_rect_pos»
Stroke= «Black» StrokeThickness= «1»
Width= «200» Fill= «LightGray» MouseMove= «x_rect_pos_MouseMove»
Cursor= «Hand»/>
<TextBlock Canvas.Left= «227» Canvas.Top= «72» Height= «25»
Name= «x_text_pos_rect» Text= «position sur rectangle (000,000)»
Width= «311»
FontFamily= «Verdana» FontSize= «14» TextAlignment= «Left»
TextWrapping= «Wrap» />
<TextBlock Canvas.Left= «227» Canvas.Top= «103» FontFamily= «Verdana»
FontSize= «14» Height= «25» Name= «x_text_pos_cnv»
Text= «position sur canvas (000,000)» TextAlignment= «Left»
TextWrapping= «Wrap» Width= «311» />
</Canvas>
Un gestionnaire pour l’événement MouseMove est attaché au Canvas x_cnv_pos
et au rectangle x_rect_pos. Le TextBlock x_text_pos_rect affiche les coordonnées
de la souris lorsque celle-ci est positionnée sur l’élément Rectangle, et le TextBlock
x_text_pos_cnv affiche les coordonnées de la souris lorsque celle-ci est positionnée
sur l’élément Canvas. Les coordonnées données sont relatives au point d’origine
de l’élément concerné. La figure 4.15 illustre le résultat obtenu.
FIGURE 4.15

Copyright 2012 Patrice REY

private void x_rect_pos_MouseMove(object sender, MouseEventArgs e) {


Point pt_rect = e.GetPosition(sender as FrameworkElement);
string message = «position sur rectangle ( « + pt_rect.X.ToString() + « , «;
message += pt_rect.Y.ToString() + « )»;
x_text_pos_rect.Text = message;
CHAPITRE 4 □ Propriétés de dépendances et événements routés 97
}
private void x_cnv_pos_MouseMove(object sender, MouseEventArgs e) {
Point pt_canvas = e.GetPosition(sender as FrameworkElement);
string message = «position sur canvas ( « + pt_canvas.X.ToString() + « , «;
message += pt_canvas.Y.ToString() + « )»;
x_text_pos_cnv.Text = message;
}
La figure 4.16 illustre l’arbre d’héritage de la classe MouseEventArgs.
FIGURE 4.16

2.5 - La gestion du clic de la souris

Lorsqu’un bouton de la souris est enfoncé sur un élément, celui-ci émet l’événement
routé MouseLeftButtonDown (bouton gauche) ou MouseRightButtonDown (bouton
droit). Lorsque le bouton est relâché, il émet l’événement routé MouseLeftButtonUp
ou MouseRightButtonUp. Ces événements propagent un argument de type
MouseButtonEventArgs, hérité de MouseEventArgs (figure 4.17).
FIGURE 4.17
98 Développez des applications Internet avec Silverlight 5
Sur le Canvas x_cnv_clic est positionné un rectangle sur lequel on attache un
gestionnaire d’événement MouseLeftButtonDown. Dès que la souris survole le
rectangle, un simple clic ou un double clic sur le rectangle est relevé et est affiché
dans le x_text_clic.
<Canvas Background= «WhiteSmoke» Height= «160» Name= «x_cnv_clic» Width= «550»
Grid.Row= «1»>
<TextBlock Canvas.Left= «12» Canvas.Top= «12» FontFamily= «Verdana»
FontSize= «14» Height= «23» Name= «x_titre_clic»
Text= «un rectangle de 200x100 pixels:» Width= «526» />
<Rectangle Canvas.Left= «12» Canvas.Top= «41» Cursor= «Hand»
Fill= «LightGray» Height= «100»
MouseLeftButtonDown= «x_rect_clic_MouseLeftButtonDown» Name= «x_rect_clic»
Stroke= «Black» StrokeThickness= «1» Width= «200» />
<TextBlock Canvas.Left= «227» Canvas.Top= «72» FontFamily= «Verdana»
FontSize= «14» Height= «25» Name= «x_text_clic»
Text= «clic souris» TextAlignment= «Left» TextWrapping= «Wrap» Width= «311»
/>
</Canvas>
La propriété ClickCount de MouseButtonEventArgs informe s’il y a eu un simple clic
ou bien un double clic. La figure 4.18 illustre le simple et double clic. Généralement
le temps qui s’écoule entre deux clics, pour former un double clic, est inférieur ou
égal à 500 millisecondes.
private void x_rect_clic_MouseLeftButtonDown(object sender,
MouseButtonEventArgs e) {
if (e.ClickCount == 1) {
x_text_clic.Text = «simple clic sur le rectangle»;
} else if (e.ClickCount == 2) {
x_text_clic.Text = «double clic sur le rectangle»;
}
}

FIGURE 4.18

Copyright 2012 Patrice REY

Par défaut, le clic droit affiche le menu contextuel de Silverlight (figure 4.19,
partie gauche). Pour l’inhiber, il faut affecter un gestionnaire à l’événement
MouseRightButtonDown et assigner true à la propriété Handled (figure 4.19, partie
CHAPITRE 4 □ Propriétés de dépendances et événements routés 99
droite).
private void x_rect_clic_MouseRightButtonDown(object sender,
MouseButtonEventArgs e) {
e.Handled = true;
}
FIGURE 4.19

Handled = false Handled = true

2.6 - La gestion de la molette de la souris

Lorsque la molette de la souris est manipulée, l’élément qui se trouve sous le


curseur émet des événements MouseWheel. Ces événements propagent un
argument de type MouseWheelEventArgs, hérité de MouseEventArgs (figure
4.20). La classe MouseWheelEventArgs expose la propriété booléenne Handled
pour la gestion de la propagation de l’événement, et la propriété entière Delta
pour la représentation de l’incrément (valeur positive si la molette est tournée
vers l’avant et valeur négative dans le cas contraire).
FIGURE 4.20
100 Développez des applications Internet avec Silverlight 5
Dans l’exemple illustré par la figure 4.21, sur le Canvas x_cnv_wheel est positionné
un Viewbox x_viewbox_wheel, à l’intérieur duquel se trouve un contrôle Image
x_image dont la propriété Source référence l’image lino_ventura.jpg, située dans
le dossier images (repère n°1). On attache au contrôle Viewbox un gestionnaire
pour l’événement MouseWheel.
<Canvas Name= «x_cnv_wheel» Grid.Row= «2» Background= «WhiteSmoke»>
<Viewbox Name= «x_viewbox_wheel» Width= «220» Height= «250» Canvas.Left= «6»
Canvas.Top= «6» MouseWheel= «x_viewbox_wheel_MouseWheel»
Cursor= «SizeNESW»>
<Image Canvas.Left= «142» Canvas.Top= «0» Height= «250» Name= «x_image»
Stretch= «Fill» Width= «220»
Source= «/EvenementSouris;component/images/lino_ventura.jpg» />
</Viewbox>
</Canvas>

FIGURE 4.21

2
1

3
Copyright 2012 Patrice REY

Quand la souris est positionnée sur le contrôle Viewbox, le curseur change de


CHAPITRE 4 □ Propriétés de dépendances et événements routés 101

forme (propriété Cursor fixée à SizeNESW). Le déplacement de la molette permet


d’agrandir le Viewbox (repère n°3) ou de le réduire (repère n°2). Un facteur
d’échelle est calculé en fonction de la propriété Delta relevée de la souris. La
largeur et la hauteur du Viewbox est mise à jour en fonction du facteur d’échelle.
private void x_viewbox_wheel_MouseWheel(object sender,
MouseWheelEventArgs e) {
double facteur_echelle = (double)e.Delta / 110;
if (facteur_echelle > 0) {
// agrandit le viewbox.
x_viewbox_wheel.Width *= facteur_echelle;
x_viewbox_wheel.Height *= facteur_echelle;
} else {
// reduit le viewbox.
x_viewbox_wheel.Width /= -facteur_echelle;
x_viewbox_wheel.Height /= -facteur_echelle;
}
}

2.7 - La capture de la souris

La capture de la souris est quasiment toujours utilisée dans le développement


des logiciels car elle permet de déplacer un objet sélectionné au préalable, par le
déplacement de la souris.
La souris peut être capturée par un objet UIElement au moyen de sa méthode
CaptureMouse. Il prend alors totalement en charge la gestion de la souris, puis la
libère au moyen de la méthode ReleaseMouseCapture. La souris est alors remise
à disposition de l’interface utilisateur générale. Le curseur de la souris peut être
modifié au moyen de la propriété Cursor d’un FrameworkElement.
Le projet CaptureSouris.sln (figure 4.22), dans le dossier chapitre04, illustre le
principe de la capture de la souris pour déplacer un dé.
Nous avons un dé représenté par un Canvas x_cnv_de (en utilisant des rectangles
et des cercles pour le dessiner). On attache à ce Canvas les gestionnaires
d’événements MouseLeftButtonDown, MouseMove et MouseLeftButtonUp.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
<Canvas Name= «x_cnv_des» Width= «100» Height= «100»
MouseLeftButtonDown= «x_cnv_des_MouseLeftButtonDown»
MouseMove= «x_cnv_des_MouseMove»
MouseLeftButtonUp= «x_cnv_des_MouseLeftButtonUp»>
...
</Canvas>
</Canvas>
102 Développez des applications Internet avec Silverlight 5
FIGURE 4.22

clic gauche
1 état initial maintenu 2

déplacement avec clic


gauche maintenu 3

La variable booléenne en_deplacement, initialisée à false, indiquera que le


déplacement est en cours quand elle sera fixée à true. La variable qte_deplac_
souris, de type Point, stockera la position de la souris sur un élément.
Le gestionnaire MouseLeftButtonDown signale que le bouton gauche de la souris
est enfoncé sur le contrôle. On récupère la position de la souris sur ce dernier, on
change l’aspect du curseur, et on signale le début de la capture de la souris par la
méthode CaptureMouse.
private bool en_deplacement = false;
private Point qte_deplac_souris;
private void x_cnv_des_MouseLeftButtonDown(object sender,
Copyright 2012 Patrice REY

MouseButtonEventArgs e) {
//le deplacement commence
en_deplacement = true;
Canvas le_canvas = (Canvas)sender;
//on releve la position de la souris par rapport
//au canvas (coin haut gauche 0,0)
qte_deplac_souris = e.GetPosition(le_canvas);
//debut de la capture de la souris
le_canvas.CaptureMouse();
CHAPITRE 4 □ Propriétés de dépendances et événements routés 103

this.Cursor = Cursors.Hand;
}
Le gestionnaire MouseMove permet de récupérer la position de la souris sur le
Canvas x_cnv_root, et de modifier par conséquent les propriété TopProperty et
LeftProperty du dé en fonction de la quantité de déplacement de la souris.
private void x_cnv_des_MouseMove(object sender, MouseEventArgs e) {
if (en_deplacement == true) {
Canvas le_canvas = (Canvas)sender;
//on recupere la position du dé sur le canvas_root
Point point = e.GetPosition(x_cnv_root);
//on bouge le dé
le_canvas.SetValue(Canvas.TopProperty, point.Y - qte_deplac_souris.Y);
le_canvas.SetValue(Canvas.LeftProperty, point.X - qte_deplac_souris.X);
}
}
Le gestionnaire MouseLeftButtonUp signale que le déplacement est terminé. La
méthode ReleaseMouseCapture libère la capture de la souris. La forme du curseur
est remise à sa valeur par défaut par l’affectation de la valeur null à la propriété
Cursor.
private void x_cnv_des_MouseLeftButtonUp(object sender,
MouseButtonEventArgs e) {
if (en_deplacement == true) {
Canvas le_canvas = (Canvas)sender;
//on libere la capture de la souris
le_canvas.ReleaseMouseCapture();
//on signale le deplacement termine
en_deplacement = false;
this.Cursor = null;
}
}

2.8 - Le focus

A un instant donné, un seul élément de l’interface utilisateur peut capturer la saisie


au clavier. On dit alors que cet élément a le focus.
Quand le plug-in dispose du focus au sein de la page HTML, pour qu’un élément
Silverlight puisse prendre le focus, il doit hériter de Control, il doit être actif (sa
propriété IsEnabled fixée à true), et il doit être visible (sa propriété Visibility fixée
à Visible). Le focus peut être appliqué par le clic de souris, par le clavier ou par
programmation.
La classe Control expose deux propriétés qui contrôlent l’utilisation de la touche
«Tab» pour déplacer le focus parmi les contrôles d’un formulaire:
104 Développez des applications Internet avec Silverlight 5
• IsTabStop indique si le contrôle peut prendre le focus au moyen de cette touche.
• TabIndex indique le numéro d’ordre dans la séquence de déplacement du focus
parmi les contrôles.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
<TextBox TabIndex= «1» ></TextBox>
<TextBox TabIndex= «2» ></TextBox>
<TextBox IsTabStop= «false» ></TextBox>
<TextBox TabIndex= «3» ></TextBox>
</Canvas>
Quand un contrôle prend le focus, l’événement routé GotFocus est déclenché
de façon asynchrone. Quand il perd le focus, l’événement routé LostFocus est
déclenché. L’élément qui a le focus peut être obtenu par la méthode statique
GetFocusedElement de la classe FocusManager (figure 4.23). Il est possible de
donner le focus à un contrôle par programmation au moyen de sa méthode Focus.
Cette méthode renvoie une valeur booléenne indiquant si l’opération a réussi.
private void x_cnv_root (object sender, RoutedEventArgs e) {
//donner le focus au controle TextBox
x_texbox.Focus();
}

FIGURE 4.23

2.9 - Les commandes


Copyright 2012 Patrice REY

Une commande est un objet qui représente une action, telle que Sauvegarder,
Charger ou Imprimer, mais qui expose une interface standard nommée ICommand.
Le mécanisme des commandes renforce le découplage entre le code relatif au
visuel et la logique applicative.
Une commande est un objet qui implémente l’interface ICommand:
• la méthode Execute déclenche l’exécution de la commande.
• la méthode CanExecute indique si cette exécution est possible.
CHAPITRE 4 □ Propriétés de dépendances et événements routés 105

• l’événement CanExecuteChanged notifie les objets abonnés d’un changement


d’état d’activité de la commande; cet événement doit être émis par la
commande.
Le projet Commandes.sln, dans le dossier chapitre04, illustre le principe et
l’application des commandes. Comme le montre la figure 4.24, sur un Grid on
positionne un bouton x_btn_imprimer et un TextBox x_textbox (repère n°1). Dès
que l’on écrit dans le contrôle de texte, le bouton pour l’impression du contenu
du champ texte s’active (repère n°2). Si on clique sur le bouton, une fenêtre nous
indique ce que l’on envoie à l’impression (repère n°3). Si on efface ce qui se trouve
dans le champ texte (repère n°4), on revient à la position de départ (repère n°1).
FIGURE 4.24

1 2

4
3

Dans Silverlight, une commande peut être déclenchée par le code ou directement
par un élément auquel elle aura été associée au préalable.
Les éléments Button, CheckBox, RadioButton ou Hyperlink disposent d’une
propriété Command et d’une propriété CommandParameter, de type Object,
qui permet de passer des paramètres à la commande. Le déclenchement de la
commande est pris en charge par le code du contrôle (par exemple, la commande
spécifiée dans la propriété Command d’un Button est déclenchée par un clic).
Ici, notre bouton a sa propriété Command fixée à la ressource statique k_commande_
imprimer, ressource qui référence la classe CommandeImpressionTexte qui
implémente l’interface ICommand. La propriété CommandParameter du bouton
106 Développez des applications Internet avec Silverlight 5
référence le texte du contrôle x_textbox par le databinding.
<UserControl x:Class= «Commandes.MainPage»
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:d= «http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc= «http://schemas.openxmlformats.org/markup-compatibility/2006»
xmlns:local= «clr-namespace:Commandes»
mc:Ignorable= «d» d:DesignHeight= «300» d:DesignWidth= «400»>
<UserControl.Resources>
<local:CommandeImpressionTexte
x:Key= «k_commande_imprimer»></local:CommandeImpressionTexte>
</UserControl.Resources>
<Grid x:Name= «LayoutRoot» Margin= «5»>
<Grid.RowDefinitions>
<RowDefinition Height= «Auto»></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Margin= «5» Content= «commande impression»
Command= «{StaticResource k_commande_imprimer}»
CommandParameter= «{Binding ElementName=x_textbox,Path=Text}»
Cursor= «Hand» Name= «x_btn_imprimer»></Button>
<TextBox x:Name= «x_textbox» Grid.Row= «1» Margin= «5»></TextBox>
</Grid>
</UserControl>
La classe CommandeImpressionTexte implémente l’interface ICommand. Comme
le visualise la figure 4.25, l’intellisense de Visual Studio inscrit le code à implémenter
pour cette interface.
FIGURE 4.25

public class CommandeImpressionTexte : ICommand {


public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) {
Copyright 2012 Patrice REY

throw new NotImplementedException();


}
public void Execute(object parameter) {
throw new NotImplementedException();
}
}
Le diagramme de classe (figure 4.26) illustre l’implémentation de l’interface
ICommand à réaliser.
CHAPITRE 4 □ Propriétés de dépendances et événements routés 107
FIGURE 4.26

Le déclenchement de la commande se fait par l’appel de la méthode Execute. La


méthode booléenne CanExecute peut être appelée au préalable pour vérification.
Si la commande passe dans un état où elle ne peut pas être exécutée, elle doit
émettre l’événement CanExecuteChanged, afin notamment de prévenir les
contrôles visuels associés. Les contrôles appellent alors la méthode CanExecute
de la commande, et adaptent leur état (par l’activation ou la désactivation d’un
bouton par exemple).
public class CommandeImpressionTexte : ICommand {
public event EventHandler CanExecuteChanged;
private bool execution_possible;
//
public bool CanExecute(object parameter) {
//pour etre executable, le texte ne doit pas etre vide
bool executer_maintenant =
(parameter != null) && (parameter.ToString() != «»);
//determine si CanExecuteChanged doit etre envoyé
if (execution_possible != executer_maintenant) {
execution_possible = executer_maintenant;
if (CanExecuteChanged != null) {
CanExecuteChanged(this, new EventArgs());
}
}
return execution_possible;
}
//
public void Execute(object parameter) {
MessageBox.Show(«Imprimer: « + parameter);
}
}
Quand l’appel de la méthode Execute se produit, le contenu du champ TextBox est
108 Développez des applications Internet avec Silverlight 5
passé en paramètre pour être transféré dans une zone où il sera traité.

Copyright 2012 Patrice REY


CHAPITRE 5 DANS CE CHAPITRE
• TextBlock
• Image
Les contrôles • La classe
ContentControl

Silverlight • Button,
HyperlinkButton,
ToggleButton,
CheckBox,
RadioButton
• ToolTip
• Popup
• La classe ItemsControl
Les contrôles sont les principaux éléments
• ListBox, ComboBox,
TabControl
d’interaction avec l’utilisateur. Silverlight fournit • TextBox,
les contrôles classiques d’une application web PasswordBox,
AutoCompleteBox
(boutons, zones de saisie, etc.), mais offre de • Paragraph, Span,
puissantes possibilités de composition et de Run, LineBreak,
personnalisation. InlineUIContainer
• RichTextBox,
Nous allons parcourir en détail tous ces contrôles RichTextBlock
de façon à comprendre leur fonctionnement, leur • ProgressBar, Slider,
personnalisation et leurs interactions dans un ScrollBar
• Calendar, DatePicker
contexte donné.
Il y a des contrôles qui gèrent le texte de façon
statique ou de façon dynamique. Certains
permettent d’afficher un contenu enrichi avec des
dispositions sophistiquées et des positionnements
imbriqués.
Il est possible de pouvoir intégrer des images au
sein d’un texte enrichi par sa police d’écriture et
ses ornements. Certains contrôles nous facilitent
la vie pour choisir des dates d’une façon visuelle
et interactive, pour faire défiler un contenu avec
des dispositions particulières, pour effectuer des
réglages à différents niveaux, pour appliquer des
styles en vue d’une optimisation du contenu, etc.
110 Développez des applications Internet avec Silverlight 5
Les contrôles sont les principaux éléments d’interaction avec l’utilisateur. Silverlight
fournit les contrôles classiques d’une application web (boutons, zones de saisie,
etc.), mais offre de puissantes possibilités de composition et de personnalisation.
Nous avons déjà rencontré un certain nombre de contrôles Silverlight comme
les conteneurs (Canvas, Grid, StackPanel, etc.). Quelques contrôles sont plus
spécialisés pour effectuer des tâches particulières comme ceux utilisés pour le
dessin 2D, ceux pour l’agrandissement des images avec un zoom, ceux pour la
lecture du son et de la vidéo. Ces contrôles spécialisés seront vus plus loin dans
des chapitres précis.
Nous allons aborder dans ce chapitre tous les contrôles basiques et fondamentaux
avec par exemple les boutons, les boites pour les textes, les listes et les cases à
cocher, etc.

1 - Le texte statique TextBlock

La classe TextBlock fournit un contrôle léger pour afficher de petites quantités de


texte. Le contrôle TextBlock est l’élément principal pour afficher du texte dans les
applications Silverlight. Lorsque vous définissez du texte dans un TextBlock, il n’est
pas nécessaire en XAML de spécifier explicitement la propriété Text. Vous pouvez
mettre le texte dans le conteneur TextBlock comme son contenu.
<TextBlock Canvas.Left= «12» Canvas.Top= «12» Height= «23» Name= «x_textblock»
Width= «376» FontFamily= «Verdana» FontSize= «14»>le chat joue avec la souris
</TextBlock>
La figure 5.1 visualise les propriétés qui permettent de personnaliser le
contenu d’un TextBlock. A noter que la classe TextBlock hérite directement de
FrameworkElement.
L’alignement horizontal du texte est défini par la propriété TextAlignment qui
peut prendre les valeurs Left, Center, Right et Justify. Il est possible de définir
des marges autour du texte, à l’intérieur du TextBlock, au moyen de la propriété
Padding. Quand le texte à afficher est plus large que l’élément qui le présente, la
propriété TextWrapping permet une réorganisation sur plusieurs lignes (valeurs
Copyright 2012 Patrice REY

énumérées Wrap et NoWrap). La hauteur de ligne peut être définie par LineHeight.
La propriété TextDecorations permet de souligner le texte au moyen de la valeur
Underline.
Dans le projet Controles.sln, dans le dossier chapitre05, DemoTexteStatique.xaml
illustre un TextBlock entouré d’une bordure (figure 5.2).
CHAPITRE 5 □ Les contrôles Silverlight 111
FIGURE 5.1

FIGURE 5.2

<Border BorderThickness=»1» BorderBrush=»Black» Width=»135» Height=»61»


Canvas.Left=»12» Canvas.Top=»12»>
<TextBlock Canvas.Left=»12» Canvas.Top=»12» Height=»60» Name=»x_textblock»
Width=»134» FontFamily=»Verdana»
FontSize=»14» TextWrapping=»Wrap» HorizontalAlignment=»Left»
112 Développez des applications Internet avec Silverlight 5
VerticalAlignment=»Top»>le chat joue avec la souris
</TextBlock>
</Border>
L’élément TextBlock supporte deux modèles objets pour définir son contenu
textuel. Le premier modèle consiste à utiliser une chaîne de caractères spécifiée
dans la propriété Text. Dans le second modèle, le contenu textuel est défini sous la
forme d’objets Inline spécifiés dans la propriété Inlines (collection d’objet Inline)
du TextBlock. Les types d’objet Inline suivants peuvent être utilisés (figure 5.3):
• Run: bloc de texte élémentaire.
• LineBreak: défini un saut de ligne.
• Span et ses dérivés Bold, Italic et Underline: regroupement d’objets Inline.
FIGURE 5.3

Chaque objet Inline dispose des propriétés de police d’écriture: FontFamily,


FontSize, FontStyle, FontWeight, FontStretch, Foreground et TextDecorations. Il est
donc possible de constituer un texte formaté de façon plus évoluée que par la
simple utilisation de la propriété Text. La figure 5.4 illustre le code XAML suivant
avec un contenu de texte formaté.
Copyright 2012 Patrice REY

<TextBlock
FontFamily= «Arial» Width= «376» Text= «texte formaté avec des runs»
Height= «156»
Canvas.Left= «12» Canvas.Top= «79» FontSize= «14» TextWrapping= «Wrap»>
<LineBreak/>
<Run Foreground= «Navy» FontFamily= «Courier New» FontSize= «24»>
Courier New 24</Run>
<LineBreak/>
CHAPITRE 5 □ Les contrôles Silverlight 113

<Run Foreground= «Teal» FontFamily= «Times New Roman» FontSize= «18»


FontStyle= «Italic»>Times New Roman Italic 18</Run>
<LineBreak/>
<Run Foreground= «SteelBlue» FontFamily= «Verdana» FontSize= «14»
FontWeight= «Bold»>Verdana Bold 14</Run>
</TextBlock>
FIGURE 5.4

Les objets Silverlight de haut niveau qui manipulent du texte définissent la police
au moyen des propriétés suivantes:
• FontFamily est le nom de la police utilisée indépendamment de son poids,
de son style et de sa densité, afin de pouvoir gérer un nombre élevé de
combinaisons.
• FontSize définit la taille de la police, en pixels (valeur 11 par défaut), pour le
contenu de texte de l’élément.
• FontStyle définit le style dans lequel le texte est rendu (Normal ou Italic).
• FontWeight définit l’épaisseur des caractères et peut prendre une valeur
énumérée telle que Light, Normal, Bold, etc.
• FontStretch fournit un jeu d’étirements de police prédéfinis (une densité des
caractères) comme valeurs de propriété statique (Normal, Expanded, etc.).
• Foreground définit le pinceau utilisé pour dessiner la police, par l’affectation
d’un objet Brush.
Les polices dont le support est garanti par le runtime Silverlight (figure 5.5) sont
Portable User Interface, Arial, Arial Black, Comic Sans MS, Courier New, Georgia,
Lucida Sans Unicode, Times New Roman, Trebuchet MS, Verdana et Webdings. La
police par défaut est Portable User Interface (correspondant à Lucida).
FIGURE 5.5
114 Développez des applications Internet avec Silverlight 5
Des polices de substitution peuvent être définies en constituant une liste de noms
séparés par une virgule. La spécification suivante renvoie la police Comic Sans MS
quand Calibri n’est pas connue du runtime et n’est pas installée sur le poste de
l’utilisateur: FontFamily = «Calibri, Comic Sans MS»
Pour utiliser des polices potentiellement non installées sur le poste de travail, il est
nécessaire d’embarquer comme fichiers de ressource dans un assembly du projet
les fichiers .ttf (figure 5.6) et d’utiliser la syntaxe URI_fichier_police#nom_police
pour son utilisation.
<TextBlock Canvas.Left= «12» Canvas.Top= «425»
FontFamily= «fontes/Bayern.ttf#Bayern» FontSize= «24» Height= «23»
Name= «textBlock12»
Text= «police incorporée: Bayern.ttf» Width= «375» />
FIGURE 5.6

2 - Le contrôle Image

Silverlight intègre le support des images bitmap aux formats JPEG (Joint
Photographics Experts Group) et PNG (Portable Network Graphics). Généralement,
un contrôle Image affiche un fichier image spécifié au moyen d’un URI dans la
propriété Source. La propriété Source se voit affectée par un objet de type
Copyright 2012 Patrice REY

BitmapImage (fichier DemoImage.xaml du projet Controles.sln).


<Image Canvas.Left= «138» Canvas.Top= «12» Height= «339» Name= «x_img»
Stretch= «Fill» Width= «250»
Source= «/Controles;component/images/lino_ventura.jpg» />
Par programmation, la classe BitmapImage doit être utilisée explicitement pour
charger une image. Elle possède à cette fin d’une propriété UriSource.
CHAPITRE 5 □ Les contrôles Silverlight 115

BitmapImage bi = new BitmapImage();


bi.UriSource = new Uri(«/images/lino_ventura.jpg», UriKind.Relative);
x_img.Source = bi
Comme l’illustre la figure 5.7, on ajoute un dossier images dans lequel on insère
un élément existant lino_ventura.jpg (repère n°1). Dans la propriété Source de x_
img, on spécifie l’URI de l’image (repère n°2). Le contrôle Image affiche l’image
sélectionnée (repère n°3).
FIGURE 5.7

1
3

Le contrôle Image dispose d’une propriété Strech dont la valeur par défaut est
Uniform. Stretch permet de contrôler la façon dont l’image est étirée dans son
conteneur. Ainsi quelle que soit la taille de l’élément, l’image n’est pas déformée.
Les valeurs énumérées de la propriété Stretch sont:
• None: permet d’afficher l’image à sa taille réelle.
• Fill: permet d’étirer l’image pour l’ajuster aux dimensions de son conteneur.
• Uniform: le contenu est redimensionné pour s’ajuster aux dimensions de
116 Développez des applications Internet avec Silverlight 5
destination pendant qu’il conserve ses proportions natives.
• UniformToFill: le contenu est redimensionné pour remplir les dimensions de
destination pendant qu’il conserve ses proportions natives; si les proportions
du rectangle de destination diffèrent de la source, le contenu source est
découpé pour s’ajuster aux dimensions de destination.
La figure 5.8 illustre les différents cas de la propriété Stretch.
FIGURE 5.8

L’événement ImageFailed du contrôle Image se produit lorsqu’il y a une erreur sur


l’URI spécifié ou une erreur sur le format de l’image pris en charge. L’implémentation
de ce gestionnaire permet de faire afficher une autre image dans ce contrôle en
cas d’erreur.

3 - La classe ContentControl

Les contrôles sont les principaux éléments d’interaction avec l’utilisateur. La


classe Control (figure 5.9) hérite de FrameworkElement, et ses classes dérivées
ContentControl et ItemsControl représentent deux familles de contrôles dits de
contenu.
Copyright 2012 Patrice REY

La classe ContentControl est une classe fondamentale. Avec ses classes dérivées,
elles exposent une propriété de contenu Content, de type Object, qui peut contenir
un objet et un seul. Un ContentControl peut ainsi contenir visuellement n’importe
quel objet, FrameworkElement ou contrôle.
ScrollViewer, CheckBox, RadioButton, Button et TabItem sont les classes majeures
qui héritent de ContentControl.
CHAPITRE 5 □ Les contrôles Silverlight 117
FIGURE 5.9

Le fichier DemoContentControl.xaml (figure n°5.10) illustre le principe d’un contrôle


dit de contenu, avec un premier bouton (repère n°1) qui contient une image, et un
second bouton (repère n°2) qui contient un Grid dans lequel sont positionnés deux
formes Polygon et une forme Ellipse.
<Button Canvas.Left= «12» Canvas.Top= «12» Height= «125» Name= «x_btn»
Width= «134» >
<Image Name= «x_img» Source= «/Controles;component/images/livre.png»
Stretch= «None»></Image>
</Button>
<Button Margin= «3» Height= «70» Width= «215» Canvas.Left= «160»
Canvas.Top= «9»>
<Grid Margin= «5»>
<Polygon Points= «100,25 125,0 200,25 125,50»
Fill= «LightGray» />
<Polygon Points= «100,25 75,0 0,25 75,50»
Fill= «SlateGray» />
<Ellipse Width= «25» Height= «25» Stroke= «Black»
Fill= «GhostWhite»></Ellipse>
</Grid>
</Button>
118 Développez des applications Internet avec Silverlight 5
FIGURE 5.10

2
1

D’autres propriétés ont trait à la gestion du contenu du contrôle: Padding


qui indique les marges autour du contenu, HorizontalContentAlignment et
VerticalContentAlignment pour aligner le contenu horizontalement et verticalement
(les valeurs énumérées HorizontalContentAlignment étant Left, Center, Right et
Stretch, et les valeurs énumérées VerticalContentAlignment étant Top, Center,
Bottom et Stretch).

4 - Les boutons

Comme le visualise l’arbre d’héritage de la figure 5.11, la classe abstraite


ButtonBase représente la classe de base pour tous les contrôles bouton tels que
Button, ToggleButton, RadioButton, CheckBox, RepeatButton et HyperlinkButton.
Les propriétés de cette classe sont:
• ClickMode qui définit le moment auquel l’événement Click se produit; la valeur
énumérée Release spécifie que l’événement doit être déclenché lorsque le
bouton de la souris est enfoncé et relâché, avec le pointeur de la souris sur
le contrôle; la valeur Press spécifie que l’événement est déclenché lorsque le
bouton de la souris est enfoncé, avec le pointeur dessus le contrôle; la valeur
Hover spécifie que l’événement doit être déclenché lorsque la souris pointe sur
le contrôle.
• IsFocused qui obtient une valeur booléenne qui détermine si le bouton a le
focus.
• IsMouseOver qui obtient une valeur booléenne qui détermine si le pointeur de
la souris se trouve sur ce contrôle.
• IsPressed qui obtient une valeur booléenne qui détermine si un ButtonBase est
Copyright 2012 Patrice REY

actuellement à l’état enfoncé.


• Command qui définit la commande à appeler lorsque l’utilisateur clique sur le
bouton.
• CommandParameter qui définit le paramètre à passer à la propriété Command.
L’événement Click est l’événement qui se produit lorsque l’utilisateur clique sur le
bouton. La classe Button représente un contrôle bouton. Elle hérite de ButtonBase.
CHAPITRE 5 □ Les contrôles Silverlight 119

FIGURE 5.11

4.1 - Le contrôle HyperlinkButton

La classe HyperlinkButton, qui hérite de ButtonBase, représente un contrôle bouton


qui affiche un lien hypertexte. Le fait de cliquer sur HyperlinkButton permet aux
utilisateurs d’accéder à une page Web ou un contenu externe au sein de la même
application Silverlight. Vous définissez l’URI de HyperlinkButton avec la propriété
NavigateUri. Si l’URI a pour valeur une page Web externe, vous pouvez spécifier
le frame ou la fenêtre cible dans laquelle la page doit s’ouvrir avec la propriété
TargetName. Si l’URI a pour valeur un contenu au sein de la même application, vous
pouvez spécifier le nom de l’objet auquel accéder avec la propriété TargetName.
Vous n’avez pas à gérer l’événement Click pour le HyperlinkButton pour naviguer
automatiquement vers la valeur spécifiée pour NavigateUri.
Le fichier DemoBoutons.xaml illustre l’utilisation d’un HyperlinkButton (figure
5.12). Quand la souris se positionne sur le lien, celui-ci voit son texte souligné et
120 Développez des applications Internet avec Silverlight 5
le curseur prend la forme Cursors.Hand (repère n°2). Quand on clique dessus, la
propriété NavigateUri fournit l’adresse web d’un site et la propriété TargetName
indique l’ouverture d’une nouvelle fenêtre cible (repère n°3).
<HyperlinkButton Content= «un lien vers un site avec un HyperlinkButton»
FontFamily= «Verdana» FontSize= «14» Foreground= «Black»
NavigateUri= «http://www.silverlight.net»
TargetName= «_blank»></HyperlinkButton>
FIGURE 5.12

3
2

4.2 - Les contrôles CheckBox et RadioButton

La classe ToggleButton est la classe de base des contrôles qui peuvent changer
d’état comme CheckBox et RadioButton.
La propriété IsChecked spécifie l’état de ToggleButton. La propriété IsThreeState
indique si ToggleButton a deux ou trois états. Si le ToggleButton est configuré pour
avoir trois états, il permet à l’utilisateur de choisir un troisième état, à savoir l’état
indéterminé. Par exemple, vous pouvez utiliser un bouton bascule à trois états
pour indiquer Oui, Non ou Non applicable.
La classe CheckBox représente un contrôle qu’un utilisateur peut sélectionner
(cocher) ou désélectionner (décocher). Le contrôle CheckBox hérite de
ToggleButton et peut avoir trois états : activé, désactivé et indéterminé. Utilisez
le contrôle CheckBox pour fournir une liste d’options qu’un utilisateur peut
sélectionner, telles qu’une liste de paramètres à appliquer à une application.
Le fichier DemoBoutons.xaml (figure 5.13) illustre l’utilisation de cases à cocher à 2
et 3 états (repère n°1). La case à cocher 2 états (repère n°2) peut se trouver dans un
Copyright 2012 Patrice REY

état coché ou décoché. La case à cocher 3 états (repère n°3) peut se trouver dans
un état coché, décoché ou indéterminé. Le gestionnaire Checked traite l’état coché,
le gestionnaire Unchecked traite l’état décoché, et le gestionnaire Indeterminate
traite l’état indéterminé.
<CheckBox x:Name= «cb1» Content= «case à cocher 2 états»
Checked= «TraiterCocher» Unchecked= «TraiterDecocher»
Canvas.Top= «4» Canvas.Left= «6» />
CHAPITRE 5 □ Les contrôles Silverlight 121

<TextBlock x:Name= «text1» Canvas.Left= «160» Width= «237» Height= «22»


Canvas.Top= «6» Text= «infos» />
<CheckBox x:Name= «cb2» Content= «case à cocher 3 états»
IsThreeState= «True» Checked= «TraiterCocher»
Indeterminate= «TraiterIndetermine» Unchecked= «TraiterDecocher» Margin= «5»
Canvas.Left= «1» Canvas.Top= «23» />
<TextBlock x:Name= «text2» Canvas.Left= «160» Width= «234» Height= «25»
Canvas.Top= «26» Text= «infos» />

FIGURE 5.13

2 1 3

private void TraiterCocher(object sender, RoutedEventArgs e) {


CheckBox cb = sender as CheckBox;
if (cb.Name == «cb1»)
text1.Text = «case à cocher 2 états -> coché»;
else
text2.Text = «case à cocher 3 états -> coché.»;
}
private void TraiterDecocher(object sender, RoutedEventArgs e) {
CheckBox cb = sender as CheckBox;
if (cb.Name == «cb1»)
text1.Text = «case à cocher 2 états -> non coché»;
else
text2.Text = «case à cocher 3 états -> non coché»;
}
private void TraiterIndetermine(object sender, RoutedEventArgs e) {
CheckBox cb = sender as CheckBox;
text2.Text = «case à cocher 3 états -> indéterminé»;
}
La classe RadioButton représente un bouton qui permet à un utilisateur de
sélectionner une option unique parmi un groupe d’options. La case d’option
RadioButton est un contrôle généralement utilisé en tant qu’élément d’un groupe
de contrôles RadioButton.
122 Développez des applications Internet avec Silverlight 5
Vous pouvez créer un RadioButton unique. Vous pouvez regrouper des contrôles
RadioButton en les plaçant dans un parent ou en définissant la propriété
GroupName sur chaque RadioButton. RadioButton et CheckBox ont une fonction
identique : l’utilisateur peut les activer ou les désactiver. Lorsque plusieurs éléments
RadioButton sont regroupés, les cases d’options s’excluent mutuellement. Un
utilisateur ne peut pas sélectionner plus d’un élément à la fois dans un même
groupe de RadioButton.
Un RadioButton a deux états : sélectionné (coché) ou désélectionné (désactivé). La
sélection d’une case d’option RadioButton est déterminée par l’état de sa propriété
IsChecked. Lorsqu’un RadioButton est sélectionné, la propriété IsChecked est true.
Lorsqu’un RadioButton est désélectionné, la propriété IsChecked est false. Vous
pouvez désélectionner un RadioButton en cliquant sur un autre RadioButton du
groupe ; mais vous ne pouvez pas le désélectionner en cliquant de nouveau dessus.
Toutefois, il est possible de désélectionner un RadioButton par programmation en
affectant à sa propriété IsChecked la valeur false.
L’exemple suivant (figure 5.14) affiche deux panneaux qui contiennent trois cases
d’option. Une case d’option de chaque panneau est regroupée avec une autre.
Les deux cases d’option restantes dans chaque panneau ne sont pas regroupées
explicitement; en d’autres termes, elles sont regroupées car elles partagent le
même contrôle parent. Lorsque vous exécutez cet exemple et sélectionnez une
case d’option, un TextBlock affiche le nom du groupe, ou indique que la case
d’option a été «regroupée dans le panneau» sans nom de groupe explicite, ainsi
que le nom de la case d’option.
<TextBlock Text= «premier groupe:» Canvas.Left= «6» Canvas.Top= «0» />
<RadioButton x:Name= «radio_1» Checked= «VerifierRadioBouton»
GroupName= «groupe_1» Content= «choix n°1»
Canvas.Left= «15» Canvas.Top= «18» />
<RadioButton x:Name= «radio_2» Checked= «VerifierRadioBouton»
GroupName= «groupe_1» Content= «choix n°2»
Canvas.Left= «15» Canvas.Top= «37» />
<TextBlock Text= «non groupé:» Canvas.Left= «6» Canvas.Top= «67» />
<RadioButton x:Name= «radio_3» Checked= «VerifierRadioBouton»
Content= «choix n°3» Canvas.Left= «15» Canvas.Top= «88» />
Copyright 2012 Patrice REY

<TextBlock x:Name= «x_choix» Canvas.Left= «179» Width= «206» Height= «25»


Text= «choix» Canvas.Top= «38» />

private void VerifierRadioBouton(object sender, RoutedEventArgs e) {


RadioButton rb = sender as RadioButton;
x_choix.Text = «vous avez choisi: « + rb.GroupName + «: « + rb.Name;
}
CHAPITRE 5 □ Les contrôles Silverlight 123
FIGURE 5.14

5 - Le contrôle ToolTip

La classe ToolTip (figure 5.15) représente un contrôle qui crée une fenêtre
indépendante dans laquelle s’affichent les informations concernant un élément de
l’interface utilisateur.
Un ToolTip permet de fournir des informations à l’utilisateur. Par exemple, vous
pouvez utiliser un ToolTip pour indiquer le nom d’un Button. Le contenu d’un
contrôle ToolTip peut varier d’une chaîne de caractères simples à un contenu plus
complexe, tel qu’un StackPanel intégrant caractères et images. Comme le contenu
d’un ToolTip ne peut pas obtenir de focus, il est inutile de positionner un bouton
ou un lien dans le contenu de l’info-bulle. Les propriétés de la classe ToolTip
permettent de définir la position et le comportement de l’info-bulle.
La classe statique ToolTipService (figure 5.15) permet de configurer une info-bulle
pour un contrôle existant par la propriété attachée ToolTip.
Le fichier DemoTooltip.xaml (figure 5.16) illustre l’utilisation minimale pour la
réalisation d’une info-bulle pour un bouton.
<Button Canvas.Left= «35» Canvas.Top= «22» Content= «un bouton avec son
tooltip»
Height= «23» Name= «x_btn1» Width= «221»
FontFamily= «Verdana» FontSize= «12» Cursor= «Hand»
ToolTipService.ToolTip= «contenu du tooltip» />

FIGURE 5.16
124 Développez des applications Internet avec Silverlight 5
FIGURE 5.15

La classe ToolTipService expose les propriétés suivantes:


• Placement définit la position de l’info-bulle par rapport à son élément visuel
cible (valeur énumérée, de type PlacementMode, avec Bottom, Right, Left,
Top et Mouse); la valeur PlacementMode.Mouse indique un positionnement à
l’emplacement du pointeur de la souris.
• PlacementTarget définit l’élément visuel par rapport auquel l’info-bulle doit
être positionnée.
• ToolTip définit une info-bulle à joindre au contrôle.
Dans l’exemple de la figure 5.17, quand le pointeur survole les formes (cercle
et carré), une info-bulle se positionne à droite du TextBox correspondant pour
Copyright 2012 Patrice REY

indiquer la démarche à suivre.


<Ellipse Height= «30» Width= «30»
Fill= «Silver» HorizontalAlignment= «Left»
ToolTipService.Placement= «Right»
ToolTipService.PlacementTarget= «{Binding ElementName=x_textbox_cercle}»
ToolTipService.ToolTip= «donnez un nom à votre cercle» Canvas.Left= «10»
Canvas.Top= «60» />
CHAPITRE 5 □ Les contrôles Silverlight 125

<TextBox x:Name= «x_textbox_cercle» Text= «mon cercle» Width= «86»


Canvas.Left= «49» Canvas.Top= «66» />
<Rectangle Height= «30» Width= «30»
Fill= «SlateGray» HorizontalAlignment= «Left»
ToolTipService.Placement= «Right»
ToolTipService.PlacementTarget= «{Binding ElementName=x_textbox_carre}»
ToolTipService.ToolTip= «donnez un nom à votre carré» Canvas.Left= «10»
Canvas.Top= «96» />
<TextBox x:Name= «x_textbox_carre» Width= «86» Canvas.Left= «49»
Canvas.Top= «99» Text= «mon carré» />

FIGURE 5.17

Dans l’exemple de la figure 5.18, quand le pointeur survole le bouton, une info-bulle
se positionne et affiche son contenu, qui est composé d’un StackPanel dans lequel
sont positionnés du texte et une image. Pour pouvoir affecter un objet complexe
à la propriété attachée ToolTip, on utilise la notation pointée <ToolTipService.
ToolTip>.
<Button Canvas.Left= «12» Canvas.Top= «64» Content= «un bouton avec un tooltip
composé» Height= «23» Name= «button1» Width= «267» FontFamily= «Verdana»
FontSize= «14»>
<ToolTipService.ToolTip>
<StackPanel>
<TextBlock Margin= «3» Text= «pour créer un livre»></TextBlock>
<Image Source= «/Controles;component/images/livre.png»></Image>
<TextBlock Margin= «3» Text= «cliquez sur ce bouton»></TextBlock>
</StackPanel>
</ToolTipService.ToolTip>
</Button>

FIGURE 5.18
126 Développez des applications Internet avec Silverlight 5

6 - Le contrôle Popup

Le contrôle Popup sert à afficher un contenu d’une façon contextuelle (à la façon


d’un menu). Il existe deux différences fondamentales entre un contrôle Popup et
un contrôle ToolTip:
• le Popup ne s’affiche jamais automatiquement; il apparait uniquement en
fonction d’une action demandée.
• le Popup accepte le focus; donc il est possible de positionner dans son contenu
des contrôles, comme le bouton, qui acceptent le focus.
La classe Popup (figure 5.19), qui hérite de FrameworkElement, expose les
propriétés suivantes:
• Child qui définit le contenu à héberger dans le Popup; ce contenu doit hériter
de UIElement, ce qui indique qu’il peut contenir n’importe quel contrôle.
• IsOpen qui définit la valeur booléenne indiquant si le Popup est affiché à l’écran
à un moment donné.
• HorizontalOffset qui définit la distance entre le côté gauche du contrôle et le
côté gauche du Popup.
• VerticalOffset qui définit la distance entre le haut du contrôle et le haut du
Popup.
Les événements Opened et Closed se produisent respectivement quand le Popup
est ouvert et quand il est fermé. La méthode SetWindow permet de définir la
fenêtre parent du Popup.
Généralement vous utilisez la classe Popup pour visualiser temporairement
du contenu destiné à accomplir une tâche particulière. Le positionnement du
Popup se fait par l’intermédiaire des propriétés HorizontalOffset et VerticalOffset,
relativement par rapport au coin haut gauche du plug-in Silverlight.
Le fichier DemoPopup.xaml (figure5.20) illustre l’utilisation d’un contrôle Popup.
Lors d’un clic sur le bouton x_btn, le Popup x_popup s’affiche (sa propriété IsOpen
passe à true). Le Popup contient un StackPanel x_popup_stackpanel qui stocke
Copyright 2012 Patrice REY

deux Textblock, x_popup_choix1 et x_popup_choix2. On ajoute un gestionnaire


MouseLeftButtonDown sur le StackPanel et les 2 TextBlock. Un ListBox x_
listbox_choix emmagasine les noms des contrôles qui traitent l’événement
MouseLeftButtonDown.
<Button Canvas.Left= «12» Canvas.Top= «12» Content= «Dessiner une figure»
Height= «33» Name= «x_btn» Width= «162»
FontFamily= «Verdana» FontSize= «14» Click= «x_btn_Click» Cursor= «Hand» />
CHAPITRE 5 □ Les contrôles Silverlight 127
FIGURE 5.19

<Popup x:Name= «x_popup» HorizontalOffset= «0» Canvas.Left= «12»


Canvas.Top= «46» VerticalOffset= «0» IsOpen= «True»>
<StackPanel Width= «162» Height= «50» Background= «LightGray»
MouseLeftButtonDown= «x_popup_stackpanel_MouseLeftButtonDown»
Name= «x_popup_stackpanel»>
<TextBlock Text= «=> un carré» Name= «x_popup_choix1»
MouseLeftButtonDown= «x_popup_choix_MouseLeftButtonDown»
Width= «90» Height= «20» HorizontalAlignment= «Left»
VerticalAlignment= «Top» Margin= «2» Cursor= «Hand»></TextBlock>
<TextBlock Margin= «2» Text= «=> un cercle» Name= «x_popup_choix2»
MouseLeftButtonDown= «x_popup_choix_MouseLeftButtonDown» Width= «90»
Height= «20» HorizontalAlignment= «Left»
VerticalAlignment= «Top» Cursor= «Hand»></TextBlock>
</StackPanel>
</Popup>
<ListBox Canvas.Left= «200» Canvas.Top= «12» Height= «176»
Name= «x_listbox_choix» Width= «188» />
128 Développez des applications Internet avec Silverlight 5
FIGURE 5.20

Le gestionnaire x_btn_Click ouvre le Popup x_popup. Le gestionnaire x_popup_


stackpanel_MouseLeftButtonDown récupère le nom du StackPanel, l’ajoute au
ListBox, puis referme le Popup en assignant la valeur false à la propriété IsOpen du
Popup. Le gestionnaire x_popup_choix_MouseLeftButtonDown fait de même mais
avec les deux TextBlock.
private void x_btn_Click(object sender, RoutedEventArgs e) {
x_popup.IsOpen = true;
}
private void x_popup_stackpanel_MouseLeftButtonDown(object sender,
MouseButtonEventArgs e) {
StackPanel recup = sender as StackPanel;
x_listbox_choix.Items.Add(recup.Name);
x_popup.IsOpen = false;
}
private void x_popup_choix_MouseLeftButtonDown(object sender,
MouseButtonEventArgs e) {
e.Handled = true;
TextBlock recup = sender as TextBlock;
x_listbox_choix.Items.Add(recup.Name);
x_popup.IsOpen = false;
}
Copyright 2012 Patrice REY

7 - La classe ItemsControl

La classe ItemsControl est la deuxième grande famille à hériter de la classe Control.


Elle définit un contrôle qui peut contenir plusieurs objets, spécifiés au moyen de sa
propriété Items, de type ItemCollection.
Comme le montre l’arbre d’héritage de la classe ItemsControl (figure 5.21), les
CHAPITRE 5 □ Les contrôles Silverlight 129

classes ListBox, ComboBox, TreeView, TabControl sont des exemples de classes


dérivées.
FIGURE 5.21

7.1 - Le contrôle ListBox

Le contrôle ListBox contient une liste d’éléments sélectionnables. Sa propriété


Items, de type ItemCollection, peut contenir un ensemble d’objets. Le ListBox est un
contrôle qui affiche une collection d’éléments. Plusieurs éléments dans un ListBox
sont visibles à la fois. Vous spécifiez si le ListBox autorise plusieurs sélections à
l’aide de la propriété SelectionMode.
Le fichier DemoItemsControl.xaml, dans la solution Controles.sln du dossier
chapitre05, illustre un contrôle ListBox (figure 5.22) qui reçoit, comme contenu,
des objets TextBlock, Button, Image, et un objet Button qui contient lui-même un
objet Image. A noter que la propriété Items est implicite (donc peut être omise).
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «400»
Height= «600»>
<ListBox Width= «207» Margin= «0,5,0,10» Height= «268» Canvas.Left= «12»
Canvas.Top= «7»>
<ListBox.Items>
130 Développez des applications Internet avec Silverlight 5
<TextBlock Text= «un TextBlock» Width= «100» Height= «25»
FontFamily= «Verdana» FontSize= «14» />
<TextBox Text= «un TextBox» Width= «150» Height= «30» />
<Button Content= «un bouton» Width= «80» Height= «25» />
<Rectangle Fill= «LightGray» Height= «20» Width= «100» Margin= «2,2,2,2»/>
<Ellipse Fill= «DarkGray» Height= «20» Width= «150» Margin= «2,2,2,2»/>
<Image Source= «/Controles;component/images/lino_ventura.jpg»
Stretch= «Fill» Width= «50» Height= «50»></Image>
<Button Width= «75» Height= «75»>
<Image Source= «/Controles;component/images/lino_ventura.jpg»
Stretch= «Fill» Width= «50» Height= «50»></Image>
</Button>
</ListBox.Items>
</ListBox>
</Canvas>
FIGURE 5.22

un contrôle TextBlock
un contrôle TextBox
un contrôle Button
un contrôle Rectangle
un contrôle Ellipse

un contrôle Image
un contrôle Button avec
dedans un Image

La propriété ItemsSource permet de remplir un ListBox avec des valeurs d’une


collection de données. La figure 5.23 illustre une source de données composée
d’objet de la classe Client.
FIGURE 5.23

le champ
m_entete_listing de la
Copyright 2012 Patrice REY

classe Client

On crée une classe Client avec les champs m_nom pour un nom, m_prenom pour
un prénom, m_adresse pour une adresse et m_entete_listing pour réaliser un
entête du client.
CHAPITRE 5 □ Les contrôles Silverlight 131

public class Client {


public String m_nom { get; set; }
public String m_prenom { get; set; }
public String m_adresse { get; set; }
public String m_entete_listing { get; set; }
public Client(String un_nom, String un_prenom, String une_adresse) {
this.m_nom = un_nom;
this.m_prenom = un_prenom;
this.m_adresse = une_adresse;
this.m_entete_listing = this.m_nom + « - « + this.m_prenom + « - «
+ m_adresse;
}
}//end class: Client
Ensuite on crée une classe MesClients, qui hérite de ObservableObjectCollection,
et dans le constructeur on ajoute un ensemble de clients, de type Client. La classe
ObservableObjectCollection est une classe qui implémente une collection d’objets.
public class MesClients:ObservableObjectCollection {
public MesClients() {
this.Add(new Client(«laurent», «ruquier», «2 impasse des billes»));
this.Add(new Client(«albert», «thomas», «place d’albret»));
this.Add(new Client(«helene», «tuche», «route de braillat»));
this.Add(new Client(«elise», «laboret», «45 place de gaulle»));
this.Add(new Client(«laure», «guibert», «2 avenue rouge»));
this.Add(new Client(«lino», «asteres», «3 rue des roses»));
}
}//end class
Pour lier en XAML un ListBox avec la source de données fournie par la classe
MesClients, on ajoute un espace de noms qui référence cette classe par
xmlns:clients dans les attributs de l’UserControl.
<UserControl x:Class= «Controles.DemoItemsControl»
...
xmlns:clients= «clr-namespace:Controles»>
On crée une ressource statique intitulée k_clients. Pour lier les données au ListBox,
la propriété ItemsSource est affectée par la ressource statique k_clients, et la
propriété DisplayMemberPath est affectée par le nom ou le chemin d’accès de la
propriété affichée pour chaque élément de données. Ici on utilise le champ m_
entete_listing comme source de données.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «400»
Height= «600»>
<Canvas.Resources>
<clients:MesClients x:Key= «k_clients»/>
</Canvas.Resources>
<ListBox Canvas.Left= «12» Canvas.Top= «286» Height= «100» Width= «376»
132 Développez des applications Internet avec Silverlight 5
ItemsSource= «{StaticResource k_clients}»
DisplayMemberPath= «m_entete_listing»></ListBox>
</Canvas>

7.2 - Le contrôle ComboBox

La classe ComboBox représente un contrôle de sélection qui combine une zone


de texte non modifiable et une partie déroulante contenant une zone de liste qui
permet aux utilisateurs de sélectionner un élément dans une liste.
Le ComboBox, qui hérite de la classe abstraite Selector, qui hérite elle-même
de la classe ItemsControl, signifie qu’il a un conteneur d’éléments associé,
un objet ComboBoxItem, qui contient des morceaux individuels de contenu
dans le contrôle. Un ComboBoxItem est créé pour chaque élément dans une
collection et peut être récupéré, qu’il soit créé de manière explicite ou pas. Vous
pouvez extraire des objets de conteneur ComboBoxItem à l’aide de la propriété
ItemContainerGenerator associée au contrôle ComboBox. Vous remplissez le
ComboBox à l’aide des propriétés Items ou ItemsSource.
Vous pouvez récupérer l’élément sélectionné de la zone de liste déroulante à l’aide
de la propriété SelectedItem ou bien, vous pouvez récupérer l’index de l’élément
sélectionné à l’aide de la propriété SelectedIndex. La zone de texte du ComboBox
affiche la sélection actuelle ou est vide si aucun élément n’est sélectionné.
Le fichier DemoItemsControl.xaml, dans la solution Controles.sln du dossier
chapitre05, illustre un contrôle ComboBox (figure 5.24) qui héberge différents
types de contenu.
FIGURE 5.24

1 2
Copyright 2012 Patrice REY

4 3
CHAPITRE 5 □ Les contrôles Silverlight 133

Lors de l’initialisation du ComboBox, la sélection est vide puisque l’on a pas défini
la propriété SelectedIndex qui a pour valeur -1 (repère n°1). En cliquant sur le
ComboBox, la liste déroulante s’affiche avec son contenu (ici, repère n°2 avec une
chaîne de caractères et un StackPanel). Si on choisit la première sélection (repère
n°3) la propriété SelectedIndex a pour valeur 0 (index de base), et la deuxième
sélection a une valeur SelectedIndex égale à 1 (repère n°4).
<ComboBox x:Name= «x_combo_general» Width= «263» Margin= «10» Canvas.Left= «2»
Canvas.Top= «390» Height= «28»
SelectionChanged= «x_combo_general_SelectionChanged»>
<ComboBoxItem Content= «une chaine texte» />
<ComboBoxItem>
<ComboBoxItem.Content>
<StackPanel Orientation= «Horizontal»>
<Rectangle Height= «10» Width= «50» Fill= «DarkGray» />
<TextBlock Margin= «2» Text= «un conteneur» />
</StackPanel>
</ComboBoxItem.Content>
</ComboBoxItem>
</ComboBox>
<TextBlock Canvas.Left= «11» Canvas.Top= «490» Height= «70»
Name= «x_info_combo1» Text= «infos» Width= «376»
FontFamily= «Verdana» FontSize= «12» TextWrapping= «Wrap» />
Quand une sélection est déterminée, le gestionnaire d’événement
SelectionChanged se produit. On récupère le ComboBoxItem sélectionné par la
propriété SelectedItem. La propriété Content de ce ComboBoxItem nous retourne
son contenu, et on peut déterminer le type du contenu par la méthode GetType (qui
nous donne ici un contenu de type String et de type StackPanel respectivement).
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
x_info_combo1.Text = «SelectedIndex -> « +
x_combo_general.SelectedIndex.ToString();
}
private void x_combo_general_SelectionChanged(object sender,
SelectionChangedEventArgs e) {
x_info_combo1.Text = «SelectedIndex -> « +
x_combo_general.SelectedIndex.ToString() + RC;
if (x_combo_general.SelectedIndex != -1) {
ComboBoxItem combo_item = x_combo_general.SelectedItem as ComboBoxItem;
object obj = combo_item.Content;
x_info_combo1.Text += «type d’objet -> « + obj.GetType().ToString();
}
}
Comme pour le ListBox, on peut lier un ComboBox à une source de données (figure
5.25) par sa propriété ItemsSource (qui référence la source des données) et par sa
propriété DisplayMemberPath (qui indique le champ à récupérer). Ici on affiche le
134 Développez des applications Internet avec Silverlight 5
champ m_entete_listing de la liste MesClients.
<ComboBox Canvas.Left= «12» Canvas.Top= «582» Height= «28»
Name= «x_combo_client» Width= «375»
ItemsSource= «{StaticResource k_clients}»
DisplayMemberPath= «m_entete_listing»></ComboBox>
FIGURE 5.25

7.3 - Le contrôle TabControl

La classe TabControl (figure 5.26) représente un contrôle qui contient plusieurs


éléments partageant tous le même espace à l’écran. Le TabControl est utile pour
réduire l’espace utilisé à l’écran tout en permettant à une application d’exposer
d’importantes quantités de données. Un TabControl imbrique plusieurs objets
TabItem dans sa propriété Items, partageant tous le même espace à l’écran.
Pas plus d’un TabItem à la fois ne peut être visible dans un TabControl. Lorsque
l’utilisateur sélectionne un TabItem, le contenu de ce TabItem devient visible et le
contenu des autres objets TabItem est masqué. Vous pouvez spécifier l’onglet qui
s’affiche avec la propriété SelectedIndex ou SelectedItem.
FIGURE 5.26

Copyright 2012 Patrice REY


CHAPITRE 5 □ Les contrôles Silverlight 135

Le fichier DemoItemsControl.xaml, dans la solution Controles.sln du dossier


chapitre05, illustre un contrôle TabControl . Pour utiliser des onglets au moyen
du TabControl, nous devons ajouter une référence sur l’assembly d’extension
SDK car ce contrôle est un contrôle spécialisé fourni par le SDK Silverlight. On
déclare un espace de noms XML, intitulé toolkit, mappé sur l’assembly en ajoutant
l’attribut suivant à l’UserControl: xmlns:toolkit = «http://schemas.microsoft.com/
winfx/2006/xaml/presentation/sdk»>.
Comme la propriété Items de TabControl est une propriété implicite, elle pourra
être omise en XAML. La structure XAML d’un TabControl sera la suivante:
<toolkit:TabControl Canvas.Left= «6» Canvas.Top= «769» Height= «203»
Name= «tabControl1» Width= «388»>
<toolkit:TabControl.Items>
...
<toolkit:TabItem> ... </toolkit:TabItem>
<toolkit:TabItem> ... </toolkit:TabItem>
...
</toolkit:TabControl.Items>
</toolkit:TabControl>
La figure 5.27 illustre différents onglets avec quelques possibilités au niveau de
l’entête de l’onglet comme au niveau du contenu de l’onglet.
FIGURE 5.27

1 2

4 3

Le repère n°1 montre un onglet dont la propriété Header du TabItem est un texte,
et son contenu est composé d’un TextBlock.
136 Développez des applications Internet avec Silverlight 5
<toolkit:TabItem Header= «onglet n°1»>
<TextBlock Text= «un contenu texte»/>
</toolkit:TabItem>
Le repère n°2 montre un onglet dont la propriété Header du TabItem est un objet
TextBlock, et son contenu est composé d’un contrôle Image.
<toolkit:TabItem>
<toolkit:TabItem.Header>
<TextBlock Text= «onglet n°2» Margin= «0,3,0,0» />
</toolkit:TabItem.Header>
<Image Height= «53» Name= «image1» Stretch= «Fill» Width= «53»
Source= «/Controles;component/images/livre.png» />
</toolkit:TabItem>
Le repère n°3 montre un onglet dont la propriété Header du TabItem est un objet
Rectangle, et son contenu est composé d’un contrôle ScrollViewer qui fait défiler
un Canvas dont le fond est de couleur grise.
<toolkit:TabItem>
<toolkit:TabItem.Header>
<Rectangle Width= «20» Height= «20» Fill= «SlateGray» Margin= «0,3,0,0» />
</toolkit:TabItem.Header>
<ScrollViewer Height= «165» Name= «scrollViewer1» Width= «373»
HorizontalAlignment= «Left» VerticalAlignment= «Top»>
<Canvas Height= «220» Name= «canvas1» Width= «343»
HorizontalAlignment= «Left» VerticalAlignment= «Top»
Background= «LightGray» />
</ScrollViewer>
</toolkit:TabItem>
Le repère n°4 montre un onglet dont la propriété Header du TabItem est un objet
StackPanel composé d’un TextBlock et d’un contrôle Ellipse, avec un alignement
horizontal; le contenu du TabItem est composé d’un contrôle Ellipse avec un fond
gris et une bordure noire de 1 pixel.
<toolkit:TabItem>
<toolkit:TabItem.Header>
<StackPanel Orientation= «Horizontal» Margin= «0,4,0,0» Width= «69»>
<TextBlock Text= «couleur «/>
<Ellipse Width= «20» Height= «20» Fill= «DarkGray» Stroke= «Black» />
Copyright 2012 Patrice REY

</StackPanel>
</toolkit:TabItem.Header>
<Ellipse Width= «100» Height= «100» Fill= «DarkGray» Stroke= «Black» />
</toolkit:TabItem>
La propriété TabStripPlacement du TabControl permet de définir par une valeur
énumérée l’emplacement des onglets. La figure 5.28 montre un TabControl avec
des onglets placés côté gauche (TabStripPlacement.Left).
CHAPITRE 5 □ Les contrôles Silverlight 137
FIGURE 5.28

<toolkit:TabControl Canvas.Left= «6» Canvas.Top= «977» Height= «94» Width=


«388»
TabStripPlacement= «Left»>
<toolkit:TabItem Header= «onglet 1»></toolkit:TabItem>
<toolkit:TabItem Header= «onglet 2»></toolkit:TabItem>
<toolkit:TabItem Header= «onglet 3»></toolkit:TabItem>
</toolkit:TabControl>

8 - Les contrôles gérant le texte

Silverlight contient un contrôle standard qui est le TextBox, et plusieurs contrôles


spécialisés qui sont le contrôle PasswordBox, le contrôle AutoCompleteBox et le
contrôle RichTextBox. Tous ces contrôles (figure 5.29) héritent directement de la
classe Control. Nous allons étudier ces différentes variantes de contrôles.
FIGURE 5.29

8.1 - Le contrôle TextBox

Le contrôle TextBox définit une zone de saisie de texte dont les caractéristiques
sont les suivantes:
• la propriété Text, de type string, définit le contenu.
• la propriété booléenne AcceptsReturn (valeur false par défaut) apporte le
support de la touche [Entrée].
• les propriétés VerticalScrollBarVisibility et HorizontalScrollBarVisibility
définissent le contrôle de la visibilité des barres de défilement.
• la méthode SelectAll et l’événement SelectionChanged participent à la gestion
138 Développez des applications Internet avec Silverlight 5
de la sélection de texte.
• le support du presse-papiers est assuré au moyen des raccourcis [Ctrl][C] pour
copier, [Ctrl][X] pour couper et [Ctrl][V] pour coller.
• l’événement TextChanged émis lors d’une modification du texte.
De nombreuses autres propriétés permettent de personnaliser le contrôle TextBox
avec notamment:
• la propriété CaretBrush définit le pinceau utilisé pour restituer la barre verticale
qui indique le point d’insertion.
• la propriété FontSource définit la source de police appliquée au TextBox pour
restituer le contenu.
• la propriété SelectedText définit le contenu de la sélection actuelle dans la zone
de texte.
• la propriété SelectionStart définit la position de départ du texte sélectionné
dans la zone de texte.
• la propriété SelectionLength définit le nombre de caractères de la sélection
actuelle dans la zone de texte.
• la propriété TextAlignment définit la manière dont le texte doit être aligné dans
la zone le texte.
• la propriété TextWrapping définit comment le saut de ligne a lieu si une ligne
de texte s’étend au delà de la largeur disponible de la zone de texte.
• la propriété Watermark définit le contenu affiché sous la forme d’un filigrane
dans le TextBox lorsqu’il est vide.
• la propriété booléenne IsReadOnly définit la valeur qui détermine si l’utilisateur
peut modifier le texte dans la zone de texte.
Le fichier DemoText.xaml, dans la solution Controles.sln du dossier chapitre05,
illustre l’utilisation d’un contrôle TextBox. Une phrase est inscrite dans un contrôle
TextBox (repère n°1). Une sélection est faite en surlignant avec la souris (repère
n°2) et un double clic sur un mot est réalisé avec la souris (repère n°3). Dans ces
deux derniers cas, un TextBlock indique l’indice de départ (base zéro) de la zone
surlignée, la longueur de la zone surlignée (sous forme d’un nombre de caractères),
Copyright 2012 Patrice REY

et le contenu de la zone surlignée.


<StackPanel Canvas.Top= «8» Canvas.Left= «11» Width= «381» Height= «98»>
<TextBox Name= «x_textbox1»
SelectionChanged= «x_textbox1_SelectionChanged»
Text= «sélectionnez un mot pour tester l’événement
SelectionChanged»></TextBox>
<TextBlock Margin= «0,10,0,0» FontFamily= «Verdana» FontSize= «12»>
Sélection courante:</TextBlock>
CHAPITRE 5 □ Les contrôles Silverlight 139

<TextBlock Name= «x_textblock_select» TextWrapping= «Wrap» Height= «39»


Text= «» FontFamily= «Verdana» FontSize= «12»
Margin= «0,5,0,0»></TextBlock>
</StackPanel>
FIGURE 5.30

1 2

Quand l’événement SelectionChanged est émis, la propriété SelectionStart définit


la position de départ du texte sélectionné dans la zone de texte, SelectionLength
définit le nombre de caractères de la sélection actuelle dans la zone de texte,
et SelectedText définit le contenu de la sélection actuelle dans la zone de texte.
La méthode statique String.Format permet d’écrire le résultat sous une forme
formatée.
private void x_textbox1_SelectionChanged(object sender, RoutedEventArgs e) {
if (x_textbox1 == null) {
return;
}
x_textblock_select.Text = String.Format(
«la sélection part de l’indice {0} avec une longueur de {1} : \»{2}\»»,
x_textbox1.SelectionStart, x_textbox1.SelectionLength,
x_textbox1.SelectedText);
}

8.2 - Le contrôle PasswordBox

La classe PasswordBox représente un contrôle pour entrer des mots de passe.


Vous pouvez entrer une ligne unique de contenu sans habillage dans un contrôle
PasswordBox. L’utilisateur ne peut pas voir le texte entré, il n’y a que les caractères
de mot de passe qui représentent le texte qui sont affichés. Vous pouvez spécifier
ce caractère de mot de passe à l’aide de la propriété PasswordChar. La propriété
Password définit le mot de passe actuellement détenu par la zone PasswordBox.
La longueur maximale autorisé pour le mot de passe est gérée par la propriété
MaxLength. La propriété SelectionBackground définit le pinceau utilisé pour rendre
140 Développez des applications Internet avec Silverlight 5
l’arrière-plan du texte sélectionné, et la propriété SelectionForeground définit
le pinceau utilisé pour le texte sélectionné dans le PasswordBox. L’événement
PasswordChanged est émis lorsque la valeur de la propriété Password change.
Le fichier DemoText.xaml, dans la solution Controles.sln du dossier chapitre05,
illustre l’utilisation d’un contrôle PasswordBox (figure 5.31). L’utilisateur entre un
mot de passe dans le PasswordBox (avec les caractères remplacés par des puces,
valeur par défaut). Un champ TextBox indique le contenu du mot de passe tapé en
temps réel grâce à l’événement PasswordChanged émis.
FIGURE 5.31

<TextBlock Text= «taper ici un mot de passe:» Canvas.Left= «11»


Canvas.Top= «122» FontFamily= «Verdana» FontSize= «14»
Height= «35» Width= «175» TextWrapping= «Wrap» />
<PasswordBox x:Name= «x_password_box»
PasswordChanged= «x_password_box_PasswordChanged» MaxLength= «10»
Height= «35»
Width= «200» HorizontalAlignment= «Left» Canvas.Left= «188» Canvas.Top= «122»
/>
<TextBlock Text= «le mot de passe tapé est:» Canvas.Left= «12»
Canvas.Top= «165»
Height= «35» Width= «160» FontFamily= «Verdana»
FontSize= «14» TextWrapping= «Wrap» />
<TextBox x:Name= «x_password_texte» HorizontalAlignment= «Left»
IsReadOnly= «True» Height= «35» Width= «200»
Canvas.Left= «188» Canvas.Top= «165» />
private void x_password_box_PasswordChanged(object sender,
RoutedEventArgs e) {
x_password_texte.Text = x_password_box.Password;
}
A noter que le contrôle PasswordBox ne supporte pas l’utilisation du presse-
papiers. L’utilisateur ne peut pas employer les raccourcis clavier pour copier du
Copyright 2012 Patrice REY

texte et le coller dans le contrôle.

8.3 - Le contrôle AutoCompleteBox

La classe AutoCompleteBox représente un contrôle qui fournit une zone de


texte pour l’entrée de l’utilisateur et une partie déroulante qui contient les
CHAPITRE 5 □ Les contrôles Silverlight 141

correspondances possibles en fonction de l’entrée dans la zone de texte.


Le contrôle AutoCompleteBox permet aux utilisateurs de sélectionner un élément
dans la partie déroulante pour compléter leur entrée dans la zone de texte plutôt
que de taper toute l’entrée. La partie déroulante du contrôle AutoCompleteBox
sera remplie avec des correspondances possibles une fois que les conditions
établies par les propriétés MinimumPopulateDelay et MinimumPrefixLength
seront remplies.
Le contrôle AutoCompleteBox propose plusieurs méthodes pour déterminer les
éléments qui sont affichés dans la partie déroulante. Il peut être configuré pour
filtrer des éléments en utilisant des méthodes de filtrage de texte prédéterminées
ou pour utiliser vos propres méthodes de filtrage personnalisées. Pour modifier le
mode de filtrage des éléments, définissez la propriété FilterMode et éventuellement
les propriétés TextFilter ou ItemFilter.
Le mode de sélection de la partie déroulante est personnalisable et déterminé
par un adaptateur de sélection. Un adaptateur de sélection est un contrôle qui
implémente en général l’interface ISelectionAdapter. L’adaptateur de sélection
par défaut est un contrôle ListBox. Vous pouvez afficher des éléments dans
l’adaptateur de sélection par défaut avec un modèle de données en définissant
la propriété ItemTemplate. Vous pouvez également utiliser un adaptateur de
sélection personnalisé dans la partie déroulante du contrôle en créant un modèle
pour le contrôle qui contient l’adaptateur de sélection personnalisé.
D’autres propriétés permettent la personnalisation de ce contrôle avec notamment:
• IsDropDownOpen définit une valeur qui indique si la partie déroulante du
contrôle est ouverte.
• IsTextCompletionEnabled définit une valeur qui indique si la première
correspondance possible trouvée pendant le processus de filtrage sera affichée
automatiquement dans la zone de texte.
• MaxDropDownHeight définit la hauteur maximale de la partie déroulante du
contrôle AutoCompleteBox.
• ValueMemberBinding définit le Binding utilisé pour obtenir la valeur à afficher
dans la partie zone de texte du contrôle AutoCompleteBox, et filtrer les
éléments à afficher dans la partie déroulante.
• ValueMemberPath définit le chemin de propriété utilisé pour obtenir la valeur
à afficher dans la partie zone de texte du contrôle AutoCompleteBox, et filtrer
les éléments à afficher dans la partie déroulante.
L’événement Populated se produit lorsque le contrôle AutoCompleteBox a rempli
la partie déroulante avec des correspondances possibles selon la propriété Text,
142 Développez des applications Internet avec Silverlight 5
et l’événement Populating se produit lorsque le contrôle AutoCompleteBox
remplit la partie déroulante avec des correspondances possibles selon la propriété
Text. SelectionChanged se produit lorsque l’élément sélectionné dans la partie
déroulante du contrôle AutoCompleteBox a changé. TextChanged se produit
lorsque le texte dans la partie zone de texte du contrôle AutoCompleteBox est
modifié.
Le fichier DemoText.xaml, dans la solution Controles.sln du dossier chapitre05,
illustre l’utilisation d’un contrôle AutoCompleteBox (figure 5.31). On demande à
l’utilisateur d’inscrire le verbe qu’il recherche. Un filtrage est ajouté de façon à
proposer à l’utilisateur les verbes qui commencent par ce qu’il écrit (repère n°1 et
n°2), ou bien les verbes qui contiennent ce qu’il écrit (repère n°3).
FIGURE 5.32

<sdk:AutoCompleteBox Canvas.Left= «12» Canvas.Top= «254» Height= «28»


Name= «x_auto_verbe» Width= «376» />
<TextBlock Canvas.Left= «11» Canvas.Top= «231» FontFamily= «Verdana»
FontSize= «14» Height= «26» Text= «recherchez un verbe:» TextWrapping= «Wrap»
Width= «377» />
Pour réaliser cela, on déclare une classe Conjugaison avec un constructeur par
défaut surchargé, qui affecte un verbe, de type string, à la propriété Verbe.
Copyright 2012 Patrice REY

public class Conjugaison {


public string Verbe { get; set; }
public Conjugaison(string un_verbe) {
Verbe = un_verbe;
}
public override string ToString() {
return Verbe;
}
}
CHAPITRE 5 □ Les contrôles Silverlight 143

Quand l’UserControl est chargé (événement Loaded), on affecte à la propriété


ItemsSource du contrôle AutoCompleteBox, un tableau composé d’objets
Conjugaison.
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
Conjugaison[] des_verbes = new[]{
new Conjugaison(«abaisser»),
new Conjugaison(«abandonner»),
new Conjugaison(«abasourdir»),
new Conjugaison(«abâtardir»),
new Conjugaison(«abattre»),
new Conjugaison(«abdiquer»)
};
x_auto_verbe.ItemsSource = des_verbes;
x_auto_verbe.ItemFilter = FiltrerVerbe;
}
Pour filtrer les données à suggérer, on affecte à la propriété ItemFilter du contrôle,
une méthode booléenne FiltrerVerbe qui reçoit en paramètre une chaîne text et
un objet item. La variable un_verbe est affectée de item par un cast (pour passer
en objet Conjugaison). Si la propriété Verbe de un_verbe contient la chaîne text ou
si elle commence par la chaîne text, alors la méthode renvoie la valeur true.
public bool FiltrerVerbe (string text, object item) {
Conjugaison un_verbe = (Conjugaison)item;
return ((un_verbe.Verbe.StartsWith(text)) ||
(un_verbe.Verbe.Contains(text)));
}

8.4 - La classe TextElement

Il faut regarder maintenant la classe TextElement qui est la classe ancêtre


des classes de contenu de document (figure 5.33). TextElement, qui hérite de
DependencyObject, est une classe abstraite utilisée comme classe de base pour
les classes abstraites Block et Inline.
La classe Block est une classe abstraite qui fournit une base pour tous les éléments
de contenu au niveau du bloc. Les éléments qui héritent de la classe Block (comme
Paragraph et Section) peuvent être utilisés pour grouper des éléments sous un
parent commun ou appliquer des attributs communs à un groupe.
La classe abstraite Inline fournit une base pour le comportement des éléments de
contenu de flux, dérivés de Inline. Ces éléments dérivés sont:
• Run qui représente une section discrète de texte mis en forme ou non mis en
forme.
• LineBreak qui représente un élément à insérer qui provoque le retour à la ligne
144 Développez des applications Internet avec Silverlight 5
du contenu en cas de rendu dans un conteneur de texte.
• Span qui groupe d’autres éléments de contenu à insérer comme Bold, Italic,
Underline et Hyperlink.
• InlineUIContainer qui fournit un élément de contenu à insérer qui permet
d’incorporer des types UIElement dans le contenu.
FIGURE 5.33

La classe Paragraph regroupe des éléments de contenu pour leur appliquer


des caractéristiques communes, et génère un saut de ligne à la fin du contenu.
Une propriété appliquée à son niveau s’applique par défaut à tout son contenu.
Copyright 2012 Patrice REY

L’alignement du texte peut être défini par la propriété TextAlignment. L’utilisation


la plus simple de Paragraph est de créer un paragraphe de texte dans un contrôle
RichTextBox. En plus du texte brut, un élément Paragraph peut héberger d’autres
éléments Inline comme Run, Span, Bold, Hyperlink, Italic, Underline, LineBreak
et InlineUIContainer.
Les objets Run et LineBreak constituent les unités de contenu de base d’un bloc de
CHAPITRE 5 □ Les contrôles Silverlight 145

texte. Pour mettre en forme le texte dans un bloc de texte, vous pouvez appliquer
une mise en forme à divers objets Run. La propriété Inlines d’un objet TextBlock est
un objet InlineCollection qui contient le contenu d’un bloc de texte. Un objet Run
est aussi utilisé dans le modèle de contenu des objets Paragraph et RichTextBox.
Tout texte figurant dans le texte interne d’un élément objet TextBlock XAML est
implicitement converti en objet Run et stocké dans la collection de propriétés
TextBlock.Inlines. Vous pouvez nommer un objet Run (ou tout autre objet
TextElement) en utilisant la propriété Name ou x:Name en XAML. Nommer un
objet Run vous permet d’opérer des changements par programme. Par exemple,
vous pouvez modifier de façon dynamique le texte ou réordonner une collection
d’éléments de texte.
LineBreak est généralement utilisé en XAML sous forme de fermeture automatique
similaire <LineBreak />. L’objet LineBreak hérite de diverses propriétés de mise en
forme de texte de la classe Inline. La plupart de ces propriétés sont ignorées lors
du rendu du texte. La définition du FontSize d’un objet LineBreak n’a aucun effet
sur l’espace vertical entre les lignes de texte qui précèdent et qui suivent. Au lieu
de cela, le FontSize, défini sur les exécutions de texte qui précèdent et qui suivent,
détermine l’espacement vertical. Plusieurs sauts de ligne successifs utilisent la
hauteur par défaut de 11 pixels.
L’élément Span est utilisé pour grouper d’autres éléments de contenu Inline.
Aucun rendu inhérent n’est appliqué au contenu ou à d’autres éléments dans
un élément Span. Autrement dit, le contenu n’est pas mis en forme s’il est placé
à l’intérieur d’un élément Span sans attribut. Toutefois, les éléments hérités de
Span, notamment Hyperlink, Bold, Italic et Underline appliquent la mise en forme
au texte. Les éléments enfants contenus dans un élément Span doivent dériver de
Inline.
La classe Bold fournit un élément de contenu à insérer qui restitue le contenu en
gras (épaisseur de police). La classe Italic fournit un élément de contenu à insérer
qui restitue le contenu avec un style de police italique. La classe Underline fournit
un élément de contenu à insérer qui restitue le contenu avec une décoration de
texte souligné.
La classe InlineUIContainer fournit un élément de contenu à insérer qui permet
d’incorporer des types UIElement dans le contenu. Vous pouvez incorporer un
UIElement, tel qu’un Button, directement dans le contenu en le mettant dans un
InlineUIContainer. Un InlineUIContainer ne peut pas accueillir plusieurs enfants
UIElement. Toutefois, l’élément enfant hébergé par un InlineUIContainer peut lui-
même héberger des enfants.
146 Développez des applications Internet avec Silverlight 5

8.5 - Le contrôle RichTextBox

Le contrôle RichTextBox est à la fois un composant de saisie et un composant


d’affichage de texte riche. Il permet de formater et de présenter un texte de façon
plus évoluée qu’avec un TextBlock. Il supporte les liens hypertextes, les images et
les contrôles interactifs. Il définit un système de documents qui intègre les concepts
de texte riche, comme le HTML, mais avec les autres pans de la technologie
Silverlight.
Le fichier DemoText.xaml, dans la solution Controles.sln du dossier chapitre05,
illustre l’utilisation d’un contrôle RichTextBox (figure 5.34) pour afficher un contenu
issu du livre Le Comte De Monte-Cristo (Alexandre Dumas père).
FIGURE 5.34

un contrôle RichTextBox
en mode ReadOnly

On ajoute un contrôle RichTextBox x_richtextbox avec le défilement vertical


présent (propriété VerticalScrollBarVisibility fixée à Visible) et en mode lecture
(propriété IsReadOnly fixée à true).
<RichTextBox Canvas.Left= «11» Canvas.Top= «404» Name= «x_richtextbox»
Height= «315» Width= «377»
VerticalScrollBarVisibility= «Visible» IsReadOnly= «True»>
...
Copyright 2012 Patrice REY

</RichTextBox>
Ajouter une image consiste à ajouter un conteneur InlineUIContainer qui héberge
un contrôle Image (en paramétrant les propriétés Width, Height, Source et Fill). Ce
conteneur doit être placé dans un bloc comme un paragraphe.
<RichTextBox Canvas.Left= «11» Canvas.Top= «404» Name= «x_richtextbox»
Height= «315» Width= «377»
VerticalScrollBarVisibility= «Visible» IsReadOnly= «True»>
CHAPITRE 5 □ Les contrôles Silverlight 147

<Paragraph TextAlignment= «Center»>


<InlineUIContainer>
<Image Stretch= «Fill» Width= «100» Height= «140»
Source= «/Controles;component/images/monte%20cristo.jpg» ></Image>
</InlineUIContainer>
</Paragraph>
...
</RichTextBox>
Un nouveau paragraphe permet d’ajouter le titre écrit en noir, avec une police de
taille 22 , en gras et avec un alignement horizontal centré.
<Paragraph Foreground= «Black» FontFamily= «Trebuchet MS» FontSize= «22»
FontWeight= «Bold» TextAlignment= «Center»>
<Run>Le Comte de Monte-Cristo</Run>
</Paragraph>
Le paragraphe suivant est composé d’un saut de ligne, d’un texte écrit avec une
couleur Gray et une taille 14, et d’un texte écrit en Black (valeur par défaut) avec
une taille de 13. L’attribut xml:space = «preserve» indique que le texte écrit dans
la balise XAML préservera les espaces (l’espace avant le mot «d’Alexandre» est
gardé).
<Paragraph>
<LineBreak></LineBreak>
<Bold>
<Italic>
<Run FontSize= «14» Foreground= «Gray»>Le bal d’été</Run>
</Italic>
</Bold>
<Bold>
<Run xml:space= «preserve» FontSize= «13»> d’Alexandre Dumas père</Run>
</Bold>
</Paragraph>
Le troisième paragraphe est écrit avec la police Verdana de taille 12, et avec un
alignement horizontal justifié (TextAlignement fixée à Justify).
<Paragraph FontSize= «12» FontFamily= «Verdana» TextAlignment= «Justify»>Le
même jour, vers l’heure où Mme Danglars faisait la séance que nous avons dite
dans le cabinet de M. le procureur du roi, une calèche de voyage, entrant dans
la rue du Helder, franchissait la porte du n°27 et s’arrêtait dans la cour.</
Paragraph>
On positionne à la suite un lien hypertexte écrit avec la couleur DimGray (propriété
Foreground), qui change au survol de la souris par une couleur DarkGray (propriété
MouseOverForeground). La figure 5.35, repère n°1, illustre le résultat obtenu.
<Hyperlink Click= «x_lien_Click» Foreground= «DimGray»
MouseOverForeground= «DarkGray» FontFamily= «Comic Sans MS»
148 Développez des applications Internet avec Silverlight 5
FontSize= «14» x:Name= «x_lien»>
=> Générer un nouveau document par programmation</Hyperlink>

FIGURE 5.35

Un gestionnaire pour l’événement Click est attaché à ce lien (x_lien_Click). Il


permet de générer un texte par programmation, et de remplacer le contenu du
RichTextBox actuel par ce texte. La figure 5.35, repère n°2, illustre le résultat
obtenu.
private void x_lien_Click(object sender, RoutedEventArgs e) {
Run runFirst = new Run();
runFirst.Text = «voici un texte «;
Bold bold = new Bold();
Run runBold = new Run();
runBold.Text = «généré dynamiquement «;
bold.Inlines.Add(runBold);
Run runLast = new Run();
runLast.Text = «en réponse au clic sur le lien»;
Paragraph paragraph = new Paragraph();
paragraph.Inlines.Add(runFirst);
paragraph.Inlines.Add(bold);
paragraph.Inlines.Add(runLast);
x_richtextbox.Blocks.Clear();
x_richtextbox.Blocks.Add(paragraph);
}
Maintenant nous allons voir le contrôle RichTextBox en mode édition (IsReadOnly
fixée à false). La figure 5.36 illustre le positionnement des contrôles. Le
RichTextBox x_richtext_edition reçoit un contenu. Dès qu’une sélection texte est
faite, on peut passer cette sélection en gras avec le bouton x_btn_gras, en italique
Copyright 2012 Patrice REY

avec le bouton x_btn_italique, et en souligné avec le bouton x_btn_souligne. Le


bouton x_btn_xaml permet d’obtenir une représentation XAML du contenu du
RichTextBox par sa propriété Xaml. Le bouton x_btn_sauve permet de sauvegarder
cette représentation, le bouton x_btn_effacer permet d’effacer le contenu du
RichTextBox, et le bouton x_btn_ouvrir permet de charger cette représentation
pour la visualiser dans le RichTextBox.
CHAPITRE 5 □ Les contrôles Silverlight 149
FIGURE 5.36 les boutons pour mettre
en gras, en italique, et bouton pour
pour souligner visualiser le XAML
les boutons pour
sauvegarder,
ouvrir et effacer

RichTextBox
x_richtext_edition

TextBox
x_text_xaml

<Grid Width= «400» Canvas.Top= «739» Canvas.Left= «0»>


<Grid.RowDefinitions>
<RowDefinition Height= «30»></RowDefinition>
<RowDefinition Height= «150»></RowDefinition>
<RowDefinition Height= «150»></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation= «Horizontal» Grid.Row= «0»>
<Button Margin= «1» Click= «x_btn_effacer_Click»
Name= «x_btn_effacer»>Effacer</Button>
<Button Margin= «1» Click= «x_btn_ouvrir_Click»
Name= «x_btn_ouvrir»>Ouvrir</Button>
<Button Margin= «1,1,15,1» Click= «x_btn_sauve_Click»
Name= «x_btn_sauve»>Sauvegarder</Button>
<Button Margin= «1» FontWeight= «Bold» Width= «25»
Click= «x_btn_gras_Click» Name= «x_btn_gras»>B</Button>
<Button Margin= «1» FontStyle= «Italic» Width= «25»
Click= «x_btn_italique_Click» Name= «x_btn_italique»>I</Button>
<Button Margin= «1,1,15,1» Width= «25» Click= «x_btn_souligne_Click»
Name= «x_btn_souligne»>
<TextBlock TextDecorations= «Underline»>U</TextBlock>
</Button>
<Button Margin= «1» Click= «x_btn_xaml_Click» Name= «x_btn_xaml»>
Visualiser XAML</Button>
</StackPanel>
<RichTextBox Name= «x_richtext_edition» Grid.Row= «1»>
<Paragraph FontSize= «20» FontWeight= «Bold»>Un bal d’été</Paragraph>
<Paragraph FontSize= «12» FontFamily= «Verdana»
150 Développez des applications Internet avec Silverlight 5
TextAlignment= «Justify»>Le
même jour, vers l’heure où Mme Danglars faisait la séance que nous avons dite
dans le cabinet de M. le procureur du roi, une calèche de voyage, entrant dans
la rue du Helder, franchissait la porte du n°27 et s’arrêtait dans la cour.
</Paragraph>
</RichTextBox>
<TextBox Grid.Row= «2» TextWrapping= «Wrap»
VerticalScrollBarVisibility= «Visible» Name= «x_text_xaml»></TextBox>
</Grid>
La figure 5.37 illustre la façon de surligner un ensemble de mots, et de les mettre
en gras avec un soulignement.
FIGURE 5.37

effectuer un surlignage

effectuer une mise en gras

effectuer un soulignement

Le principe est le suivant pour mettre une sélection en gras. On obtient un objet
TextSelection contenant la sélection actuelle dans le contrôle RichTextBox par la
propriété Selection du RichTextBox. On instancie une variable currentState en lui
affectant la valeur d’une épaisseur normale (FontWeights.Normal). Si la propriété
FontWeightProperty de la sélection est définie, currentState récupère sa valeur.
En fonction de cela, on applique la valeur FontWeights.Bold avec la méthode
ApplyPropertyValue dans le cas où sa valeur était FontWeights.Normal. Enfin, on
donne au RichTextBox le focus pour éviter que l’utilisateur soit obligé de cliquer
de nouveau dans le contrôle.
private void x_btn_gras_Click(object sender, RoutedEventArgs e) {
Copyright 2012 Patrice REY

TextSelection selection = x_richtext_edition.Selection;


FontWeight currentState = FontWeights.Normal;
if (selection.GetPropertyValue(Run.FontWeightProperty) !=
DependencyProperty.UnsetValue)
currentState =
(FontWeight)selection.GetPropertyValue(Run.FontWeightProperty);
if (currentState == FontWeights.Normal) {
selection.ApplyPropertyValue(Run.FontWeightProperty, FontWeights.Bold);
} else {
CHAPITRE 5 □ Les contrôles Silverlight 151

selection.ApplyPropertyValue(Run.FontWeightProperty, FontWeights.Normal);
}
x_richtext_edition.Focus();
}
La démarche, qui consiste à mettre une sélection de texte en gras, est identique
pour mettre une sélection en italique ou pour la souligner. Il suffit juste de se
référer aux propriétés concernées.
private void x_btn_italique_Click(object sender, RoutedEventArgs e) {
TextSelection selection = x_richtext_edition.Selection;
FontStyle currentState = FontStyles.Normal;
if (selection.GetPropertyValue(Run.FontStyleProperty) !=
DependencyProperty.UnsetValue)
currentState = (FontStyle)selection.GetPropertyValue(
Run.FontStyleProperty);
if (currentState == FontStyles.Italic) {
selection.ApplyPropertyValue(Run.FontStyleProperty, FontStyles.Normal);
} else {
selection.ApplyPropertyValue(Run.FontStyleProperty, FontStyles.Italic);
}
x_richtext_edition.Focus();
}
private void x_btn_souligne_Click(object sender, RoutedEventArgs e) {
TextSelection selection = x_richtext_edition.Selection;
TextDecorationCollection currentState = null;
if (selection.GetPropertyValue(Run.TextDecorationsProperty) !=
DependencyProperty.UnsetValue)
currentState =
(TextDecorationCollection)selection.GetPropertyValue(
Run.TextDecorationsProperty);
if (currentState != TextDecorations.Underline) {
selection.ApplyPropertyValue(Run.TextDecorationsProperty,
TextDecorations.Underline);
} else {
selection.ApplyPropertyValue(Run.TextDecorationsProperty, null);
}
x_richtext_edition.Focus();
}
La propriété Xaml de RichTextBox permet d’obtenir une représentation XAML du
contenu du contrôle. Le bouton x_btn_xaml permet d’éditer ce contenu XAML
dans le TextBox x_text_xaml. La figure 5.38 illustre le résultat obtenu.
private void x_btn_xaml_Click(object sender, RoutedEventArgs e) {
x_text_xaml.Text = x_richtext_edition.Xaml;
}
152 Développez des applications Internet avec Silverlight 5
FIGURE 5.38

Les boutons x_btn_sauve et x_btn_ouvrir permettent, quant à eux, de sauvegarder


le fichier XAML généré et de pouvoir le charger de nouveau, avec les boites de
dialogue habituelles de sauvegarde et d’ouverture.
private void x_btn_sauve_Click(object sender, RoutedEventArgs e) {
SaveFileDialog saveFile = new SaveFileDialog();
saveFile.Filter = «XAML Files (*.xaml)|*.xaml»;
if (saveFile.ShowDialog() == true) {
using (FileStream fs = (FileStream)saveFile.OpenFile()) {
System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
byte[] buffer = enc.GetBytes(x_richtext_edition.Xaml);
fs.Write(buffer, 0, buffer.Length);
}
}
}
private void x_btn_ouvrir_Click(object sender, RoutedEventArgs e) {
OpenFileDialog openFile = new OpenFileDialog();
openFile.Filter = «XAML Files (*.xaml)|*.xaml»;
string content = «»;
if (openFile.ShowDialog() == true) {
using (StreamReader reader = openFile.File.OpenText()) {
content = reader.ReadToEnd();
}
Copyright 2012 Patrice REY

x_richtext_edition.Xaml = content;
x_text_xaml.Text = «»;
}
}
Le bouton x_btn_effacer permet juste de vider les contrôles possédant du contenu
avant d’effectuer une ouverture du fichier XAML sauvegardé.
private void x_btn_effacer_Click(object sender, RoutedEventArgs e) {
CHAPITRE 5 □ Les contrôles Silverlight 153

x_richtext_edition.Blocks.Clear();
x_text_xaml.Text = «»;
}

8.6 - Le contrôle RichTextBlock

Silverlight, dans sa version 5, apporte un nouveau contrôle qu’est le RichTextBlock.


Ce contrôle est une version spécialisée du RichTextBox en mode lecture
uniquement. La classe RichTextBlock hérite directement de FrameworkElement.
La figure 5.39 visualise l’arbre d’héritage. Un contrôle RichTextBlock est fait pour
travailler en partenariat avec le contrôle RichTextBlockOverflow, qui hérite lui
aussi directement de FrameworkElement, pour pouvoir réaliser des dispositions
de contenus sophistiquées.
FIGURE 5.39

Le projet DemoRichTextBlock.sln, dans le dossier chapitre05, illustre le principe de


ce nouveau contrôle (figure 5.40). Lors du redimensionnement de la fenêtre, on
voit bien que le texte qui s’étalait sur 2 colonnes, s’étale maintenant sur 3 colonnes
tout en gardant ses caractéristiques d’écriture (il y a un basculement du texte d’une
colonne à l’autre).
Pour réaliser cela sur 3 colonnes, il faut positionner dans la première colonne un
contrôle RichTextBlock intitulé x_richtextblock, et dans les 2 colonnes suivantes, un
contrôle RichTextBlockOverflow par colonne, intitulés x_overflow1 et x_overflow2.
Pour que le texte qui est en excès dans x_richtextblock puisse continuer de s’afficher
dans x_overflow1, il faut lier x_richtextblock par databinding à x_overflow1, par
l’intermédiaire de la propriété OverflowContentTarget: OverflowContentTarget =
«{Binding ElementName=x_overflow1}».
154 Développez des applications Internet avec Silverlight 5
On procède de même pour lier x_overflow1 à x_overflow2 par l’intermédiaire de
la propriété OverflowContentTarget de x_overflow1: OverflowContentTarget =
«{Binding ElementName=x_overflow2}».
FIGURE 5.40

après un redimensionnement de la fenêtre

<RichTextBlock x:Name= «x_richtextblock»


OverflowContentTarget= «{Binding ElementName=x_overflow1}»>
<Paragraph FontSize= «13» FontFamily= «Verdana» TextAlignment= «Left»>Le même
jour, vers l’heure où Mme Danglars faisait la séance que nous avons dite dans
le cabinet de M. le procureur du roi, une calèche de voyage, entrant dans la
rue du Helder, franchissait la porte du n°27 et s’arrêtait dans la cour.</
Paragraph>
<Paragraph FontSize= «13» FontFamily= «Verdana» TextAlignment= «Left»>Au bout
d’un instant la portière s’ouvrait, et Mme de Morcerf en descendait appuyée
au bras de son fils. À peine Albert eut-il reconduit sa mère chez elle que,
commandant un bain et ses chevaux, après s’être mis aux mains de son valet
de chambre, il se fit conduire aux Champs-Élysées, chez le comte de Monte-
Cristo.</Paragraph>
</RichTextBlock>
<RichTextBlockOverflow x:Name= «x_overflow1» Grid.Column= «1»
OverflowContentTarget= «{Binding ElementName=x_overflow2}»>
</RichTextBlockOverflow>
<RichTextBlockOverflow x:Name= «x_overflow2» Grid.Column= «2»>
</RichTextBlockOverflow>

9 - La classe RangeBase
Copyright 2012 Patrice REY

La classe abstraite RangeBase (figure 5.41) est la classe de base pour des contrôles
qui représentent des éléments avec une valeur dans une plage spécifique. Les
classes dérivées sont ProgressBar, ScrollBar et Slider.
Un contrôle RangeBase a une valeur Value qui peut être définie entre les propriétés
Minimum et Maximum. Il indique visuellement sa valeur Value. Il a un style par
défaut limité. Il utilise la contrainte de propriété pour s’assurer que les propriétés
CHAPITRE 5 □ Les contrôles Silverlight 155

Minimum, Maximum et Value maintiennent la relation Minimum <= Valeur <=


Maximum. La contrainte affecte la valeur de retour de chaque propriété, mais pas
la valeur de son champ associé. Si les conditions qui provoquent la contrainte sont
supprimées, la valeur de retour de chaque propriété reflète de nouveau la valeur
véritable de son champ associé.
FIGURE 5.41

Un objet RangeBase est caractérisé par:


• la propriété LargeChange qui définit une valeur à ajouter à ou à soustraire de
la valeur Value d’un contrôle RangeBase.
• la propriété Maximum qui définit la valeur Value la plus élevée possible de
l’élément de plage.
• la propriété Minimum qui définit la valeur Value minimale de l’élément de
plage.
• la propriété SmallChange qui définit une valeur Value à ajouter à ou à soustraire
de la valeur Value d’un contrôle RangeBase.
• la propriété Value qui définit le paramètre actuel du contrôle de plage qui peut
être contraint.
• l’événement ValueChanged qui se produit lorsque la valeur de plage est
modifiée.

9.1 - Le contrôle ProgressBar

La classe ProgressBar représente un contrôle qui indique la progression d’une


156 Développez des applications Internet avec Silverlight 5
opération. Un contrôle ProgressBar indique visuellement la progression d’une
opération longue avec l’un des deux styles suivants: une barre qui affiche un motif
répété, et une barre qui se remplit en fonction d’une valeur.
La propriété IsIndeterminate détermine l’apparence d’un ProgressBar. Affectez
la valeur true à IsIndeterminate pour afficher un motif répété. Affectez la valeur
false à IsIndeterminate pour remplir la barre en fonction d’une valeur. Lorsque
IsIndeterminate est false, vous spécifiez la plage avec les propriétés Minimum et
Maximum. Par défaut, Minimum est 0 et Maximum est 100. Pour spécifier la valeur
de progression, vous définissez la propriété Value.
Le projet DemoRangeBase.sln, dans le dossier chapitre05, illustre le contrôle
ProgressBar. La figure 5.42 visualise un ProgressBar avec ses deux styles.
FIGURE 5.42

<StackPanel x:Name= «LayoutRoot» Background= «White» Width= «400»>


<Border BorderThickness= «2» BorderBrush= «Black»>
<StackPanel Background= «LightGray»>
<TextBlock HorizontalAlignment= «Center» Margin= «10»
Text= «un ProgressBar avec une valeur déterminée»
FontFamily= «Verdana» FontSize= «14» />
<ProgressBar x:Name= «pg1» Value= «100» Margin= «10» Maximum= «200»
Height= «24» IsIndeterminate= «False» />
</StackPanel>
</Border>
<Border BorderThickness= «2» BorderBrush= «Black»>
<StackPanel Background= «LightGray»>
<TextBlock HorizontalAlignment= «Center»
Copyright 2012 Patrice REY

Margin= «10» Text= «un ProgressBar avec une valeur indéterminée»


FontFamily= «Verdana» FontSize= «14» />
<ProgressBar x:Name= «pg2» Margin= «10» Height= «28»
IsIndeterminate= «True» />
</StackPanel>
</Border>
</StackPanel>
CHAPITRE 5 □ Les contrôles Silverlight 157

9.2 - Le contrôle Slider

La classe Slider représente un contrôle qui permet à l’utilisateur d’effectuer une


sélection dans une plage de valeurs en déplaçant un contrôle Thumb le long d’un
contrôle Track. Le Slider représente donc une glissière.
Un contrôle Slider permet aux utilisateurs de sélectionner une valeur dans une
plage de valeurs. Vous pouvez personnaliser un contrôle Slider en définissant ses
propriétés. La liste suivante décrit quelques-uns des attributs d’un Slider que vous
pouvez personnaliser :
• l’orientation du Slider, horizontale ou verticale, avec la propriété Orientation.
• le sens dans lequel la valeur est augmentée le long du Slider avec la propriété
IsDirectionReversed.
Le projet DemoRangeBase.sln, dans le dossier chapitre05, illustre le contrôle Slider.
La figure 5.43 visualise un Slider avec ses deux sens. La valeur courante du premier
Slider est récupérée dans le gestionnaire ValueChanged pour être affichée dans un
TextBlock. Pour le deuxième Slider, un TextBlock affiche la valeur courante de la
glissière, avec sens verticale, dans sa propriété Text liée par databinding à la valeur
Value de x_slider2.
FIGURE 5.43

Slider avec sens


horizontal
1

Slider avec sens


vertical
2

<TextBlock Text= «Slider avec l’événement ValueChanged:» Canvas.Left= «12»


Canvas.Top= «193» Width= «376»
FontFamily= «Verdana» FontSize= «14» />
<Slider x:Name= «x_slider1»
Minimum= «0» Maximum= «10»
ValueChanged= «x_slider1_ValueChanged»
Canvas.Left= «12» Canvas.Top= «216» Width= «376» Height= «31» />
<TextBlock x:Name= «x_text1»
Text= «valeur courante: 0» Canvas.Left= «126» Canvas.Top= «248»
158 Développez des applications Internet avec Silverlight 5
FontFamily= «Verdana» FontSize= «12» />
private void x_slider1_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e) {
x_text1.Text = «valeur courante: « + x_slider1.Value.ToString();
}
<Slider Canvas.Left= «147» Canvas.Top= «281» Height= «143» Maximum= «10»
Minimum= «0» Name= «x_slider2» Width= «58»
Orientation= «Vertical» Background= «GhostWhite» />
<TextBlock Canvas.Left= «12» Canvas.Top= «313» FontFamily= «Verdana»
FontSize= «14» Text= «Slider avec sens vertical et databinding:»
Width= «129» Height= «66» TextWrapping= «Wrap»
TextAlignment= «Center» />
<TextBlock Canvas.Left= «224» Canvas.Top= «337»
FontFamily= «Verdana»
FontSize= «12» Name= «textBlock1»
Height= «24» Width= «164» TextAlignment= «Center»
TextWrapping= «Wrap»
Text= «{Binding ElementName=x_slider2, Path=Value}» />
<TextBlock Canvas.Left= «224» Canvas.Top= «315» FontFamily= «Verdana»
FontSize= «12» Height= «24» Name= «textBlock2»
Text= «valeur courante:» TextAlignment= «Center»
TextWrapping= «Wrap»
Width= «164» />

9.3 - Le contrôle ScrollBar

La classe ScrollBar représente un contrôle qui fournit une barre de défilement


ayant un contrôle Thumb glissant dont la position correspond à une valeur.
Le contrôle ScrollBar se compose d’un contrôle Thumb et de deux contrôles
RepeatButton. Vous pouvez augmenter et diminuer la propriété RangeBase.Value
du contrôle ScrollBar en appuyant sur les contrôles RepeatButton ou en déplaçant
le contrôle Thumb. La plage de valeurs par défaut pour la propriété RangeBase.
Value va de 0 à 10. RangeBase.Value représente la distance linéaire du contrôle
Thumb entre les points de terminaison du contrôle ScrollBar. Vous pouvez modifier
la plage de valeurs par défaut en définissant les propriétés RangeBase.Minimum
Copyright 2012 Patrice REY

et RangeBase.Maximum. La propriété Orientation détermine si le ScrollBar est


affiché horizontalement ou verticalement. Le suivi du défilement dans un ScrollBar
est orienté de sorte que les valeurs augmentent de haut en bas pour un contrôle
ScrollBar vertical, ou de gauche à droite pour un contrôle ScrollBar horizontal. A
noter que pour afficher le contenu d’une zone dotée de barres de défilement, il
faut utiliser le contrôle ScrollViewer.
CHAPITRE 5 □ Les contrôles Silverlight 159

10 - Les contrôles Calendar et DatePicker

Les classes Calendar et DatePicker héritent de la classe Control. La classe Calendar


représente un contrôle qui permet à un utilisateur de sélectionner une date à
l’aide d’un affichage de calendrier visuel. Un contrôle Calendar peut être utilisé
seul ou comme partie déroulante d’un contrôle DatePicker. Il affiche les jours d’un
mois, les mois d’une année ou les années d’une décennie, selon la valeur de la
propriété DisplayMode. Lors de l’affichage des jours d’un mois, l’utilisateur peut
sélectionner une date, une plage de dates ou plusieurs plages de dates. Les types
de sélections autorisées sont contrôlés par la propriété SelectionMode. La plage de
dates affichée est régie par les propriétés DisplayDateStart et DisplayDateEnd. Si
DisplayMode est Year ou Decade, seuls les mois ou les années qui contiennent des
dates affichables sont affichés. La définition de la plage affichable sur une plage
qui n’inclut pas le DisplayDate actuel lève un ArgumentOutOfRangeException. La
propriété BlackoutDates peut être utilisée pour spécifier des dates qui ne peuvent
pas être sélectionnées. Ces dates seront affichées comme grisées et désactivées.
Par défaut, Today est mis en surbrillance. Cela peut être désactivé en affectant la
valeur false à IsTodayHighlighted.
La classe DatePicker représente un contrôle qui permet à l’utilisateur de sélectionner
une date. Le contrôle DatePicker permet à l’utilisateur de sélectionner une date en
la tapant dans un champ de texte ou en utilisant un contrôle Calendar déroulant.
Une grande partie des propriétés d’un contrôle DatePicker concernent la gestion de
son contrôle Calendar intégré et fonctionnent de manière identique à la propriété
équivalente dans Calendar. En particulier, les propriétés IsTodayHighlighted,
FirstDayOfWeek, BlackoutDates, DisplayDateStart, DisplayDateEnd, DisplayDate
et SelectedDate fonctionnent de manière identique à leurs équivalents Calendar.
Les utilisateurs peuvent taper une date directement dans un champ de texte,
ce qui définit la propriété Text. Si le DatePicker ne peut pas convertir la chaîne
entrée en date valide, l’événement DateValidationError est déclenché. Par
défaut, cela provoque une exception, mais un gestionnaire d’événements pour
DateValidationError peut affecter la valeur false à la propriété ThrowException et
empêcher la levée d’une exception.
Le projet DemoDate.sln, dans le dossier chapitre05, illustre l’utilisation des contrôles
Calendar et DatePicker. La figure 5.44 visualise, côté gauche, un calendrier avec la
propriété DisplayMode à Month, et un calendrier avec la propriété DisplayMode à
Year. Côté droit sont positionnés deux contrôles DatePicker, l’un avec la propriété
160 Développez des applications Internet avec Silverlight 5
SelectedDateFormat fixée à Short et l’autre avec la propriété SelectedDateFormat
fixée à Long.
FIGURE 5.44

un contrôle
Calendar avec
DisplayMode= un contrôle
«Month» DatePicker avec
SelectedDateFormat
un contrôle
= «Short»
DatePicker avec
un contrôle SelectedDateFormat
Calendar avec = «Long»
DisplayMode=
«Year»

<!-- calendriers -->


<StackPanel>
<sdk:Calendar Margin= «3» SelectionMode= «MultipleRange»
SelectedDatesChanged= «x_calendrier_SelectedDatesChanged»
Name= «x_calendrier_jour»></sdk:Calendar>
<sdk:Calendar Margin= «3» DisplayMode= «Year»
SelectedDatesChanged= «x_calendrier_SelectedDatesChanged»
Name= «x_calendrier_mois»></sdk:Calendar>
</StackPanel>
<TextBlock Grid.Row= «1» Grid.ColumnSpan= «2» x:Name= «x_info_erreur»
Foreground= «Black» TextWrapping= «Wrap» Height= «25»
FontFamily= «Verdana» FontSize= «14» Text= «»></TextBlock>
<!-- datepicker -->
<StackPanel Grid.Column= «1»>
<sdk:DatePicker Margin= «3»
DateValidationError= «x_datepicker_DateValidationError»
Name= «x_datepicker1»></sdk:DatePicker>
<sdk:DatePicker Margin= «3» SelectedDateFormat= «Long»
Copyright 2012 Patrice REY

DateValidationError= «x_datepicker_DateValidationError»
Name= «x_datepicker2»></sdk:DatePicker>
</StackPanel>
Dans le contrôle Calendar, on sélectionne une date (figure 5.45). Dans le gestionnaire
de l’événement SelectedDatesChanged, on implémente une information d’erreur si
on choisit par exemple un jour de week-end (DayOfWeek.Saturday et DayOfWeek.
Sunday).
CHAPITRE 5 □ Les contrôles Silverlight 161
FIGURE 5.45

on
sélectionne l’événement
une date dans SelectedDatesChanged
un Calendar nous prévient qu’on
choisit un jour de week
end

private void x_calendrier_SelectedDatesChanged(object sender,


SelectionChangedEventArgs e) {
x_info_erreur.Text = «»;
foreach (DateTime selectedDate in e.AddedItems) {
if ((selectedDate.DayOfWeek == DayOfWeek.Saturday) ||
(selectedDate.DayOfWeek == DayOfWeek.Sunday)) {
x_info_erreur.Text = «les weekend ne sont pas autorisés»;
//((Calendar)sender).SelectedDates.Remove(selectedDate);
}
}
}
La figure 5.46 visualise le choix effectué dans un DatePicker avec deux formats
d’affichage différents. Si on entre la date en la tapant, le gestionnaire de l’événement
DateValidationError nous prévient en cas d’erreur.
FIGURE 5.46
162 Développez des applications Internet avec Silverlight 5
private void x_datepicker_DateValidationError(object sender,
DatePickerDateValidationErrorEventArgs e) {
x_info_erreur.Text = «’» + e.Text +
«’ est un choix invalide car « + e.Exception.Message;
}

Copyright 2012 Patrice REY


CHAPITRE 6 DANS CE CHAPITRE
• La classe Application
• Le passage de
Le modèle paramètres
• L’écran d’accueil
d’application personnalisé
• Les fichiers de
ressources
Silverlight • Les bibliothèques de
classes

Dans les précédents chapitres, nous avons vu


de nombreux contrôles à insérer dans une page
Silverlight. Il est temps maintenant de regarder
comment fonctionne le modèle d’application.
La classe Application représente le cycle de vie
d’une application au travers de ses propriétés, et
de ses événements qui se produisent lors de la
création, du chargement, du fonctionnement et de
la fin d’une application.
Nous verrons comment s’effectue le passage de
paramètres à une application dans le but de la
personnaliser.
La construction d’un écran d’accueil permettra
de faire patienter l’utilisateur quand le poids de
l’application est important.
La gestion des fichiers de ressources et les façons d’y
accéder, représentent une partie très importante à
savoir manipuler parfaitement.
Vous apprendrez notamment les stratégies qui
permettent de traiter du téléchargement à la
demande des fichiers quand ils sont nécessaires.
Un gain en poids et en réactivité des applications
est toujours très apprécié.
164 Développez des applications Internet avec Silverlight 5
Le modèle d’application Silverlight fournit les fonctionnalités suivantes, qui peuvent
être utilisées par les applications managées :
• un système d’activation qui permet au plug-in Silverlight de télécharger le
package d’application ainsi que tous les assemblys de bibliothèque externe
dont votre application a besoin au démarrage.
• une classe Application qui encapsule des services souvent requis par les
applications Silverlight, comme la notification des événements de cycle de vie
et une interface vers le plug-in Silverlight.
• un système d’extensibilité d’application qui encapsule des services
supplémentaires requis par certaines applications, tels qu’une couche d’accès
aux données personnalisée.
• un système de gestion des ressources qui inclut des mécanismes de secours et
de référencement d’URI, le chargement de ressources à la demande, le partage
de styles et de modèles ainsi que la prise en charge de la globalisation.
Grâce à ces fonctionnalités, vous pouvez implémenter des structures d’application
correctement factorisées qui équilibrent le temps de démarrage avec la disponibilité
des ressources. Par exemple, vous pouvez implémenter une application qui fournit
un accès immédiat à un petit jeu initial de pages mais télécharge d’autres pages à
la demande. Vous pouvez également fournir un téléchargement initial volumineux
et afficher un écran de démarrage pour garantir la réactivité. Si vous fournissez un
téléchargement initial important, vous pouvez réduire le temps de téléchargement
suivant à l’aide de la mise en cache des bibliothèques d’applications. Enfin vous
pouvez implémenter un système de navigation personnalisé comme service
d’extension d’application.

1 - La classe Application

Lors de la génération d’un projet Silverlight, Visual Studio réalise un fichier avec
l’extension .xap qui constitue le paquet de déploiement de l’application. Ce fichier
est en fait une archive au format ZIP.
Copyright 2012 Patrice REY

Le projet ModeleApplication.sln, dans le dossier chapitre06, une fois compilé,


génère un paquet ModeleApplication.xap. En renommant ce paquet avec
l’extension .zip et en décompressant ce paquet, on s’aperçoit qu’il est constitué
d’un fichier ModeleApplication.dll et d’un fichier manifeste AppManifest.xaml.
Bien évidemment, le paquet peut contenir d’autres assemblys référencés par
l’application, ainsi que des fichiers de ressources.
La figure 6.1 visualise la façon de faire pour ouvrir et lire le contenu du fichier
CHAPITRE 6 □ Le modèle d’application Silverlight 165

manifeste AppManifest.xaml. Dans l’explorateur de solutions, on clique sur le


bouton afficher tous les fichiers. Dans le sous-dossier Debug du dossier Bin, on
fait un clic droit sur le fichier AppManifest.xaml, et on choisit ouvrir avec. Dans la
fenêtre qui s’ouvre, on choisit éditeur XML (Texte).
FIGURE 6.1
bouton pour afficher tous
les fichiers

2
1

Le fichier manifeste AppManifest.xaml est un fichier au format XAML, qui contient


dans notre exemple:
<Deployment xmlns= «http://schemas.microsoft.com/client/2007/deployment»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
EntryPointAssembly= «ModeleApplication»
EntryPointType= «ModeleApplication.App»
RuntimeVersion= «5.0.61118.0»>
<Deployment.Parts>
<AssemblyPart x:Name= «ModeleApplication» Source= «ModeleApplication.dll»
/>
</Deployment.Parts>
</Deployment>
Chaque assembly embarqué est décrit au moyen d’un élément AssemblyPart, au
sein de la collection Parts de l’élément Deployment. L’élément Deployment décrit
le point d’entrée de l’application au moyen des attributs EntryPointAssembly (qui
166 Développez des applications Internet avec Silverlight 5
est l’assembly principal) et EntryPointType (qui est la classe à instancier). Ici, nous
retrouvons bien la classe App générée par Visual Studio. L’attribut RuntimeVersion
spécifie la version requise de Silverlight.
La classe App est instanciée par le plug-in à partir des informations contenues
dans le fichier manifeste. Il s’agit d’une classe non visuelle. Son fichier App.xaml
est utilisé principalement pour spécifier d’une façon déclarative des propriétés et
des ressources globales.
<Application
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
x:Class= «ModeleApplication.App» >
<Application.Resources>

</Application.Resources>
</Application>
Comme le montre la figure 6.2, la classe App hérite de la classe Application qui
elle-même hérite de la classe de base Object.
FIGURE 6.2

Copyright 2012 Patrice REY


CHAPITRE 6 □ Le modèle d’application Silverlight 167

La classe Application est une classe qui encapsule une application Silverlight et
fournit les services suivants :
• point d’entrée d’application,
• durée de vie d’une application,
• gestion d’application,
• ressources de portée application,
• détection d’exception non gérée
Le point d’entrée d’une application Silverlight est la classe de votre assembly
d’application qui dérive de la classe Application. Cette classe est connue
comme classe de l’application. Lorsque le plug-in démarre, Silverlight utilise les
métadonnées du package d’application pour instancier la classe d’application. A ce
stade, la durée de vie de l’application commence. La durée de vie d’une application
se produit dans l’ordre suivant :
• construction (Application),
• initialisation (Startup),
• exécution de l’application,
• arrêt de l’application (initié par l’utilisateur),
• sortie (Exit).
En gérant l’événement Startup, vous pouvez récupérer et traiter les paramètres
d’initialisation à partir de la propriété InitParams de l’objet StartupEventArgs
passé au gestionnaire d’événements Startup. Les paramètres d’initialisation sont
configurés à l’aide de la propriété initParams de l’élément HTML <object> utilisé
pour configurer et instancier le plug-in Silverlight.
Lors du démarrage, vous pouvez aussi spécifier l’interface utilisateur de l’application
principale à afficher en définissant la propriété RootVisual.
Une fois l’application en cours d’exécution, il est possible d’accéder à l’objet
Application et à son état à partir de la propriété statique Current. Le motif singleton
garantit que l’état géré par Application, y compris les ressources partagées
(Resources) et les propriétés personnalisées, est disponible à partir d’un seul
emplacement de portée application.
Dans le fichier App.xaml, l’attribut x:Class, de la balise <Application>, fournit la
valeur pour le code-behind qui initie l’élément visuel racine. Ici nous avons x:Class
= «ModeleApplication.App» qui référence la classe App. En ouvrant le fichier App.
xaml.cs, nous avons le code de programmation suivant:
public partial class App : Application {
public App() {
this.Startup += this.Application_Startup;
168 Développez des applications Internet avec Silverlight 5
this.Exit += this.Application_Exit;
this.UnhandledException += this.Application_UnhandledException;
InitializeComponent();
}
private void Application_Startup(object sender, StartupEventArgs e) {
this.RootVisual = new MainPage();
}
private void Application_Exit(object sender, EventArgs e) {
}
private void Application_UnhandledException(object sender,
ApplicationUnhandledExceptionEventArgs e) {
if (!System.Diagnostics.Debugger.IsAttached) {
e.Handled = true;
Deployment.Current.Dispatcher.BeginInvoke(
delegate { ReportErrorToDOM(e); });
}
}
private void ReportErrorToDOM(ApplicationUnhandledExceptionEventArgs e) {
try {
string errorMsg = e.ExceptionObject.Message
+ e.ExceptionObject.StackTrace;
errorMsg = errorMsg.Replace(‘»’, ‘\’’).Replace(«\r\n», @»\n»);
System.Windows.Browser.HtmlPage.Window.Eval(«throw new Error(\»Unhandled
Error in Silverlight Application « + errorMsg + «\»);»);
} catch (Exception) {
}
}
}
Le code-behind montre que la classe App hérite de la classe Application. Le
constructeur en câble les événements Sartup (émis au démarrage de l’application)
et Exit (émis en sortie de l’application). La classe MainPage est instanciée dans
l’événement Startup et assignée à la propriété RootVisual.
La classe Application gère les services principaux de l’application au moyen d’un
singleton qui peut être obtenu par la propriété statique Application.Current.
A noter que l’événement UnhandledException est déclenché en cas d’exception
non gérée par le code Silverlight. L’implémentation générée par Visual Studio
transmet le message d’erreur au plug-in, qui lui-même le transmet à la fonction
Copyright 2012 Patrice REY

javascript onSilverlightError.
Si on atteint la définition de la méthode InitializeComponent qui est générée
automatiquement par Visual Studio, on voit qu’elle fait appel à la méthode statique
Application.LoadComponent. Cette méthode statique charge un fichier XAML situé
à l’URI (Uniform Resource Identifier) spécifié et le convertit en une instance de
l’objet spécifié par l’élément racine du fichier XAML. Ici elle charge le fichier App.
xaml et le convertit en une instance de la classe App.
CHAPITRE 6 □ Le modèle d’application Silverlight 169

public void InitializeComponent() {


if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Windows.Application.LoadComponent(this,
new System.Uri(«/ModeleApplication;component/App.xaml»,
System.UriKind.Relative));
}
Les membres de la classe Application sont:
• la propriété statique Current qui obtient l’objet Application pour l’application
en cours.
• la propriété Host qui obtient divers détails sur l’hôte de l’application Silverlight.
• la propriété InstallState qui obtient l’état d’installation hors navigateur actuel
de l’application.
• la propriété IsRunningOutOfBrowser qui obtient une valeur qui indique si
l’application a été lancée à partir de l’état hors navigateur.
• la propriété MainWindow qui obtient la fenêtre d’application hors navigateur.
• la propriété Resources qui obtient une collection de ressources de portée
application, telles que les styles, les modèles et les pinceaux.
• la propriété RootVisual qui définit l’interface utilisateur de l’application
principale.
• la propriété ApplicationLifetimeObjects qui obtient les services d’extension
d’application qui ont été inscrits pour cette application.
• la propriété HasElevatedPermissions qui obtient une valeur qui indique si
l’application s’exécute en dehors du navigateur avec des autorisations élevées.
• la méthode CheckAndDownloadUpdateAsync qui lance un processus asynchrone
pour vérifier la disponibilité d’une version mise à jour de l’application et pour
la télécharger.
• la méthode statique GetResourceStream qui retourne un fichier de ressources
à partir d’un emplacement du package d’application.
• la méthode Install qui tente d’installer l’application afin qu’elle puisse s’exécuter
en dehors du navigateur.
• la méthode statique LoadComponent qui charge un fichier XAML situé à l’URI
spécifié et le convertit en une instance de l’objet spécifié par l’élément racine
du fichier XAML.
• l’événement CheckAndDownloadUpdateCompleted qui se produit lorsque
l’application a terminé de vérifier la disponibilité des mises à jour en réponse à
un appel de la méthode CheckAndDownloadUpdateAsync.
170 Développez des applications Internet avec Silverlight 5
• l’événement Exit qui se produit juste avant la fermeture d’une application, et
qui ne peut être annulé.
• l’événement InstallStateChanged qui se produit lorsque la valeur de la propriété
InstallState est modifiée.
• l’événement Startup qui se produit au démarrage d’une application.
• l’événement UnhandledException qui se produit lorsqu’une exception est levée
par Silverlight et qu’elle n’est pas gérée.

2 - Passage de paramètres dans le plug-in

L’élément HTML object définissant le plug-in permet de définir des paramètres


personnalisés au moyen d’un élément param de nom initParams contenant
une liste de paires argument=valeur séparées par une virgule (<param name =
«initParams» value = «param1=valeur1,param2=valeur2» />).
Dans le code Silverlight, les paramètres et leurs valeurs peuvent être obtenus dans
l’événement Startup de l’objet Application, dans le dictionnaire InitParams de
l’argument de type StartupEventArgs.
Le projet DemoTransfertParametre.sln, dans le dossier chapitre06, illustre le
passage de paramètres. Le contrôle MainPage.xaml est constitué d’un ComboBox
présentant un choix entre une vue synthétique et une vue détaillée d’une fiche
d’un acteur de cinéma. La vue synthétique affiche l’image de l’acteur et son nom
en dessous. La vue détaillée ajoute en plus, côté droit, un ScrollViewer qui déroule
un texte concernant la vie de l’acteur.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Height= «400»
Width= «500»>
<ComboBox Canvas.Left= «12» Canvas.Top= «12» Height= «31» Name= «x_combo»
Width= «183» SelectedIndex= «0» FontFamily= «Verdana» FontSize= «14»
SelectionChanged= «x_combo_SelectionChanged»>
<ComboBoxItem Content= «vue synthétique» IsSelected= «True» />
<ComboBoxItem Content= «vue détaillée» />
</ComboBox>
<Image Canvas.Left= «12» Canvas.Top= «59» Height= «211» Name= «image1»
Copyright 2012 Patrice REY

Stretch= «Fill» Width= «183»


Source= «/DemoTransfertParametre;component/images/lino_ventura.jpg» />
<TextBlock Canvas.Left= «12» Canvas.Top= «284» Height= «73»
Name= «x_text_rapide» Width= «183»
TextWrapping= «Wrap» FontFamily= «Verdana» FontSize= «13» />
<ScrollViewer Canvas.Left= «222» Canvas.Top= «59» Height= «278»
Name= «x_scroll» Width= «266» Background= «White»>
<TextBlock Height= «310» Name= «x_texte_complet» HorizontalAlignment=
«Left»
CHAPITRE 6 □ Le modèle d’application Silverlight 171

VerticalAlignment= «Top» Width= «238» FontFamily= «Verdana» FontSize= «13»


TextWrapping= «Wrap» />
</ScrollViewer>
</Canvas>
La figure 6.3 visualise les résultats obtenus en fonction des paramètres passés.
FIGURE 6.3

<param name = "initParams" <param name = "initParams"


value = "vue=synthetique" /> value = "vue=detaillee" />
L’élément param de nom initParams, dans la balise object, reçoit une valeur
vue=synthetique dans son attribut value (figure 6.3, côté gauche) et une valeur
vue=detaillee dans son attribut value (figure 6.3, côté droit).
<object data= «data:application/x-silverlight-2,»
type= «application/x-silverlight-2» width= «100%» height= «100%»>
<param name= «source» value= «ClientBin/DemoTransfertParametre.xap»/>
<param name= «onError» value= «onSilverlightError» />
<param name= «background» value= «white» />
<param name= «minRuntimeVersion» value= «5.0.61118.0» />
<param name= «autoUpgrade» value= «true» />
<param name= «initParams» value= «vue=synthetique» />
</object>
<object data= «data:application/x-silverlight-2,»
type= «application/x-silverlight-2» width= «100%» height= «100%»>
<param name= «source» value= «ClientBin/DemoTransfertParametre.xap»/>
<param name= «onError» value= «onSilverlightError» />
<param name= «background» value= «white» />
<param name= «minRuntimeVersion» value= «5.0.61118.0» />
<param name= «autoUpgrade» value= «true» />
<param name= «initParams» value= «vue=detaillee» />
</object>
172 Développez des applications Internet avec Silverlight 5
Dans la classe App, qui hérite de Application, on défini un champ m_mode_encours,
de type ModeDeVue qui est une énumération (composée de 3 valeurs énumérées:
non_defini, mode_synthetique et mode_detaille). La propriété ModeEnCours
stocke l’état du champ m_mode_encours.
La démarche, pour récupérer des paramètres, consiste à vérifier que la clé vue est
présente dans les paramètres (par la méthode ContainsKey appliquée au dictionnaire
InitParams). Si la clé est présente, on récupère sa valeur (InitParams[«vue»]) et
on met à jour le champ m_mode_encours. Puis on instancie MainPage que l’on
affecte à RootVisual de la classe App.
public enum ModeDeVue { non_defini, mode_synthetique, mode_detaille };
private ModeDeVue m_mode_encours;
public ModeDeVue ModeEnCours {
get { return m_mode_encours; }
}
private void Application_Startup(object sender, StartupEventArgs e) {
//cas: <param name=»initParams» value=»vue=synthetique» />
//cas: <param name=»initParams» value=»vue=detaillee» />
m_mode_encours = ModeDeVue.non_defini;
if (e.InitParams.ContainsKey(«vue»)) {
string recup_vue = e.InitParams[«vue»];
switch (recup_vue) {
case «synthetique»:
this.m_mode_encours = ModeDeVue.mode_synthetique;
break;
case «detaillee»:
this.m_mode_encours = ModeDeVue.mode_detaille;
break;
}
this.RootVisual = new MainPage();
}
}
Dans le fichier MainPage.xaml.cs, on récupère la valeur de la propriété ModeEnCours
de la classe App en utilisant la méthode statique Application.Current. Il faut pour
cela effectuer un cast ((App)Application.Current).ModeEnCours pour récupérer la
propriété. Il ne reste plus qu’à initialiser les contrôles en fonction de l’énumération
App.ModeDeVue.mode_synthetique et App.ModeDeVue.mode_detaillee.
Copyright 2012 Patrice REY

private void UserControl_Loaded(object sender, RoutedEventArgs e) {


XmlReader lecteur_xml = XmlReader.Create(«contenu.xml»);
XmlReaderSettings set = new XmlReaderSettings();
lecteur_xml.ReadToFollowing(«fiche»);
lecteur_xml.ReadToFollowing(«titre»);
x_text_rapide.Text = lecteur_xml.ReadElementContentAsString();
lecteur_xml.ReadToFollowing(«detail»);
x_texte_complet.Text = lecteur_xml.ReadElementContentAsString();
CHAPITRE 6 □ Le modèle d’application Silverlight 173

lecteur_xml.Close();
if (((App)Application.Current).ModeEnCours ==
App.ModeDeVue.mode_synthetique) {
x_combo.SelectedIndex = 0;
x_scroll.Visibility = Visibility.Collapsed;
}
if (((App)Application.Current).ModeEnCours ==
App.ModeDeVue.mode_detaille) {
x_combo.SelectedIndex = 1;
x_scroll.Visibility = Visibility.Visible;
}
}
private void x_combo_SelectionChanged(object sender,
SelectionChangedEventArgs e) {
if (x_scroll != null) {
switch (x_combo.SelectedIndex) {
case 0:
x_scroll.Visibility = Visibility.Collapsed;
break;
case 1:
x_scroll.Visibility = Visibility.Visible;
break;
}
}
}
Maintenant, nous allons voir comment instancier une page XAML nommée en
fonction des paramètres passés. Nous avons un UserControl PageDico.xaml que
nous voulons instancier et affecter à la propriété RootVisual de la classe App, et
avec le choix de la vue synthétique ou détaillée.
Dans les paramètres, on passe la séquence value= «page_depart=PageDico,
vue=detaillee».
<object data= «data:application/x-silverlight-2,»
type= «application/x-silverlight-2» width= «100%» height= «100%»>
<param name= «source» value= «ClientBin/DemoTransfertParametre.xap»/>
<param name= «onError» value= «onSilverlightError» />
<param name= «background» value= «white» />
<param name= «minRuntimeVersion» value= «5.0.61118.0» />
<param name= «autoUpgrade» value= «true» />
<param name= «initParams» value= «page_depart=PageDico,vue=detaillee» />
</object>
La démarche est identique pour vérifier la présence d’un paramètre dans le
dictionnaire et pour obtenir sa valeur.
Comme on souhaite instancier PageDico.xaml, et non pas MainPage.xaml, il faut
donc créer une instance d’un fichier XAML pour l’affecter à RootVisual. Le type
de la classe App est récupéré par la méthode GetType et stocké dans la variable
174 Développez des applications Internet avec Silverlight 5
v_type.
On récupère l’assembly v_assembly par la propriété Assembly de v_type (il faut
ajouter une référence using System.Reflection pour la classe Assembly). Pour
instancier un UserControl, on lui affecte la valeur résultant de l’application de
la méthode CreateInstance sur la variable v_assembly. Cette méthode reçoit en
paramètre le nom du fichier XAML à instancier (qui est v_type.Namespace + «.»
+ v_nom_page_charger qui correspond à PageDico.xaml).
//cas: <param name=»initParams»
//value=»page_depart=PageDico,vue=synthetique» />
if (e.InitParams.ContainsKey(«page_depart»)) {
string v_nom_page_charger = e.InitParams[«page_depart»];
//usercontrol a charger
UserControl uc_page_a_charger = null;
//try catch pour lever une exception
try {
//type App
Type v_type = this.GetType();
//assembly correspondante
Assembly v_assembly = type.Assembly;
//instanciation de PageDico.xaml
uc_page_a_charger =
(UserControl)v_assembly.CreateInstance(v_type.Namespace + «.»
+ v_nom_page_charger);
} catch {
uc_page_a_charger = null;
}
this.RootVisual = uc_page_a_charger;
//mise a jour des vues synthetique et detaillee
m_mode_encours = ModeDeVue.non_defini;
if (e.InitParams.ContainsKey(«vue»)) {
string recup_vue = e.InitParams[«vue»];
switch (recup_vue) {
case «synthetique»:
this.m_mode_encours = ModeDeVue.mode_synthetique;
break;
case «detaillee»:
this.m_mode_encours = ModeDeVue.mode_detaille;
break;
Copyright 2012 Patrice REY

}
}
}
La figure 6.4 illustre le résultat obtenu par l’instanciation de l’UserControl PageDico.
xaml avec sa vue détaillée. On avait passé en paramètre page_depart=PageDico,
et on affecte une instance de PageDico.xaml à RootVisual de App.
CHAPITRE 6 □ Le modèle d’application Silverlight 175
FIGURE 6.4

<param name = "initParams"


value = "page_depart=PageDico,
vue=detaillee" />

3 - Ecran d’accueil personnalisé

Si le poids du fichier de l’application est conséquent, il faudra le faire précéder


d’un paquet léger spécialisé dans l’affichage d’un écran d’accueil (splash screen),
contenant généralement une animation.
Le projet DemoEcranAccueil.sln (figure 6.5), dans le dossier chapitre06, consiste
à visualiser des photographies de paysages sous la neige. Ces photographies sont
incorporées comme ressources dans l’application, en les ajoutant dans un dossier
intitulé images. Une fois l’application générée, on s’aperçoit que le fichier au
format xap a une taille de 12.7 Mo.
FIGURE 6.5
176 Développez des applications Internet avec Silverlight 5
Quand l’utilisateur va charger l’application, Silverlight va, par défaut, afficher un
écran d’accueil (figure 6.6). C’est un écran d’accueil basique permettant de faire
patienter l’utilisateur avant la fin complète du téléchargement.
FIGURE 6.6

Le but est de remplacer cet écran d’accueil simpliste par un écran d’accueil
personnalisé. La figure 6.7 visualise l’écran d’accueil que nous allons réaliser.
FIGURE 6.7

Copyright 2012 Patrice REY

Dans les paramètres de la balise object qui représente le plug-in Silverlight, on


ajoute une balise param dont l’attribut name est fixé à splashsreensource, et
l’attribut value est fixé à EcranAccueil.xaml. Pour suivre la progression de la
quantité téléchargée, de façon à pouvoir informer l’utilisateur, on ajoute une
CHAPITRE 6 □ Le modèle d’application Silverlight 177

balise param dont l’attribut name est fixé à onsourcedownloadprogresschanged


et l’attribut value est fixé à onsourcedownloadprogresschanged (la valeur de cet
attribut représente la fonction javascript qui sera appelée pour mettre à jour les
données de l’écran d’accueil).
<object data= «data:application/x-silverlight-2,»
type= «application/x-silverlight-2» width= «100%» height= «100%»>
<param name= «source» value= «ClientBin/DemoEcranAccueil.xap»/>
<param name= «onError» value= «onSilverlightError» />
<param name= «background» value= «white» />
<param name= «minRuntimeVersion» value= «5.0.61118.0» />
<param name= «autoUpgrade» value= «true» />
<!-- ecran d’accueil -->
<param name= «splashscreensource» value= «EcranAccueil.xaml» />
<param name= «onsourcedownloadprogresschanged»
value= «onSourceDownloadProgressChanged» />
</object>
Pour ajouter un écran d’accueil (figure 6.8), on fait un clic droit sur le projet web,
puis choisir ajouter un nouvel élément. Dans la fenêtre qui s’ouvre, choisissez les
modèles installés pour Silverlight (côté gauche), cliquez sur Page JScript (côté
droit), et nommez la page EcranAccueil.xaml.
FIGURE 6.8

Avec un conteneur Grid, son contenu sera positionné au centre de la fenêtre. La


barre de progression est composé d’un rectangle x_rect_fond pour simuler le fond,
d’un rectangle x_rect_progress avec une couleur bleue pour simuler la progression,
et d’un indicateur texte indicateur_texte pour afficher le taux de progression. Le
rectangle x_rect_progress a une longueur nulle au départ, et la transformation
d’échelle ScaleX le fera grandir en longueur.
178 Développez des applications Internet avec Silverlight 5
<Grid xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»>
<StackPanel VerticalAlignment= «Center»>
<Grid>
<Rectangle x:Name= «x_rect_fond» Fill= «White» Stroke= «Black»
StrokeThickness= «1» Stretch= «Fill» Height= «30» Margin= «100,20»>
</Rectangle>
<Rectangle x:Name= «x_rect_progress» Fill= «Blue» Height= «28»
Margin= «101,20»>
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name= «transformation_echelle» ScaleX= «0»
ScaleY= «1»/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
<TextBlock x:Name= «indicateur_texte» HorizontalAlignment= «Center»
Text= «chargement 0%»></TextBlock>
</StackPanel>
</Grid>
La fonction javascript onSourceDownloadProgressChanged permet de mettre
en relation le taux de téléchargement avec l’application. Elle consiste à fournir à
indicateur_texte la chaîne de caractères à écrire, et à transformation_echelle la
valeur de pourcentage correspondant à la progression.
<script type= «text/javascript»>
function onSourceDownloadProgressChanged(sender, eventArgs) {
sender.findName(«indicateur_texte»).Text =
«chargement «+ Math.round((eventArgs.progress * 100)) + «% en cours ...»;
sender.findName(«transformation_echelle»).ScaleX = eventArgs.progress;
}
</script>

4 - Les fichiers de ressources

Les fichiers de ressources sont des fichiers spécialement marqués pour être
embarqués ou référencés par l’application, et pour être chargés au moyen d’un
Copyright 2012 Patrice REY

URI.
Ces fichiers peuvent être de n’importe quel type, binaire ou texte. Ils doivent
être intégrés au projet Visual Studio, et leur propriété Action de génération doit
prendre l’une des valeurs suivantes:
• Resource: le fichier est embarqué dans l’assembly.
• Contenu: le fichier est embarqué dans le paquet mais pas dans l’assembly.
CHAPITRE 6 □ Le modèle d’application Silverlight 179

• Aucun: le fichier n’est pas embarqué et sera chargé à la demande depuis le


serveur.
Le projet DemoRessourceLocale.sln, dans le dossier chapitre06, illustre différents
modes pour l’accessibilité aux fichiers de ressources.

4.1 - Les fichiers embarqués

Dans le projet (figure 6.9), on ajoute un dossier contenu dans lequel on y insère un
élément existant, sous forme d’une image intitulée meuble_peint_1.png (repère
n°1). Dans la fenêtre des propriétés, son action de génération est fixée à Resource
et son nom de fichier est fixé à meuble_peint_1.png (repère n°2).
Dans le code XAML, la propriété Source de Image est fixée à contenu/meuble_
peint_1.png. La figure 6.10 illustre le résultat obtenu.
<Image Canvas.Left= «12» Canvas.Top= «49» Height= «228» Name= «x_img1»
Stretch= «Fill» Width= «200»
Source= «contenu/meuble_peint_1.png» />

FIGURE 6.9

FIGURE 6.10
180 Développez des applications Internet avec Silverlight 5
Une autre façon consiste, en XAML, à fixer la propriété Source du contrôle Image
par une référence à une image indiquant qu’elle n’est pas chargée (ici on utilise
une image point_d_interrogation.png).
<Image Canvas.Left= «227» Canvas.Top= «49» Height= «228» Name= «x_img2»
Stretch= «Fill» Width= «200»
Source= «/DemoRessourceLocale;component/contenu/point_d_interrogation.png»
/>
A noter qu’il existe deux façons pour fixer la propriété Source:
• soit en référençant directement l’emplacement par un URI (Source = « contenu/
point_d_interrogation.png»).
• soit en référençant par l’assembly (Source = «/DemoRessourceLocale;component/
contenu/point_d_interrogation.png»).
Dans le code-behind, on charge l’image embarquée dans l’assembly. Pour cela, on
instancie un objet StreamResourceInfo qui reçoit le fichier de ressources obtenu
à partir de l’emplacement du package de l’application, par la méthode statique
Application.GetResourceStream. On instancie un nouvel objet BitmapImage et on
affecte à sa propriété Source le flux contenu dans la ressource (propriété Stream du
StreamResourceInfo). Le contrôle Image x_img2 reçoit dans sa propriété Source
le BitmapImage.
StreamResourceInfo sri = Application.GetResourceStream(
new Uri(«DemoRessourceLocale;component/contenu/meuble_peint_2.jpg»,
UriKind.Relative));
BitmapImage bitmap2 = new BitmapImage();
bitmap2.SetSource(sri.Stream);
x_img2.Source = bitmap2;
Après le chargement de l’application (figure 6.11), le point d’interrogation est
remplacé par l’image meuble_peint_2.jpg embarquée dans l’assembly.
FIGURE 6.11

Copyright 2012 Patrice REY


CHAPITRE 6 □ Le modèle d’application Silverlight 181

4.2 - Les fichiers téléchargés

Si la ressource n’est pas embarquée dans l’assembly, il est possible de la télécharger


à la demande. Le contrôle Image x_img3 possède comme image de départ celle
d’un point d’interrogation (image embarquée).
<Image Canvas.Left= «443» Canvas.Top= «49» Height= «228» Name= «x_img3»
Source= «/DemoRessourceLocale;component/contenu/point_d_interrogation.png»
Stretch= «Fill» Width= «200» />
Dans le dossier ClientBin sur le serveur, on ajoute un nouveau sous-dossier intitulé
contenu (figure 6.12) dans lequel on ajoute un élément existant meuble_peint_3.
jpg (repère n°1). Cette image ajoutée a comme action de génération aucun et
comme nom de fichier meuble_peint_3.jpg (repère n°2).
FIGURE 6.12

Quand l’application est déployée, on affecte à la propriété Source du contrôle


Image x_img3 un BitmatImage avec l’URI pointant sur meuble_peint_3.jpg.La
figure 6.13 illustre le résultat obtenu.
FIGURE 6.13
182 Développez des applications Internet avec Silverlight 5
BitmapImage bitmap3 = new BitmapImage(
new Uri(«contenu/meuble_peint_3.jpg», UriKind.Relative));
x_img3.Source = bitmap3;
Si l’image à afficher possède un poids important, le contrôle Image va rester sans
avoir rien à afficher tant que la ressource image ne sera pas téléchargée. Il faut
donc faire en sorte de faire patienter l’utilisateur et lui indiquer que l’image est
en cours de téléchargement avant sa visualisation (cette image se trouve sur le
serveur, et par conséquent elle n’est pas embarquée dans l’application, ce qui
permet d’avoir un poids moindre pour le paquet xap).
Le contrôle Image x_img4 affiche une image représentant un point d’interrogation.
Le bouton x_btn_img4 va permettre de télécharger l’image et d’informer l’utilisateur
de ce qui se passe, par la présence d’une barre de progression x_progress_img4 et
d’un indicateur de chargement x_progress_img4_text.
<Border BorderThickness= «1» BorderBrush= «Black» Width= «700» Height= «248»
Canvas.Left= «0» Canvas.Top= «292»>
<Canvas Height= «247»>
<Image Canvas.Left= «6» Canvas.Top= «6» Height= «228» Name= «x_img4»
Source= «/DemoRessourceLocale;component/contenu/point_d_interrogation.png»
Stretch= «Fill» Width= «200» />
<ProgressBar Canvas.Left= «417» Canvas.Top= «93» Height= «32»
Name= «x_progress_img4» Width= «275» Foreground= «Green» />
<TextBlock Canvas.Left= «417» Canvas.Top= «102» Height= «23»
Name= «x_progress_img4_text» Text= «0%» Width= «275»
TextAlignment= «Center» />
<Button Canvas.Left= «211» Canvas.Top= «93» Content= «Télécharger l’image»
Height= «32» Name= «x_btn_img4» Width= «200»
FontSize= «12» Click= «x_btn_img4_Click»/>
<TextBlock Canvas.Left= «211» Canvas.Top= «131» Height= «74»
Name= «x_infos» Text= «infos» Width= «481» TextWrapping= «Wrap» />
</Canvas>
</Border>

La classe WebClient fournit des méthodes communes pour l’envoi de données à


une ressource identifiée par un URI ou pour la réception de données en provenance
de cette ressource.
Copyright 2012 Patrice REY

A noter que pour utiliser un objet WebClient, il faut ajouter une référence à
l’espace de noms System.IO.
Dans le gestionnaire de l’événement Click du bouton, on instancie un objet WebClient.
On lui ajoute un gestionnaire pour l’événement DownloadProgressChanged (émis
quand le téléchargement est en cours) et pour l’événement OpenReadCompleted
(émis quand le téléchargement est terminé). La méthode OpenReadAsync permet
de lancer le téléchargement de façon asynchrone.
CHAPITRE 6 □ Le modèle d’application Silverlight 183

private void x_btn_img4_Click(object sender, RoutedEventArgs e) {


string uri = App.Current.Host.Source.AbsoluteUri;
x_infos.Text = uri + RC;
WebClient webClient = new WebClient();
webClient.OpenReadCompleted +=
new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
webClient.DownloadProgressChanged +=
new DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
webClient.OpenReadAsync(
new Uri(«contenu/ressource_web.zip», UriKind.Relative));
x_btn_img4.IsEnabled = false;
x_progress_img4.Minimum = 0;
x_progress_img4.Maximum = 100;
x_progress_img4.Value = 0;
}
L’image à télécharger, intitulée meuble_peint_4.jpg, se trouve dans une archive
ressource_web.zip. Cette archive est stockée dans le dossier contenu sur le serveur
(figure 6.14).
FIGURE 6.14

Le gestionnaire webClient_DownloadProgressChanged permet de mettre


à jour la barre de progression et l’indicateur de progression, en fonction
du téléchargement effectué. La propriété ProgressPercentage de l’objet
DownloadProgressChangedEventArgs donne la valeur du pourcentage de
téléchargement effectué. La propriété Value de la barre de progression est mise à
jour, ainsi que la propriété Text de l’indicateur de progression.
private void webClient_DownloadProgressChanged(object sender,
DownloadProgressChangedEventArgs e) {
x_progress_img4_text.Text = e.ProgressPercentage.ToString() + « % chargé.»;
x_progress_img4.Value = e.ProgressPercentage;
}
Quand le téléchargement est terminé (événement OpenReadCompleted), s’il n’y a
pas d’erreur (e.Error == null), on peut récupérer la ressource image pour l’afficher.
On instancie un StreamResourceInfo avec le flux téléchargé (e.Result). On instancie
un autre StreamResourceInfo qui reçoit le résultat de flux par la méthode statique
Application.GetResourceStream avec un URI pointant sur la ressource image
184 Développez des applications Internet avec Silverlight 5
(ressource/meuble_peint_4.jpg). Et on affecte à la propriété Source du contrôle
Image x_img4 un BitmapImage dont sa source est le flux contenant l’image à
afficher (propriété Stream du StreamResourceInfo).
private void webClient_OpenReadCompleted(object sender,
OpenReadCompletedEventArgs e) {
if (e.Error == null) {
x_infos.Text += «terminé»;
x_btn_img4.IsEnabled = true;
StreamResourceInfo sri = new StreamResourceInfo(e.Result, null);
BitmapImage bitmap2 = new BitmapImage();
StreamResourceInfo sri_img =
Application.GetResourceStream(sri,
new Uri(«ressource_web/meuble_peint_4.jpg», UriKind.Relative));
bitmap2.SetSource(sri_img.Stream);
x_img4.Source = bitmap2;
} else {
x_infos.Text += «erreur:» + RC;
x_infos.Text += e.Error.Message;
}
}

FIGURE 6.15

2
Copyright 2012 Patrice REY
CHAPITRE 6 □ Le modèle d’application Silverlight 185

La figure 6.15 illustre le résultat obtenu. Au départ (repère n°1), l’image n’est pas
chargée. Quand on clique sur le bouton, le téléchargement commence et la barre de
progression (repère n°2) indique la quantité téléchargée. Quand le téléchargement
est terminé, le point d’interrogation est remplacé par l’image téléchargée.
Le téléchargement d’un fichier XML, intitulé questionnaire.xml, dans l’archive
ressource_web.zip, s’effectue de la même façon par un téléchargement asynchrone.
<Border BorderBrush= «Silver» BorderThickness= «1» Canvas.Left= «0»
Canvas.Top= «547» Height= «273» Name= «border1» Width= «700»>
<Canvas Name= «canvas1»>
<ScrollViewer Canvas.Left= «6» Canvas.Top= «6» Height= «259»
Name= «scrollViewer1» Width= «491» Background= «White»>
<TextBlock Height= «600» Name= «x_text_xml» Text= «»
HorizontalAlignment= «Left» VerticalAlignment= «Top» Width= «463» />
</ScrollViewer>
<Button Canvas.Left= «503» Canvas.Top= «6» Content= «Télécharger XML»
Height= «34» Name= «x_btn_xml» Width= «189» Click= «x_btn_xml_Click»/>
</Canvas>
</Border>
Le TextBlock x_text_xml reçoit le contenu du fichier XML téléchargé. Ici le flux reçu
sera un fichier XML (sri_xml.Stream) et devra être traité comme tel. Pour cela on
instancie un objet XmlReader par la méthode XmlReader.Create. On lit le contenu
du fichier XML par la méthode Read. A noter qu’il faut ajouter une référence à
l’espace de noms System.Xml pour l’utilisation de la classe XmlReader.
private void x_btn_xml_Click(object sender, RoutedEventArgs e) {
WebClient wc_xml = new WebClient();
wc_xml.OpenReadCompleted +=
new OpenReadCompletedEventHandler(wc_xml_OpenReadCompleted);
wc_xml.OpenReadAsync(new Uri(«contenu/ressource_web.zip», UriKind.
Relative));
}
private void wc_xml_OpenReadCompleted(object sender,
OpenReadCompletedEventArgs e) {
if (e.Error == null) {
StreamResourceInfo sri = new StreamResourceInfo(e.Result, null);
StreamResourceInfo sri_xml =
Application.GetResourceStream(sri,
new Uri(«ressource_web/questionnaire.xml», UriKind.Relative));
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = false;
XmlReader reader_xml = XmlReader.Create(sri_xml.Stream, settings);
while (reader_xml.Read()) {
x_text_xml.Text += reader_xml.Value;
}
reader_xml.Close();
} else {
186 Développez des applications Internet avec Silverlight 5
x_text_xml.Text += «erreur:» + RC;
x_text_xml.Text += e.Error.Message;
}
}
La figure 6.16 illustre le résultat obtenu après le téléchargement du fichier
questionnaire.xml.
FIGURE 6.16

5 - Les bibliothèques de classes

Dans un projet important, il existe toujours des contrôles et des ressources qui
seront réutilisés de nombreuses fois par divers autres contrôles et projets. Pour
une meilleure visibilité du projet, il faut les intégrer dans des bibliothèques de
classes de façon à les rendre réutilisables.
Le projet DemoAssembly.sln, dans le dossier chapitre06, illustre ce principe de
partitionnement et de réutilisation.
Le projet commence comme toujours par la création d’un projet d’application
Silverlight. Dans notre fichier MainPage.xaml, nous voulons insérer un contrôle
qui aura été créé et compilé dans une bibliothèque de classes. Pour cela, on ajoute
à la solution un nouveau projet, en faisant un clic droit sur le nom de la solution et
Copyright 2012 Patrice REY

en choisissant le sous-menu nouveau projet du menu ajouter. Une fenêtre s’ouvre


(figure 6.17) pour les réglages à effectuer: choisissez bibliothèque de classes et
donnez comme nom AfficheurImage. Dans la fenêtre suivante, sélectionnez la
version 5 de Silverlight dans la liste déroulante.
Dans le projet AfficheurImage.sln, on ajoute un dossier contenu avec à l’intérieur
2 images (figure 6.18).
CHAPITRE 6 □ Le modèle d’application Silverlight 187
FIGURE 6.17

FIGURE 6.18

On ajoute aussi un contrôle Silverlight intitulé Afficheur.xaml. Ce contrôle est


composé d’une bordure avec à l’intérieur un contrôle Image qui affiche la ressource
paysage2.jpg.
<UserControl x:Class= «AfficheurImage.Afficheur»
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:d= «http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc= «http://schemas.openxmlformats.org/markup-compatibility/2006»
mc:Ignorable= «d» d:DesignHeight= «300» d:DesignWidth= «400»>
<Grid x:Name= «x_grid_root» Background= «White»>
<Border BorderBrush= «Black» BorderThickness= «2» Name= «border1»>
188 Développez des applications Internet avec Silverlight 5
<Image Height= «263» Name= «image1» Stretch= «Fill» Width= «371»
Source= «/AfficheurImage;component/contenu/paysage2.jpg» />
</Border>
</Grid>
</UserControl>
Une fois terminé, on compile la bibliothèque de classes en appuyant sur F6. La
compilation va générer un fichier AfficheurImage.dll. Les propriétés du projet
(figure 6.19) indique le nom de l’assembly généré et le nom de l’espace de noms
de la bibliothèque de classes.
FIGURE 6.19

1 2

Maintenant, dans notre projet principal, nous voulons utiliser le contrôle Afficheur
et les ressources images qui sont contenus dans la bibliothèque de classes.
Pour pouvoir utiliser la bibliothèque de classes (figure 6.20), il faut ajouter une
référence à cette bibliothèque pour la rendre accessible. En faisant un clic droit sur
le dossier références du projet DemoAssembly.sln, choisir ajouter une référence
(repère n°1). Dans la fenêtre qui s’affiche, dans l’onglet projets, sélectionnez
AfficheurImage, puis OK (repère n°2).
FIGURE 6.20

2
Copyright 2012 Patrice REY

1
CHAPITRE 6 □ Le modèle d’application Silverlight 189

Dans MainPage.xaml, pour ajouter une instance du contrôle AfficheurImage,


on ajoute un attribut xmlns à l’UserControl pour référencer l’espace de noms
(xmlns:visuel = «clr-namespace:AfficheurImage;assembly=AfficheurImage»). Cet
espace que l’on intitule <visuel> peut être ensuite appelé pour créer une instance
du contrôle (<visuel:Afficheur Grid.Column= «0»> </visuel:Afficheur>).
Dans la deuxième colonne, on positionne un contrôle Image dont la propriété
Source pointe sur une image contenue dans la bibliothèque de classes. Comme on
l’a déjà vu, la propriété Source s’écrira sur le modèle /nom_assembly;component/
nom_ressource. Ici on aura donc Source = «/AfficheurImage;component/contenu/
point_d_interrogation.png».
<UserControl x:Class=»DemoAssembly.MainPage»
xmlns=»http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x=»http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:d=»http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc=»http://schemas.openxmlformats.org/markup-compatibility/2006»
xmlns:visuel=»clr-namespace:AfficheurImage;assembly=AfficheurImage»
mc:Ignorable=»d» d:DesignHeight=»300» d:DesignWidth=»800»>
<Grid x:Name=»x_grid_root» Background=»WhiteSmoke»>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=»400» />
<ColumnDefinition Width=»400» />
</Grid.ColumnDefinitions>
<visuel:Afficheur Grid.Column=»0»></visuel:Afficheur>
<Image Grid.Column=»1» Name=»image1» Stretch=»Fill» Width=»200»
Height=»250»
Source=»/AfficheurImage;component/contenu/point_d_interrogation.png»/>
</Grid>
</UserControl>
La figure 6.21 montre le résultat obtenu: côté gauche, nous avons bien une instance
du contrôle, et côté droit, nous avons bien une image dont la ressource est dans la
bibliothèque de classes.
FIGURE 6.21
CHAPITRE 7 DANS CE CHAPITRE
• Le chargement des

La navigation
contrôles
• Naviguer d’une page à
l’autre
• La boite de dialogue
personnalisée
• La classe Frame
• Le mapping d’URI
• L’interface
INavigationContent
Loader

Nous avons vu jusqu’à présent une grande variété


de contrôles. Il nous manque encore ceux qui
permettent d’effectuer la transition d’une page à
l’autre.
Il existe deux options possibles pour le passage
d’une page à l’autre.
La première option qui consiste à charger des
contrôles en fonction des actions de l’utilisateur.
C’est le principe du chargement à la demande (dit
aussi à la volée).
La deuxième option qui consiste à utiliser le
système de navigation de Silverlight. Celui-ci passe
par l’utilisation des contrôles spécialisés Page et
Frame. L’idée de base de cette option est qu’un
simple conteneur serve pour délivrer du contenu,
dont l’accès se fait par un URI.
Dans ce chapitre, vous apprendrez les bases pour
pouvoir naviguer d’une page à l’autre. Vous ferez
un détour pour voir la conception des boites de
dialogues personnalisées (ce sont les boites dites
«modales»). Vous terminerez par l’utilisation des
contrôles Page et Frame du système de navigation
de Silverlight.
192 Développez des applications Internet avec Silverlight 5
Le chargement des contrôles et le passage d’une page à l’autre, sont des pratiques
courantes utilisées pour la navigation dans les applications.

1 - Le chargement des contrôles

De nombreuses applications Silverlight se composent d’une simple page centrale


qui sert de fenêtre principale pour tout le déroulement de l’application.

1.1 - Les contrôles embarqués

La façon de faire la plus courante est de charger les contrôles en fonction des
actions de l’utilisateur. Très souvent, la page centrale est composée d’un Grid avec
des lignes et des colonnes. Une des cellules possède un contrôle du style ListBox,
et l’utilisateur, en cliquant sur une des rubriques, déclenche le chargement d’un
contrôle spécifique qui s’affiche dans une autre cellule. Les contrôles sont dits
embarqués, et sont générés à la demande.
Le projet DemoControleEmbarque.sln, dans le dossier chapitre07, illustre ce type
de pratique, en affichant un ListBox avec des rubriques indiquant un type de
contrôle, et en générant le contrôle à la volée pour l’afficher dans une cellule.
Le dossier contenu se compose des ressources utilisées par les contrôles.
AfficheurImage.xaml et TexteLivre.xaml sont deux contrôles personnalisés de
type UserControl: le premier permet de visualiser trois images différentes, et le
deuxième permet de lire un texte de livre (figure 7.1).
Le fichier MainPage.xaml représente le contrôle principal pour toute l’application.
Il est composé d’un Grid avec 2 lignes et 1 colonne. Dans la première ligne,
on positionne un ListBox x_listbox doté d’un gestionnaire d’événement
SelectionChanged. Dans la deuxième ligne, on positionne un conteneur Border
x_border_contenu à l’intérieur duquel est ajouté un TextBlock.
<Grid x:Name= «x_grid_root» Background= «WhiteSmoke»>
<Grid.RowDefinitions>
<RowDefinition Height= «100» />
Copyright 2012 Patrice REY

<RowDefinition Height= «600» />


</Grid.RowDefinitions>
<ListBox Name= «x_listbox» Grid.Row= «0»
SelectionChanged= «x_listbox_SelectionChanged»/>
<Border Grid.Row= «1» BorderBrush= «SlateGray» BorderThickness= «1»
Name= «x_border_contenu» Background= «WhiteSmoke»>
<TextBlock Grid.Row= «1» Height= «74» HorizontalAlignment= «Center»
Name= «textBlock1»
Text= «cliquez sur une des rubriques au-dessus pour afficher son contenu»
CHAPITRE 7 □ La navigation 193
FIGURE 7.1

les ressources

le contrôle AfficheurImage

le contrôle TexteLivre

VerticalAlignment= «Center» Width= «180» FontFamily= «Verdana»


FontSize= «14» TextAlignment= «Center» TextWrapping= «Wrap» />
</Border>
</Grid>
Le contrôle AfficheurImage.xaml est un contrôle qui permet de visualiser trois
images différentes par l’intermédiaire des boutons précédent et suivant (x_btn_
precedent et x_btn_suivant).
<Canvas x:Name= «x_cnv_root» Background= «White» Height= «532»
HorizontalAlignment= «Center» VerticalAlignment= «Top»
Width= «600»>
<Rectangle Canvas.Left= «5» Canvas.Top= «5» Height= «520» Name= «rectangle1»
Stroke= «Black» StrokeThickness= «1» Width= «588» />
<Image Canvas.Left= «12» Canvas.Top= «59» Height= «398» Name= «x_img»
Stretch= «Fill» Width= «576»
Source= «/DemoControleEmbarque;component/contenu/meuble_peint_1.jpg» />
<TextBlock Canvas.Left= «12» Canvas.Top= «12» Height= «32» Name= «textBlock1»
Text= «Visualiser des images» Width= «576»
FontFamily= «Comic Sans MS» FontSize= «18» TextAlignment= «Center» />
<Button Canvas.Left= «177» Canvas.Top= «479» Content= «Précédent»
Height= «34»
Name= «x_btn_precedent» Width= «111» Click= «x_btn_precedent_Click»
Cursor= «Hand» />
<Button Canvas.Left= «333» Canvas.Top= «479» Content= «Suivant» Height= «34»
Name= «x_btn_suivant» Width= «111» Click= «x_btn_suivant_Click»
194 Développez des applications Internet avec Silverlight 5
Cursor= «Hand» />
</Canvas>
La variable m_numero_encours stocke le numéro de l’image en cours de
visualisation, et la constante c_nbre_img représente le nombre d’images à
visualiser.
Le visuel des boutons est géré par la méthode MettreAJourVisuelBouton. Et la
méthode AfficherUneImage se charge d’afficher l’image en cours dans le contrôle
Image x_img.
//bouton precedent clic
private void x_btn_precedent_Click(object sender, RoutedEventArgs e) {
m_numero_encours--;
MettreAJourVisuelBouton();
AfficherUneImage();
}
//bouton suivant clic
private void x_btn_suivant_Click(object sender, RoutedEventArgs e) {
m_numero_encours++;
MettreAJourVisuelBouton();
AfficherUneImage();
}
//mise a jour des visuels des boutons
private void MettreAJourVisuelBouton() {
if (m_numero_encours > 1 && m_numero_encours < c_nbre_img) {
x_btn_precedent.IsEnabled = true;
x_btn_suivant.IsEnabled = true;
}
if (m_numero_encours == 1) {
x_btn_precedent.IsEnabled = false;
x_btn_suivant.IsEnabled = true;
}
if (m_numero_encours == c_nbre_img) {
x_btn_precedent.IsEnabled = true;
x_btn_suivant.IsEnabled = false;
}
}
//remplissage du contenu de l’image
private void AfficherUneImage() {
BitmapImage bitmap = new BitmapImage();
Copyright 2012 Patrice REY

bitmap.UriSource =
new Uri(«contenu/meuble_peint_» + m_numero_encours.ToString() + «.jpg»,
UriKind.Relative);
x_img.Source = bitmap;
}
Le contrôle TexteLivre.xaml est un contrôle qui permet d’afficher un extrait de
chapitre du livre du Comte de Monte-Cristo. Le texte et les images sont insérés
CHAPITRE 7 □ La navigation 195

dans un RichTextBox avec un défilement vertical activé.


<Canvas x:Name= «x_cnv_root» Background= «White» Height= «532»
HorizontalAlignment= «Center» VerticalAlignment= «Top»
Width= «600»>
<Rectangle Canvas.Left= «5» Canvas.Top= «5» Height= «520» Name= «rectangle1»
Stroke= «Black» StrokeThickness= «1» Width= «588» />
<TextBlock Canvas.Left= «12» Canvas.Top= «12» Height= «32»
Name= «textBlock1»
Text= «Lire un chapitre de livre» Width= «576»
FontFamily= «Comic Sans MS» FontSize= «18» TextAlignment= «Center» />
<RichTextBox Canvas.Left= «64» Canvas.Top= «65» Name= «x_richtextbox»
Height= «350» Width= «474»
VerticalScrollBarVisibility= «Visible» IsReadOnly= «True»>
...
</RichTextBox>
</Canvas>
Quand l’UserControl MainPage.xaml est chargé, x_listbox reçoit des rubriques sous
forme de chaînes de caractères. Ces chaînes représentent le nom des contrôles
(AfficheurImage et TexteLivre).
Lors de l’émission de l’événement SelectionChanged de x_listbox, la méthode
statique CreateInstance génère une instance du contrôle choisi. Puis elle est
affectée à la propriété Child du Border x_border_contenu.
//evenement loaded de usercontrol
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
x_listbox.Items.Add(«AfficheurImage»);
x_listbox.Items.Add(«TexteLivre»);
}
//evenement selection sur listbox
private void x_listbox_SelectionChanged(object sender,
SelectionChangedEventArgs e) {
string uc_nom = x_listbox.SelectedItem.ToString();
Type type = this.GetType();
Assembly assembly = type.Assembly;
UserControl nouvel_uc =
(UserControl)assembly.CreateInstance(type.Namespace + «.» + uc_nom);
x_border_contenu.Child = nouvel_uc;
}
La figure 7.2 visualise le résultat obtenu. Un clic sur une des rubriques du ListBox
génère le contrôle à la volée, et l’affiche dans le contrôle Border du dessous. Quand
la première rubrique est générée, l’utilisation des boutons permet la visualisation
des trois images comme dans un diaporama. Quand la deuxième rubrique est
générée, le RichTextBox affiche un contenu texte enrichi.
196 Développez des applications Internet avec Silverlight 5
FIGURE 7.2

Copyright 2012 Patrice REY

1.2 - Naviguer d’une page à l’autre

L’autre façon de naviguer consiste à partir d’une page pour aller vers une autre page,
et ainsi de suite. Les pages sont généralement des contrôles de type UserControl,
chargées une fois et réutilisables grâce à une mise en cache dans une structure de
CHAPITRE 7 □ La navigation 197

type dictionnaire (association d’une clé avec une valeur).


Le projet NavigationSimulee.sln, dans le dossier chapitre07, illustre ce type de
façon de naviguer, en chargeant la première page et permettant de naviguer vers
d’autres pages.
La page chargée en premier sera ici MainPage.xaml. De cette page, on pourra
aller vers la page Rectangle.xaml ou la page Ellipse.xaml. Un bouton sommaire
permettra de revenir à la page MainPage.xaml (figure 7.3).
FIGURE 7.3

les différentes pages

Le fichier MainPage.xaml constitue la page de départ. A l’intérieur d’un


ScrollViewer, on positionne un Canvas. A l’intérieur du Canvas, on ajoute le texte
et deux liens, x_lien_1 et x_lien_2, de type HyperlinkButton. Le gestionnaire de
l’événement Click sur les liens permettra le passage d’une page à l’autre.
<Grid x:Name= «x_grid_root» Background= «White»>
<ScrollViewer Name= «x_scroll» Background= «DarkGray»>
<Canvas Height= «391» Name= «canvas1» Width= «369»
HorizontalAlignment= «Left» VerticalAlignment= «Top» Background= «White»>
<TextBlock Canvas.Left= «9» Canvas.Top= «11» Height= «28»
Name= «textBlock1»
Text= «Les figures géométriques:» Width= «347»
FontFamily= «Comic Sans MS» FontSize= «16» />
<HyperlinkButton Name= «x_lien_1» Content= «1 - le rectangle (et le carré)»
Canvas.Left= «26» Canvas.Top= «45»
Foreground= «SlateGray» FontFamily= «Verdana» FontSize= «14»
Click= «x_lien_Click»></HyperlinkButton>
<HyperlinkButton Name= «x_lien_2» Content= «2 - l’ellipse (et le cercle)»
Canvas.Left= «26» Canvas.Top= «66»
198 Développez des applications Internet avec Silverlight 5
Foreground= «SlateGray» FontFamily= «Verdana» FontSize= «14»
Click= «x_lien_Click»></HyperlinkButton>
</Canvas>
</ScrollViewer>
</Grid>
Les contrôles Rectangle.xaml et Ellipse.xaml sont composés eux aussi de
textes, d’images, ainsi que d’un TextBox dans lequel on pourra consigner des
commentaires, et d’un bouton qui permettra le retour à la page de départ. Le code
XAML pour Rectangle.xaml est le suivant:
<Grid x:Name= «x_grid_root» Background= «White»>
<ScrollViewer Name= «x_scroll» Background= «DarkGray»>
<Canvas Height= «391» Name= «canvas1» Width= «369» HorizontalAlignment=
«Left»
VerticalAlignment= «Top» Background= «White»>
<Rectangle Canvas.Left= «2» Canvas.Top= «6» Height= «33» Name= «rectangle1»
Stroke= «Black» StrokeThickness= «1» Width= «363» />
<TextBlock Canvas.Left= «9» Canvas.Top= «11» Height= «28» Name=
«textBlock1»
Text= «Le rectangle:» Width= «347» FontFamily= «Comic Sans MS»
FontSize= «16» />
<Rectangle Canvas.Left= «148» Canvas.Top= «62» Height= «27»
Name= «rectangle2» Stroke= «Black» StrokeThickness= «1»
Width= «89» Fill= «Gainsboro» />
<TextBlock Canvas.Left= «9» Canvas.Top= «45» Height= «73» Name=
«textBlock2»
Text= «un rectangle possède une longueur et une largeur» Width= «118»
FontFamily= «Verdana» FontSize= «12» TextWrapping= «Wrap» />
<TextBlock Canvas.Left= «9» Canvas.Top= «112» FontFamily= «Verdana»
FontSize= «12» Height= «73» Name= «textBlock3»
Text= «quand la longueur est égale à la largeur, on est en présence d’un
carré» TextWrapping= «Wrap» Width= «118» />
<Rectangle Canvas.Left= «148» Canvas.Top= «137» Fill= «Gainsboro»
Height= «30» Name= «rectangle3» Stroke= «Black»
StrokeThickness= «1» Width= «30» />
<Button Canvas.Left= «265» Canvas.Top= «10» Content= «Sommaire» Height=
«23»
Name= «x_btn_sommaire» Width= «95» Click= «x_btn_sommaire_Click» />
<TextBox Canvas.Left= «9» Canvas.Top= «215» Height= «49» Name= «textBox1»
Copyright 2012 Patrice REY

Width= «347» Background= «WhiteSmoke» />


<TextBlock Canvas.Left= «9» Canvas.Top= «197» Height= «23» Name=
«textBlock4»
Text= «Consigner votre avis:» />
</Canvas>
</ScrollViewer>
</Grid>
Maintenant, voyons le mécanisme à mettre en place pour cette navigation de
CHAPITRE 7 □ La navigation 199

page en page. Dans le fichier App.xaml.cs, on définit un champ m_rootVisual de


type Grid qui servira de conteneur général pour l’application. L’énumération Page
énumère les différentes pages sous la forme de leur nom de contrôle XAML. Le
dictionnaire d_cache_page, de type Dictionary, permettra de stocker les pages
sous la forme <clé,valeur>, avec la clé de type App.Page (énumération) et avec la
valeur de type UserControl.
//le conteneur grid qui heberge vos pages
private Grid m_rootVisual = new Grid();
//enumeration
public enum Page { MainPage, Rectangle, Ellipse };
//
private static Dictionary<Page, UserControl> d_cache_page =
new Dictionary<Page, UserControl>();
La propriété RootVisual de App reçoit un objet qui dérive de UIElement (ici c’est
le Grid m_rootVisual). La méthode statique NaviguerVersPage permet de charger
une autre page. La variable v_app_encours récupère l’application courante. Si
le dictionnaire ne contient pas déjà la page recherchée, alors on instancie une
nouvelle page et on l’ajoute au dictionnaire. Le conteneur m_rootVisual est vidé
de son contenu par la méthode Clear appliquée à sa propriété Children. Puis on lui
ajoute la page cherchée par la méthode Add.
//evenement startup
private void Application_Startup(object sender, StartupEventArgs e) {
//chargement de la premiere page
this.RootVisual = m_rootVisual;
NaviguerVersPage(Page.MainPage);
}
//naviguer vers une page
public static void NaviguerVersPage(Page une_page) {
//on recupere l’objet application courant et
//on le cast en App (qui derive de Application)
App v_app_encours = (App)Application.Current;
//on verifie si la page a deja ete cree avant
if (!d_cache_page.ContainsKey(une_page)) {
//on cree la premiere instance de cet page et
//on la met en cache pour une utilisation future
Type type = v_app_encours.GetType();
Assembly assembly = type.Assembly;
d_cache_page[une_page] =
(UserControl)assembly.CreateInstance(
type.Namespace + «.» + une_page.ToString());
}
// Change the currently displayed page.
v_app_encours.m_rootVisual.Children.Clear();
v_app_encours.m_rootVisual.Children.Add(d_cache_page[une_page]);}
200 Développez des applications Internet avec Silverlight 5
La figure 7.4 illustre le résultat obtenu de ce mécanisme. Un clic sur le lien x_lien_1
permet de charger la page Rectangle.xaml, et un clic sur le lien x_lien_2 permet
de charger la page Ellipse.xaml. Le chargement d’une page s’effectue alors par la
méthode statique App.NaviguerVersPage. Il en est de même pour le bouton qui
permet de revenir au sommaire.
//clic sur un hyperlinkbutton
private void x_lien_Click(object sender, RoutedEventArgs e) {
HyperlinkButton lien = sender as HyperlinkButton;
switch (lien.Name) {
case «x_lien_1»:
App.NaviguerVersPage(App.Page.Rectangle);
break;
case «x_lien_2»:
App.NaviguerVersPage(App.Page.Ellipse);
break;
}
}

FIGURE 7.4

Copyright 2012 Patrice REY

Comme les pages sont générées une seule fois puis mises dans le dictionnaire, si on
inscrit dans le TextBox un commentaire, celui-ci reste visible quand on reviendra
sur cette même page. La figure 7.5 visualise le résultat obtenu avec une navigation
CHAPITRE 7 □ La navigation 201

dans les deux sens.


FIGURE 7.5

2 - La boite de dialogue personnalisée

La classe ChildWindow (figure 7.6) permet la réalisation de boites de dialogue


personnalisées. L’assistant fenêtre enfant silverlight de Visual Studio crée une
classe héritée de ChildWindow qui dispose d’un fichier de description XAML.
FIGURE 7.6
202 Développez des applications Internet avec Silverlight 5
Cette classe hérite de ContentControl. Une fenêtre ChildWindow s’ouvre au moyen
de sa méthode Show, avec un effet d’animation de taille. Le mode d’ouverture est
modal, ce qui empêche l’utilisateur d’interagir avec les autres éléments visuels
de l’application, qui sont recouverts d’un voile semi-opaque. Généralement,
l’utilisateur saisit les informations dans la boite de dialogue et valide la saisie par un
bouton OK, ou l’annule au moyen d’un bouton Annuler. Ces boutons provoquent la
fermeture de la fenêtre.
Le projet BoiteDialogue.sln, dans le dossier chapitre07, illustre la réalisation et le
fonctionnement de la boite de dialogue de type ChildWindow.
La figure 7.7 montre les étapes pour créer une boite de dialogue intitulée
InfosUtilisateur.xaml.
FIGURE 7.7

1
2

L’assistant de Visual Studio crée automatiquement le code des boutons


(implémentation des gestionnaires des événements). Les gestionnaires de
l’événement Click de ces boutons positionne la propriété booléenne DialogResult
de la fenêtre. Cette propriété est nullable. Sa valeur sera lue par le code appelant
après la fermeture. La fermeture est déclenchée par la méthode Close, ou par
Copyright 2012 Patrice REY

l’affectation d’une valeur à DialogResult. Un effet d’animation de la taille, en


réduction, accompagne la fermeture. Les événements Closing et Closed sont
alors émis. L’événement Closing permet d’empêcher le processus de fermeture
au moyen de la propriété Cancel de l’argument CancelEventArgs du gestionnaire
d’événement.
La figure 7.8 montre la boite de dialogue de départ (haut de la figure 7.8) et la boite
CHAPITRE 7 □ La navigation 203

de dialogue personnalisée en fonction de ce que l’on a besoin (bas de la figure 7.8).


FIGURE 7.8

Nous souhaitons ici récupérer un identifiant composé du nom et du prénom de


la personne. Il faut donc ajouter une propriété Identifiant à la boite de dialogue
InfosUtilisateur.xaml.
public partial class InfosUtilisateur : ChildWindow {
//constructeur
public InfosUtilisateur() {
InitializeComponent();
}
private void OKButton_Click(object sender, RoutedEventArgs e) {
this.DialogResult = true;
}
private void CancelButton_Click(object sender, RoutedEventArgs e) {
this.DialogResult = false;
}
//propriete identifiant generee
public string Identifiant {
get { return txtFirstName.Text + « « + txtLastName.Text; }
}
}
204 Développez des applications Internet avec Silverlight 5
Dans MainPage.xaml.cs, on instancie un objet boite_dialogue de type
InfosUtilisateur. Et sur le clic du bouton, on affiche la boite de dialogue modale
par la méthode Show.
//
private InfosUtilisateur boite_dialogue = new InfosUtilisateur();
//clic btn x_btn_ident
private void x_btn_ident_Click(object sender, RoutedEventArgs e) {
boite_dialogue.Show();
}
Pour récupérer la valeur de la propriété Identifiant de la boite de dialogue, on
ajoute un gestionnaire d’événement Closed. Si la propriété DialogResult est à true,
alors on peut afficher l’identifiant dans la fenêtre parent.
public MainPage() {
InitializeComponent();
boite_dialogue.Closed += new EventHandler(boite_dialogue_Closed);
}
//evenement closed sur boite dialogue
private void boite_dialogue_Closed(object sender, EventArgs e) {
if (boite_dialogue.DialogResult == true) {
lblInfo.Text = «Bienvenue dans l’application, « +
boite_dialogue.Identifiant + «.»;
}

FIGURE 7.9

Copyright 2012 Patrice REY


CHAPITRE 7 □ La navigation 205

La figure 7.9 visualise le cheminement visuel des étapes concernant l’affichage,


puis la fermeture de la boite de dialogue.

3 - Le contrôle Frame

Silverlight intègre un mécanisme permettant de naviguer parmi les pages d’une


application. La navigation peut être contrôlée par le navigateur, chaque page
disposant d’un URI pouvant être utilisé comme adresse. La navigation peut être
aussi pilotée par des contrôles Silverlight et par programmation.

3.1 - Utilisation d’un Frame

Cette navigation parmi les pages est prise en compte dans l’historique du navigateur.
De plus, la navigation ne provoque pas le rechargement de l’application.
Les pages sont affichées au sein d’un conteneur visuel de la classe Frame. Sa
propriété Source indique l’URI de la page à charger. Sa méthode Navigate permet
de charger une page par programmation.
A l’exécution d’une page HTML comprenant une application Silverlight affichant
une page Silverlight dans un Frame, la barre d’adresse du navigateur affiche l’URI
de la page Silverlight. La syntaxe de l’URL de la page HTML est complétée par le
symbole # suivi de l’URI de la page Silverlight.
La classe Frame hérite de ContentControl et dispose de membres permettant
de contrôler la navigation. La méthode Navigate instancie une page de façon
asynchrone. Elle émet les événements suivants pendant le processus de
chargement:
• Navigating : le processus est enclenché; il est possible d’empêcher le
processus de navigation au moyen de la propriété Cancel de l’argument
NavigatingCancelEventArgs du gestionnaire d’événement.
• Navigated : la page a été chargée et la propriété Content est mise à jour.
La méthode StopLoading permet d’arrêter un chargement. L’événement
NavigationStopped est alors émis. L’URI de la page de destination peut être lu dans
la propriété Source. L’URI courant peut être lu dans la propriété CurrentSource.
Les méthodes GoForward et GoBack permettent de naviguer dans l’historique. Les
propriétés CanGoBack et CanGoForward indiquent si cela est possible.
Le projet FrameV1.sln, dans le dossier chapitre07, illustre l’utilisation d’un Frame
pour la visualisation de pages. Pour ajouter un Frame en XAML, il faut ajouter
une référence à un espace de noms: xmlns:naviguer = «clr-namespace:System.
206 Développez des applications Internet avec Silverlight 5
Windows.Controls; assembly = System.Windows.Controls.Navigation».
On positionne comme page de départ l’UserControl Page1.xaml. Le bouton x_
btn_navig permet de charger dans le Frame x_frame la page Page2.xaml par la
méthode Navigate.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Height= «230»>
<Border BorderThickness= «1» CornerRadius= «10» BorderBrush= «Black»
Canvas.Left= «5» Canvas.Top= «6» Width= «390» Height= «160»>
<naviguer:Frame Canvas.Left= «12» Canvas.Top= «12» Height= «150»
Width= «376» Name= «x_frame»>
<naviguer:Frame.Content>
<visuel:Page1></visuel:Page1>
</naviguer:Frame.Content>
</naviguer:Frame>
</Border>
<Button Canvas.Left= «93» Canvas.Top= «176» Content= «naviguer vers la page
suivante» Height= «37» Name= «x_btn_navig» Width= «212»
Click= «x_btn_navig_Click»/>
</Canvas>
//clic bouton
private void x_btn_navig_Click(object sender, RoutedEventArgs e) {
x_frame.Navigate(new Uri(«/Page2.xaml», UriKind.Relative));
}
La figure 7.10 montre le résultat obtenu. Au lancement, le Frame charge la page
Page1.xaml. Un clic sur le bouton et le Frame charge la page Page2.xaml. Dans ce
dernier cas, on voit bien que le titre de la page HTML est /Page2.xaml. Et si dans
la barre d’adresse du navigateur on tape directement l’URL http://localhost:2115/
FrameV1TestPage.html#/Page2.xaml, on accède directement à la page dans
l’application. Cela peut poser certains problèmes de sécurité.
FIGURE 7.10

Copyright 2012 Patrice REY

Si on appuie sur le bouton de retour en arrière de page du navigateur, une exception


non gérée par Silverlight est levée (figure 7.11). Ce problème peut être réglé par
l’utilisation du mapping d’URI.
CHAPITRE 7 □ La navigation 207
FIGURE 7.11

3.2 - Le mapping d’URI

Le mapping d’URI consiste à associer un URI virtuel à un URI original, au moyen d’un
objet UriMapping. La propriété Uri de cet objet définit l’URI virtuel. Sa propriété
MappedUri indique l’URI original. Les différents objets UriMapping doivent être
regroupés dans la collection UriMappings d’un objet UriMapper. L’ordre dans
lequel les éléments UriMapping sont définis est important. En effet, lorsqu’un URI
est traité par un UriMapper, le premier UriMapping de la liste correspondant à la
syntaxe de l’URI est appliqué.
Le projet FrameV2.sln, dans le dossier chapitre07, illustre le principe du mapping
d’URI.
Dans le fichier App.xaml, on ajoute dans les ressources un objet UriMapper avec
une clé x:Key = «k_uri_mapper». Puis on insère les objets UriMapping dans la
collection UriMappings de l’objet UriMapper. L’objet UriMapping qui a sa propriété
Uri vide, correspond à la page de départ, donc avec une propriété MappedUri =
«/Page1.xaml». Le deuxième objet UriMapping a sa propriété Uri fixée à vers_
page2, et sa propriété MappedUri fixée à /Page2.xaml.
<Application
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
x:Class= «FrameV2.App»
xmlns:naviguer= «clr-namespace:System.Windows.Navigation;
assembly=System.Windows.Controls.Navigation»>
<Application.Resources>
<naviguer:UriMapper x:Key= «k_uri_mapper»>
<!-- page de depart: page1.xaml-->
208 Développez des applications Internet avec Silverlight 5
<naviguer:UriMapping Uri= «» MappedUri= «/Page1.xaml» />
<naviguer:UriMapping Uri= «vers_page2» MappedUri= «/Page2.xaml» />
</naviguer:UriMapper>
</Application.Resources>
</Application>
Il ne reste plus qu’à fixer la propriété UriMapper de x_frame à la ressource statique
k_uri_mapper.
<naviguer:Frame Canvas.Left= «12» Canvas.Top= «12» Height= «150» Width= «376»
Name= «x_frame» UriMapper= «{StaticResource k_uri_mapper}»>
<naviguer:Frame.Content>
<visuel:Page1></visuel:Page1>
</naviguer:Frame.Content>
</naviguer:Frame>
Pour naviguer vers une page, comme avec le bouton x_btn_navig, on pourra
utiliser directement l’URI simplifié vers_page2 dans la méthode Navigate.
//clic bouton
private void x_btn_navig_Click(object sender, RoutedEventArgs e) {
//x_frame.Navigate(new Uri(«/Page2.xaml», UriKind.Relative));
x_frame.Navigate(new Uri(«vers_page2», UriKind.Relative));
}
La figure 7.12 visualise le résultat obtenu. Quand on est sur la page Page2.xaml, en
cliquant sur le bouton de retour de page du navigateur, il nous ramène à la page
Page1.xaml (il n’y a plus d’exception levée).
FIGURE 7.12

Copyright 2012 Patrice REY


CHAPITRE 7 □ La navigation 209

4 - Authentification avec INavigationContentLoader

L’interface INavigationContentLoader définit des méthodes pour le chargement


de contenu qui correspond à un URI.
En général, vous n’appellerez pas directement les méthodes de cette interface
à moins que vous n’implémentiez une alternative au système de navigation
Silverlight.
Vous implémentez cette interface pour fournir le chargement du contenu
personnalisé pour le système de navigation Silverlight. Par exemple, vous pouvez
fournir un chargeur de contenu qui permet la navigation vers les pages des
assemblys extraits du serveur à la demande. Vous pouvez également utiliser cette
interface pour implémenter la redirection URI (l’authentification utilisateur). Le
système de navigation Silverlight requiert que la cible de navigation finale soit une
instance d’un objet UserControl.
Pour utiliser une implémentation INavigationContentLoader, créez une
instance et utilisez-la pour définir la propriété Frame.ContentLoader. La classe
PageResourceContentLoader est actuellement la seule implémentation
INavigationContentLoader dans le Framework Silverlight. Une instance de cette
classe est la valeur par défaut de la propriété ContentLoader.
Les classes NavigationService et Frame fournissent une méthode Refresh. Cela
s’avère utile lors de l’utilisation des extensions de navigation qui peuvent remettre
du contenu différent pour le même URI selon les interactions de l’utilisateur
dans une page particulière. Par exemple, cela active certains scénarios de
navigation avec des pages qui requièrent l’authentification utilisateur. Le type
INavigationContentLoader expose les membres suivants:
• la méthode BeginLoad commence le chargement de contenu asynchrone pour
l’URI cible spécifié.
• la méthode CancelLoad essaie d’annuler le chargement du contenu pour
l’opération asynchrone spécifiée.
• la méthode CanLoad obtient une valeur qui indique si l’URI spécifié peut être
chargé.
• la méthode EndLoad exécute l’opération de chargement de contenu asynchrone.
Le projet Authentification.sln, dans le dossier chapitre07, illustre l’utilisation de
l’interface INavigationContentLoader, pour permettre un accès restreint à un
ensemble de pages.
La figure 7.13 visualise le résultat obtenu. Au lancement, la page par défaut
210 Développez des applications Internet avec Silverlight 5
(PageDepart.xaml) est affichée (repère n°1). Deux liens pointent vers des pages qui
nécessitent une authentification (repère n°2). Une fois l’authentification réussie,
toutes les pages protégées sont accessibles (repère n°3).
FIGURE 7.13

L’arborescence des fichiers est visualisée sur la figure 7.14. Les pages avec un accès
protégé (Article1.xaml et Article2.xaml) sont stockées dans un dossier intitulé
FIGURE 7.14

dossier contenant les ressources

dossier contenant les pages


Copyright 2012 Patrice REY

avec un accès protégé

la classe Authentification qui


implémente l’interface
INavigationContentLoader
page par défaut
page pour s’authentifier
CHAPITRE 7 □ La navigation 211

PagesProtegees. La page par défaut est PageDepart.xaml. L’authentification


s’effectue avec la page PageLogin.xaml, avec en complément la classe
Authentification qui implémente l’interface INavigationContentLoader.
Au lancement de l’application, dans le fichier App.xaml.cs, on crée une propriété
statique UtilisateurEstAuthentifie fixée à false.
public partial class App : Application {
//
public static bool UtilisateurEstAuthentifie {
get;
set;
}
//
public App() {
this.Startup += this.Application_Startup;
this.Exit += this.Application_Exit;
this.UnhandledException += this.Application_UnhandledException;
InitializeComponent();
}
private void Application_Startup(object sender, StartupEventArgs e) {
App.UtilisateurEstAuthentifie = false;
this.RootVisual = new MainPage();
}
...}
Un mapping d’URI est positionné dans App.xaml pour faire en sorte que PageDepart.
xaml soit la page affichée par défaut (avec le principe de l’objet UriMapper).
<Application
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
x:Class= «Authentification.App»
xmlns:navigation= «clr-namespace:System.Windows.Navigation;
assembly=System.Windows.Controls.Navigation»>
<Application.Resources>
<navigation:UriMapper x:Key= «k_uri_mapper»>
<navigation:UriMapping Uri= «» MappedUri= «/PageDepart.xaml»/>
</navigation:UriMapper>
</Application.Resources>
</Application>
On définit la classe Authentification qui implémente l’interface
INavigationContentLoader. Cette classe possède des propriétés ajoutées:
PageLoginXaml qui indique le chemin de la page utilisée pour l’authentification,
et DossierPagesProteges qui indique le nom du dossier dans lequel se trouve les
pages protégées.
public class Authentification: INavigationContentLoader {
212 Développez des applications Internet avec Silverlight 5
//
public string PageLoginXaml {
get;
set;
}
public string DossierPagesProteges {
get;
set;
}
private PageResourceContentLoader chargeur =
new PageResourceContentLoader();
//
public IAsyncResult BeginLoad(Uri targetUri, Uri currentUri,
AsyncCallback userCallback, object asyncState) {
if (!App.UtilisateurEstAuthentifie) {
if ((System.IO.Path.GetDirectoryName(targetUri.ToString()).Trim(‘\\’) ==
DossierPagesProteges) &&
(targetUri.ToString() != PageLoginXaml)) {
targetUri = new Uri(PageLoginXaml, UriKind.Relative);
}
}
return chargeur.BeginLoad(targetUri, currentUri, userCallback,
asyncState);
}
//
public bool CanLoad(Uri targetUri, Uri currentUri) {
return chargeur.CanLoad(targetUri, currentUri);
}
//
public void CancelLoad(IAsyncResult asyncResult) {
chargeur.CancelLoad(asyncResult);
}
//
public LoadResult EndLoad(IAsyncResult asyncResult) {
return chargeur.EndLoad(asyncResult);
}
}//end class
Dans MainPage.xaml, pour l’objet Frame x_frame, on affecte à sa propriété
ContentLoader un objet Authentification dont le chemin de la cible est PageLogin.
Copyright 2012 Patrice REY

xaml, et le chemin du contenu est le dossier PagesProtegees.


<UserControl x:Class= «Authentification.MainPage»
...
xmlns:mc= «http://schemas.openxmlformats.org/markup-compatibility/2006»
xmlns:navigation= «clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Navigation»
xmlns:visuel= «clr-namespace:Authentification»
mc:Ignorable= «d» d:DesignHeight= «300» d:DesignWidth= «700»>
CHAPITRE 7 □ La navigation 213

...
<navigation:Frame x:Name= «x_frame»
UriMapper= «{StaticResource k_uri_mapper}»>
<navigation:Frame.ContentLoader>
<visuel:Authentification PageLoginXaml= «/PageLogin.xaml»
DossierPagesProteges= «PagesProtegees»></visuel:Authentification>
</navigation:Frame.ContentLoader>
</navigation:Frame>
...
Le gestionnaire de l’événement Click du bouton x_btn_connexion (dans le fichier
PageLogin.xaml) gère l’authentification. Ici on choisit un accès par un mot de passe
codé en dur (1234 et 5678). En réel, il faudrait ajouter une connexion au serveur
pour les vérifications habituelles.
public partial class PageLogin : Page {
public PageLogin() {
InitializeComponent();
}
private void x_btn_connexion_Click(object sender, RoutedEventArgs e) {
//en reel, cela passe par un envoi au serveur pour verifier
if (x_text_util.Text == «1234» && x_text_passe.Text == «5678») {
App.UtilisateurEstAuthentifie = true;
this.NavigationService.Refresh();
}
}
}
CHAPITRE 8 DANS CE CHAPITRE
• La classe Shape
• Rectangle, Ellipse, Line,
Le graphisme Polyline, Polygon
• Path, PathGeometry,
PathFigure
• RectangleGeometry,
EllipseGeometry,
LineGeometry,
GeometryGroup
• PathSegment,
LineSegment,
ArcSegment,
Silverlight expose des fonctionnalités graphiques BezierSegment,
QuadraticBezierSegment
vectorielles évoluées au moyen d’un modèle objet • Le clipping
riche et cohérent. • TranslateTransform,
ScaleTransform,
Dans ce chapitre, vous allez commencer par RotateTransform,
dessiner les nombreuses formes géométriques de SkewTransform,
base comme le rectangle, l’ellipse et la ligne. MatrixTransform
• TransformGroup,
Des objets plus complexes vous permettront CompositeTransform
d’assembler des formes géométriques pour en • La classe PlaneProjection
faire des géométries évoluées, réutilisables dans
des contextes de données différents.
Le segment, la ligne, l’arc et les courbes de Bézier
cubiques et quadratiques, permettent de composer
de nombreuses figures géométriques.
Vous verrez comment ces figures géométriques
peuvent être employées pour réaliser des découpes
(le clipping).
La dernière partie du chapitre est consacrée aux
transformations (translation, mise à l’échelle,
rotation, inclinaison), et à la transformation
matricielle. Il sera abordé aussi le système de
projection permettant d’appliquer à un élément des
transformations 3D. L’objectif des projections est
de rendre les interfaces utilisateur plus intuitives.
216 Développez des applications Internet avec Silverlight 5

Silverlight expose des fonctionnalités graphiques vectorielles évoluées au moyen


d’un modèle objet riche et cohérent.

1 - Les formes géométriques basiques

Le dessin de figures géométriques basiques est confié aux classes héritées de


Shape, qui exploitent les outils suivants:
• les géométries qui déterminent la forme des figures.
• les pinceaux qui déterminent la couleur et le motif du fond et des traits d’une
figure; les différents types de motif sont représentés par des classes spécifiques
héritées de la classe Brush.
Tous ces objets peuvent être stockés sous la forme de ressources et être ainsi
réutilisés en XAML au sein de plusieurs éléments.

1.1 - La classe Shape

Une forme est un élément qui affiche une figure géométrique. Sa classe hérite
de la classe abstraite Shape, et donc de FrameworkElement et de UIElement.
Ainsi, l’héritage de UIElement fait qu’un objet Shape est interactif (sensibilité aux
périphériques de saisie) et compatible avec le système de disposition.
Les classes qui héritent de Shape (figure 8.1) sont:
• la classe Rectangle qui représente un rectangle avec des dimensions données.
• la classe Ellipse qui représente une ellipse inscrite dans un rectangle fictif.
• la classe Line qui représente une ligne droite reliant deux points donnés.
• la classe Polygon qui représente un polygone spécifié par une suite de points.
• la classe Polyline qui représente une suite de lignes droites connectées.
• la classe Path qui représente une suite de segments droits ou courbes
connectés.
Toutes ces classes héritent de la classe Shape les caractéristiques communes
Copyright 2012 Patrice REY

suivantes:
• la propriété Stroke qui référence le pinceau utilisé pour le dessin du contour de
la forme, ou de la ligne dans le cas de Line, Polyline et Path.
• la propriété Fill qui référence le pinceau utilisé pour le remplissage de la forme
(cas de Rectangle, Ellipse, Polygon et Path).
• d’un ensemble de propriétés préfixées par Stroke, qui permettent de définir
l’apparence du trait; par exemple StrokeThickness indique son épaisseur.
CHAPITRE 8 □ Le graphisme 217

• la propriété Stretch qui indique le type d’étirement appliqué à la figure dans


son conteneur.
Une figure héritée de Shape ne peut s’afficher que si les propriétés Stroke ou Fill
sont spécifiées.
FIGURE 8.1

Le projet DemoFormeGeometrique.sln, dans le dossier chapitre08, illustre


l’utilisation de ces différentes classes dérivées de Shape.

1.2 - La classe Rectangle

La classe Rectangle permet de spécifier ses dimensions au moyen des propriétés


Width (la largeur) et Height (la hauteur), mais n’offre aucune propriété pour
spécifier le position du coin haut gauche du rectangle.
Son positionnement pourra être réalisé au moyen de ses propriétés Margin,
HorizontalAlignment et VerticalAlignment. Si le rectangle est positionné sur
un Canvas, les propriétés attachées Canvas.Left et Canvas.Top indiqueront son
emplacement.
Les coins d’un rectangle peuvent être arrondis en spécifiant le rayon de l’arrondi
dans les propriétés RadiusX et RadiusY.
La figure 8.2 visualise le fichier Rectangle.xaml, dans lequel différentes
configurations de rectangles sont effectuées.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600» Height=
«300»>
<Rectangle Canvas.Left= «12» Canvas.Top= «12» Height= «43» Name= «x_rect1»
218 Développez des applications Internet avec Silverlight 5
Stroke= «Black» StrokeThickness= «1»
Width= «104» Fill= «Silver» />
<Rectangle Canvas.Left= «12» Canvas.Top= «73» Fill= «Silver» Height= «43»
Name= «x_rect2» Stroke= «Black»
StrokeThickness= «2» Width= «104» RadiusX= «20» RadiusY= «20» />
<Rectangle Canvas.Left= «12» Canvas.Top= «136» Fill= «Silver» Height= «43»
Name= «x_rect3» RadiusX= «20» RadiusY= «10»
Stroke= «Black» StrokeThickness= «2» Width= «104» />
<Rectangle Canvas.Left= «12» Canvas.Top= «196» Fill= «Silver» Height= «43»
Name= «x_rect4» RadiusX= «10» RadiusY= «20»
Stroke= «Black» StrokeThickness= «2» Width= «104» />
</Canvas>
FIGURE 8.2

1.3 - La classe Ellipse

La classe Ellipse fonctionne de façon similaire à la classe Rectangle. Elle définit


une ellipse dans un rectangle fictif. Les propriétés Width (la largeur) et Height (la
hauteur) donnent les dimensions du rectangle fictif dans lequel l’ellipse est inscrite.
Le positionnement de l’ellipse pourra être réalisé au moyen de ses propriétés
Copyright 2012 Patrice REY

Margin, HorizontalAlignment et VerticalAlignment. Si l’ellipse est positionnée sur


un Canvas, les propriétés attachées Canvas.Left et Canvas.Top indiqueront son
emplacement.
La figure 8.3 visualise le fichier Ellipse.xaml dans lequel différentes configurations
d’ellipses sont effectuées.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600» Height=
«300»>
CHAPITRE 8 □ Le graphisme 219

<Ellipse Canvas.Left= «12» Canvas.Top= «12» Height= «43» Name= «x_ellipse1»


Stroke= «Black» StrokeThickness= «2»
Width= «104» Fill= «Silver» />
<Ellipse Canvas.Left= «48» Canvas.Top= «72» Fill= «Silver» Height= «60» Name=
«x_rect2» Stroke= «Black»
StrokeThickness= «2» Width= «30» />
<Ellipse Canvas.Left= «39» Canvas.Top= «150» Fill= «Silver» Height= «50»
Name= «x_rect3»
Stroke= «Black» StrokeThickness= «2» Width= «50» />
</Canvas>
FIGURE 8.3

1.4 - La classe Line

La classe Line représente une ligne droite reliant deux point donnés. Ses propriétés
X1 et Y1 spécifient le point d’origine, et ses propriétés X2 et Y2 spécifient le point
de destination. Ces coordonnées sont relatives à la surface du contenu direct de
la figure.
Les propriétés StrokeStartLineCap et StrokeEndLineCap indiquent respectivement
la forme des terminaisons de début et de fin du trait au moyen d’une énumération
PenLineCap:
• Flat définit aucune terminaison particulière.
• Square définit un rectangle dont l’épaisseur est égale à la moitié de la largeur
du trait.
• Round définit un demi-cercle.
• Triangle définit un triangle isocèle.
220 Développez des applications Internet avec Silverlight 5
Les propriétés StrokeDashArray, StrokeDashCap et StrokeDashOffset permettent
de définir un trait en pointillé.
La figure 8.4 visualise le fichier Ligne.xaml dans lequel différentes configurations
de lignes sont effectuées.
FIGURE 8.4

<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»


Height= «648»>
<Line Name= «x_ligne1» X1= «0» Y1= «0» X2= «100» Y2= «0» Stroke= «Black»
Copyright 2012 Patrice REY

StrokeThickness= «10» Width= «100» Height= «25»


Canvas.Left= «23» Canvas.Top= «31»></Line>
<Line Canvas.Left= «20» Canvas.Top= «84» Height= «36» Name= «line1»
Width= «116»
X1= «10» X2= «100» Y1= «10» Y2= «10» Stroke= «Black» StrokeThickness= «20»
StrokeStartLineCap= «Square» StrokeEndLineCap= «Square» />
<Line Canvas.Left= «20» Canvas.Top= «147» Name= «line2» Stroke= «Black»
StrokeEndLineCap= «Round» StrokeStartLineCap= «Round» StrokeThickness= «20»
X1= «10» X2= «100» Y1= «10» Y2= «10» Width= «116» Height= «29» />
CHAPITRE 8 □ Le graphisme 221

<Line Canvas.Left= «20» Canvas.Top= «214» Height= «29» Name= «line3»


Stroke= «Black» StrokeEndLineCap= «Triangle»
StrokeStartLineCap= «Triangle» StrokeThickness= «20» Width= «116» X1= «10»
X2= «100» Y1= «10» Y2= «10» />
<Line Canvas.Left= «12» Canvas.Top= «291» Height= «29» Name= «line4»
Width= «211» X1= «10» X2= «200» Y1= «10» Y2= «10»
Stroke= «Black» StrokeEndLineCap= «Flat» StrokeStartLineCap= «Flat»
StrokeThickness= «10» StrokeDashArray= «2»/>
<Line Canvas.Left= «12» Canvas.Top= «380» Height= «29» Width= «211» X1= «10»
X2= «200» Y1= «10» Y2= «10»
Stroke= «Black» StrokeEndLineCap= «Flat» StrokeStartLineCap= «Flat»
StrokeThickness= «10» StrokeDashArray= «3,1»/>
<Line Canvas.Left= «12» Canvas.Top= «481» Height= «29» Stroke= «Black»
StrokeDashArray= «3,1,2,1» StrokeEndLineCap= «Flat»
StrokeStartLineCap= «Flat» StrokeThickness= «10» Width= «211» X1= «10»
X2= «200» Y1= «10» Y2= «10» />
</Canvas>

1.5 - La classe Polyline

La classe Polyline représente une série de droites jointes déterminée par la


propriété Points de type PointCollection. En XAML, elle peut s’exprimer sous la
forme d’une série de paires de coordonnées («x1,y1 x2,y2 ...»).
La propriété StrokeLineJoin indique la forme appliquée aux coins de la figure au
moyen de l’énumération PenLineJoin:
• Miter définit un angle pointu (par défaut).
• Bevel définit un angle rogné.
• Round définit un angle arrondi.
Les angles pointus peuvent être rognés de façon contrôlée au moyen de la propriété
StrokeMiterLimit.
La figure 8.5 visualise le fichier Polyligne.xaml dans lequel différentes configurations
de séries de lignes sont effectuées.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «552»>
<Polyline Stroke= «Black» StrokeThickness= «8»
Points= «0,75 80,75 80,100 130,50 80,10 80,40 0,40» StrokeLineJoin= «Miter»
Width= «137» Height= «111» Canvas.Left= «12» Canvas.Top= «12»
Fill= «Gainsboro»></Polyline>
<Polyline Canvas.Left= «12» Canvas.Top= «129» Fill= «Gainsboro» Height= «111»
Points= «0,75 80,75 80,100 130,50 80,10 80,40 0,40» Stroke= «Black»
StrokeLineJoin= «Bevel» StrokeThickness= «8»
Width= «137» />
<Polyline Canvas.Left= «12» Canvas.Top= «246» Fill= «Gainsboro» Height= «111»
222 Développez des applications Internet avec Silverlight 5
Points= «0,75 80,75 80,100 130,50 80,10 80,40 0,40» Stroke= «Black»
StrokeLineJoin= «Round» StrokeThickness= «8»
Width= «137» />
<Polyline Canvas.Left= «12» Canvas.Top= «363» Fill= «Gainsboro» Height= «111»
Points= «0,75 80,75 80,100 130,50 80,10 80,40 0,40» Stroke= «Black»
StrokeLineJoin= «Round» StrokeThickness= «8»
Width= «137» StrokeDashArray= «2,1»/>
</Canvas>

FIGURE 8.5

1.6 - La classe Polygon Copyright 2012 Patrice REY

La classe Polygon fonctionne de façon similaire à la classe Polyline. Les points


des sommets sont spécifiés dans la propriété Points de type PointCollection. Un
segment relie automatiquement le dernier point au premier point pour fermer la
figure.
Les segments d’un polygone peuvent se croiser pour former une figure composée
de plusieurs zones. La propriété FillRule détermine la façon dont ces zones sont
colorées par le pinceau indiqué dans la propriété Fill:
CHAPITRE 8 □ Le graphisme 223

• EvenOdd: la zone est colorée si elle est séparée de l’extérieur de la figure par
un nombre impair de segments (comportement par défaut).
• NonZero: la zone est colorée en fonction de la direction suivie par les points
formant les segments; si le nombre de segments pour une direction donnée
est identique à celui pour la direction opposée, la zone n’est pas colorée; si ces
nombres sont différents, la zone est colorée; ainsi un compteur est incrémenté
pour chaque segment entourant la zone dans un sens donné, et décrémenté
pour chaque segment dans l’autre sens; au final, si le compteur est nul, la zone
n’est pas colorée.
La figure 8.6 visualise le fichier Polygon.xaml dans lequel différentes configurations
d’objets Polygon sont effectuées.
FIGURE 8.6
224 Développez des applications Internet avec Silverlight 5
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «700»>
<Polygon
Points= «150,0 300,300 0,150 300,100 0,100 150,300 300,0»
Stroke= «Black»
StrokeThickness= «2» Width= «300» Height= «300»
Fill= «LightGray» FillRule= «EvenOdd»></Polygon>
<Polygon Canvas.Left= «0» Canvas.Top= «322» Fill= «LightGray»
FillRule= «Nonzero» Height= «300»
Points= «150,0 300,300 0,150 300,100 0,100 150,300 300,0» Stroke= «Black»
StrokeThickness= «2» Width= «300» />
</Canvas>

2 - Les tracés et la géométrie

Un tracé évolué est obtenu au moyen d’un objet Path et d’une géométrie assignée
à sa propriété Data. La géométrie définit la forme d’une figure et le Path en assure
le rendu visuel. Les types de géométries sont (figure 8.7):
• RectangleGeometry qui définit un rectangle de dimensions données.
• EllipseGeometry qui définit une ellipse inscrite dans un rectangle fictif.
• LineGeometry qui définit une ligne droite reliant deux points donnés.
• PathGeometry qui définit un ensemble de figures composées chacune d’une
suite de segments droits ou courbes connectés.
• GeometryGroup qui définit une géométrie issue du regroupement de plusieurs
géométries.
La figure 8.8 visualise le fichier Path.xaml dans lequel deux objets Path sont
visualisés. Un GeometryGroup regroupe plusieurs géométries basiques. La
coloration des zones imbriquées suit la règle de remplissage EvenOdd.
<Path Fill= «LightGray» Stroke= «Black» StrokeThickness= «2» Width= «273»
Height= «196»>
<Path.Data>
<GeometryGroup FillRule= «EvenOdd»>
<EllipseGeometry Center= «75,75» RadiusX= «50» RadiusY= «50» />
<RectangleGeometry Rect= «100,50,100,30»></RectangleGeometry>
Copyright 2012 Patrice REY

<EllipseGeometry Center= «155,135» RadiusX= «100» RadiusY= «50» />


</GeometryGroup></Path.Data></Path>
<Path.Data>
<GeometryGroup FillRule= «Nonzero»>
<EllipseGeometry Center= «75,75» RadiusX= «50» RadiusY= «50» />
<RectangleGeometry Rect= «100,50,100,30» />
<EllipseGeometry Center= «155,135» RadiusX= «100» RadiusY= «50» />
</GeometryGroup></Path.Data></Path>
CHAPITRE 8 □ Le graphisme 225
FIGURE 8.7

FIGURE 8.8
226 Développez des applications Internet avec Silverlight 5
La classe PathGeometry (figure 8.9) permet de constituer des figures évoluées
au moyen de segments de différents types. La classe de base abstraite de ces
segments est PathSegment. Les classes qui dérivent de PathSegment sont:
• la classe LineSegment et PolylineSegment qui représentent des segments
linéaires.
• la classe ArcSegment qui représente des segments courbes en arc.
• la classe BezierSegment et PolyBezierSegment qui représentent des segments
composés d’une courbe de Bézier cubique.
• la classe QuadraticBezierSegment et PolyQuadraticBezierSegment qui
représentent des segments composés d’une courbe de Bézier quadratique.
FIGURE 8.9

Les segments sont assemblés au sein d’un objet PathFigure. Un PathGeometry


Copyright 2012 Patrice REY

est constitué d’un ou plusieurs objets PathFigure, qui sont assignés à sa propriété
Figures de type PathFigureCollection. Un objet PathFigure regroupe ses segments
dans une collection de type PathSegmentCollection référencée par sa propriété
Segments.
PathFigure a sa propriété StartPoint qui détermine le point d’origine du premier
segment. Chaque segment dispose d’une propriété indiquant son point de
CHAPITRE 8 □ Le graphisme 227

destination. A partir du deuxième segment, le point d’origine d’un segment


correspond au point de destination de son prédécesseur. Le point de destination
d’un segment est référencé différemment selon le type du segment. Dans le cas de
LineSegment et de ArcSegment, il s’agit de la propriété Point. La propriété IsClosed
de PathFigure indique si un segment relie automatiquement au point d’origine le
point de destination du dernier segment spécifié.
La figure 8.10 visualise le fichier PathGeometry.xaml dans lequel un objet
PathGeometry est affecté à la propriété Data d’un objet Path.
<Path Stroke= «Black» StrokeThickness= «2» Width= «416» Height= «220» >
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint= «10,50»>
<PathFigure.Segments>
<BezierSegment Point1= «100,0»
Point2= «200,200»
Point3= «300,100»/>
<LineSegment Point= «400,100» />
<ArcSegment Size= «50,50» RotationAngle= «45»
IsLargeArc= «True» SweepDirection= «Clockwise»
Point= «200,100»/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>

FIGURE 8.10
228 Développez des applications Internet avec Silverlight 5

2.1 - La ligne droite

Un objet LineSegment permet de relier deux points. Les propriétés Stroke,


StrockeThickness, StrokeDashArray et Fill, permettent de donner une couleur, une
épaisseur, un trait pointillé et une couleur de remplissage à la géométrie dessinée.
La figure 8.11 visualise le fichier GeometrieLigneDroite.xaml avec une géométrie et
des variantes sur ces paramètres.
<Path Stroke= «Black» StrokeThickness= «2» Width= «200» Height= «200»>
<Path.Data>
<PathGeometry>
<PathFigure IsClosed= «True» StartPoint= «10,100»>
<LineSegment Point= «100,100» />
<LineSegment Point= «100,50» />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke= «Black» StrokeThickness= «3» Width= «200» Height= «200»
Canvas.Left= «200» StrokeDashArray= «2,2»>
<Path.Data>
<PathGeometry>
<PathFigure IsClosed= «True» StartPoint= «10,100»>
<LineSegment Point= «100,100» />
<LineSegment Point= «100,50» />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke= «Black» StrokeThickness= «3» Width= «200» Height= «200»
Canvas.Left= «400» StrokeDashArray= «5,2» Fill= «LightGray»>
<Path.Data>
<PathGeometry>
<PathFigure IsClosed= «True» StartPoint= «10,100»>
<LineSegment Point= «100,100» />
<LineSegment Point= «100,50» />
</PathFigure>
</PathGeometry>
Copyright 2012 Patrice REY

</Path.Data>
</Path>

2.2 - La ligne courbe

En séparant une ellipse en deux parties par une ligne droite, on obtient deux arcs
complémentaires, l’un étant large et l’autre étroit. L’objet ArcSegment représente
CHAPITRE 8 □ Le graphisme 229
FIGURE 8.11

un de ces deux arcs en fonction de sa propriété booléenne IsLargeArc. Les points


d’intersection entre l’ellipse et la ligne droite correspondent au point d’origine
et au point de destination du segment. La propriété Point représente le point de
destination (le point d’origine étant le point de destination du segment précédent
ou le StartPoint du PathFigure). Le rayon de l’ellipse est défini au moyen de la
propriété Size. Size.Width correspond au rayon en largeur et Size.Height au rayon
en hauteur. La propriété SweepDirection indique si l’arc est dessiné entre son
point d’origine et son point de destination dans le sens des aiguilles d’une montre
(Clockwise) ou dans le sens contraire (CounterClockwise). Enfin, la propriété
RotationAngle indique un angle de rotation appliqué à l’ellipse, en appui sur son
point d’origine et son point de destination. Cette propriété est sans effet visible
dans le cas d’un cercle (hauteur et largeur identiques).
La figure 8.12 visualise le fichier GeometrieLigneCourbe.xaml avec une géométrie
et des variantes sur ces paramètres.
FIGURE 8.12

point de
terminaison

grand
petit arc arc

StartPoint
230 Développez des applications Internet avec Silverlight 5
<Path Stroke= «Black» StrokeThickness= «2» Width= «200» Height= «200»>
<Path.Data>
<PathGeometry>
<PathFigure IsClosed= «False» StartPoint= «10,100»>
<ArcSegment Point= «250,150» Size= «200,300» />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>

2.3 - Les courbes de Bézier

La courbe de Bézier est implémentée au travers des classes BezierSegment (courbe


de Bézier cubique) et QuadraticBezierSegment (courbe de Bézier quadratique).
Un objet BezierSegment est constitué d’un point d’origine (point de destination
du segment précédent ou StartPoint du PathFigure) et d’un point de destination
Point3. En supplément, il comporte deux points de contrôle, l’un Point1, associé au
point d’origine, et l’autre Point2, associé au point de destination. Chaque point de
contrôle impacte la forme de la courbe à une extrémité du segment. La droite fictive
et non visuelle, qui relie une extrémité du segment à son point de contrôle est une
tangente qui détermine la courbure au niveau du point d’extrémité. L’amplitude de
la courbure est d’autant plus importante que ces deux points sont écartés.
La figure 8.13 visualise le fichier GeometrieCourbeBezier.xaml avec la géométrie
d’une courbe de Bézier.
FIGURE 8.13

Copyright 2012 Patrice REY

<Path Stroke= «Black» StrokeThickness= «2» Width= «200» Height= «200»>


<Path.Data>
<PathGeometry>
CHAPITRE 8 □ Le graphisme 231

<PathFigure StartPoint= «10,10»>


<BezierSegment Point1= «130,30» Point2= «40,140»
Point3= «150,150»></BezierSegment>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke= «Green» StrokeThickness= «2» StrokeDashArray= «5 2»
Canvas.Top= «0» Width= «100» Height= «50»>
<Path.Data>
<GeometryGroup>
<LineGeometry StartPoint= «10,10» EndPoint= «130,30»></LineGeometry>
<LineGeometry StartPoint= «40,140» EndPoint= «150,150»></LineGeometry>
</GeometryGroup>
</Path.Data>
</Path>
<Path Fill= «Red» Stroke= «Red» StrokeThickness= «8» Canvas.Top= «-4»
Width= «161» Height= «117» Canvas.Left= «-28»>
<Path.Data>
<GeometryGroup>
<EllipseGeometry Center= «130,30»></EllipseGeometry>
<EllipseGeometry Center= «40,140»></EllipseGeometry>
</GeometryGroup>
</Path.Data>
</Path>
<Path Canvas.Left= «50» Canvas.Top= «126» Height= «50» Stroke= «Green»
StrokeDashArray= «5 2» StrokeThickness= «2» Width= «100»>
<Path.Data>
<GeometryGroup>
<LineGeometry EndPoint= «130,30» StartPoint= «10,10» />
<LineGeometry EndPoint= «150,150» StartPoint= «40,140» />
</GeometryGroup>
</Path.Data>
</Path>
<Path Canvas.Left= «-69» Canvas.Top= «105» Fill= «Red» Height= «117»
Stroke= «Red» StrokeThickness= «8» Width= «161»>
<Path.Data>
<GeometryGroup>
<EllipseGeometry Center= «130,30» />
<EllipseGeometry Center= «40,140» />
</GeometryGroup>
</Path.Data>
</Path>
La classe PolyBezierSegment permet de spécifier en un seul élément une
succession de courbes de Bézier. Les points sont spécifiés par bloc de trois dans
la propriété Points de type PointCollection, chaque bloc représentant une courbe
cubique. Les courbes quadratiques QuadraticBezierSegment sont moins élaborées
que les courbes cubiques. Elles n’utilisent qu’un seul point de contrôle (Point1). Le
232 Développez des applications Internet avec Silverlight 5
point de destination est Point2. La classe PolyQuadraticBezierSegment permet de
spécifier en un seul élément une succession de courbes quadratiques. Les points
sont spécifiés par bloc de deux dans la propriété Points de type PointCollection,
chaque bloc représentant une courbe quadratique.

2.4 - Mini-langage de tracé

Le mini-langage de tracé est une syntaxe abrégée de définition de géométrie qui


utilise un langage spécifique appelé mini-langage de tracé. Cette syntaxe permet
de réduire considérablement la taille du fichier XAML produit pour les géométries.
La syntaxe est la suivante:
Data = «[fillrule] pathfigure[pathfigure]*»
Le jeton fillrule est facultatif, il détermine la méthode de remplissage. Le jeton
pathfigure représente un objet PathFigure et est composé des commandes
suivantes:
• une commande Move qui définit la propriété StartPoint de l’objet PathFigure.
• une série de commandes de dessin.
• une commande optionnelle de fermeture.
Plusieurs jetons PathFigure peuvent s’enchaîner. Une lettre majuscule de
commande indique des coordonnées absolues. Une lettre minuscule indique
des coordonnées relatives au point précédent. La syntaxe du jeton fillrule et des
différentes commandes sont les suivantes:
• F0 pour PathGeometry.FillRule = FillRule.EvenOdd,
• F1 pour PathGeometry.FillRule = FillRule.NonZero,
• M x,y pour PathFigure.StarPoint,
• L x,y pour LineSegment,
• H x pour LineSegment,
• V y pour LineSegment,
• A sizeX,sizeY angle isLargeArc sweepDirection x,y pour ArcSegment,
• C x1,y1 x2,y2 x3,y3 pour BezierSegment,
Copyright 2012 Patrice REY

• S x2,y2 x3,y3 pour BezierSegment lissée,


• Q x1,y1 x2,y2 pour QuadraticBezierSegment,
• T x1,y1 x2,y2 pour QuadraticBezierSegment lissée,
• Z pour fermeture.
<Path Stroke= «Black» StrokeThickness= «2» Width= «200» Height= «200»
Data= «M10,10 C130,30 40,140 150,150»></Path>
CHAPITRE 8 □ Le graphisme 233

2.5 - La découpe (clipping)

Silverlight permet d’effectuer des opérations de découpe (clipping) à tout objet


UIElement, donc à tout type de contenu (texte, image, vidéo, élément d’interface
utilisateur, etc.).
La classe UIElement expose une propriété Clip, de type Geometry. Cette géométrie
détermine une région à l’intérieur de laquelle l’élément est visible et accessible
(par exemple avec la souris ou le doigt dans le cas du toucher). A l’extérieur de
cette région, l’élément n’est ni visible ni accessible.
La figure 8.14 visualise le fichier Clipping.xaml dans lequel des découpes variées
sont effectuées sur une image et sur un bouton.
Le repère n°1 de la figure 8.14 visualise un contrôle Image dont la découpe est une
géométrie en forme d’ellipse (EllipseGeometry).
<Image Canvas.Left= «226» Canvas.Top= «12» Height= «250» Name= «x_img_clip1»
Source= «/DemoFormeGeometrique;component/contenu/lino_ventura.jpg»
Stretch= «Fill» Width= «200» >
<Image.Clip>
<EllipseGeometry RadiusX= «100» RadiusY= «125» Center= «100,125»>
</EllipseGeometry>
</Image.Clip>
</Image>
Un assemblage de géométries se fait par l’intermédiaire d’un GeometryGroup. Cet
objet GeometryGroup est affecté ensuite à la propriété Clip d’un contrôle.
<Image Canvas.Left= «442» Canvas.Top= «12» Height= «250» Name= «x_img_clip2»
Source= «/DemoFormeGeometrique;component/contenu/lino_ventura.jpg»
Stretch= «Fill» Width= «200»>
<Image.Clip>
<GeometryGroup FillRule= «Nonzero»>
<EllipseGeometry RadiusX= «75» RadiusY= «50» Center= «80,125» />
<EllipseGeometry RadiusX= «100» RadiusY= «25» Center= «100,200» />
<EllipseGeometry RadiusX= «75» RadiusY= «130» Center= «125,125» />
</GeometryGroup>
</Image.Clip>
</Image>
Une géométrie plus élaborée peut être utilisée pour la découpe, comme ici (figure
8.14, repère n°3) avec un PathGeometry à base de courbes de Bézier. La propriété
Clip du contrôle Rectangle est exprimée en mini-langage.
<Rectangle Canvas.Left= «12» Canvas.Top= «428» Height= «440»
Name= «rectangle1»
Stroke= «Black» StrokeThickness= «1»
234 Développez des applications Internet avec Silverlight 5
Width= «647» Clip= «M536,228 C625,609 451,298 315,383 C179,468 62,431 70,345
C78,259 447,-153 536,228 z»>
<Rectangle.Fill>
<ImageBrush ImageSource= «/DemoFormeGeometrique;component/
contenu/meuble_peint_2.jpg» />
</Rectangle.Fill>
</Rectangle>
FIGURE 8.14

3
Copyright 2012 Patrice REY
CHAPITRE 8 □ Le graphisme 235

Le repère n°2 de la figure 8.14 visualise un bouton pour lequel on effectue un


clipping avec un objet EllipseGeometry et un objet RectangleGeometry.
<Button Canvas.Left= «226» Canvas.Top= «292» Content= «exemple avec un bouton»
Cursor= «Hand» FontFamily= «Verdana»
FontSize= «14» Height= «130» Name= «x_btn_clip1» Width= «200» >
<Button.Clip>
<EllipseGeometry RadiusX= «90» RadiusY= «60» Center= «100,65»>
</EllipseGeometry>
</Button.Clip>
</Button>
<Button Canvas.Left= «450» Canvas.Top= «292» Content= «exemple avec un bouton»
Cursor= «Hand» FontFamily= «Verdana»
FontSize= «14» Height= «130» Name= «button1» Width= «200»>
<Button.Clip>
<RectangleGeometry RadiusX= «10» RadiusY= «10» Rect= «10,10,180,100» />
</Button.Clip>
</Button>

3 - Les transformations

Silverlight possède un système de transformations qui permet de déplacer, de


réduire, d’agrandir, de faire pivoter ou d’incliner une figure, voire de lui appliquer
une combinaison de ces effets. Ces transformations sont dites affines c’est-à-
dire qu’elles préservent la structure et le parallélisme de la figure originale. Une
transformation affine permet par exemple de transformer un carré en losange,
mais pas en trapèze ou en cercle.
Le mécanisme consiste en l’application d’un système de coordonnées transformées
mappé sur le système de coordonnées initial en fonction d’une matrice. Cette
matrice est définit par la classe MatrixTransform.
Des classes de plus haut niveau et plus faciles à mettre en œuvre sont intégrées
pour effectuer des transformations courantes (figure 8.15):
• la classe TranslateTransform qui permet le déplacement.
• la classe ScaleTransform qui permet la mise à l’échelle par agrandissement ou
réduction.
• la classe RotateTransform qui permet la rotation.
• la classe SkewTransform qui permet l’inclinaison.
• les classes TransformGroup et CompositeTransform qui permettent des
transformations issues du regroupement de plusieurs transformations.
• la classe MatrixTransform qui permet d’effectuer une transformation à l’aide
d’une matrice.
236 Développez des applications Internet avec Silverlight 5
Toutes ces classes de transformations héritent des classes abstraites Transform et
GeneralTransform (figure 8.15).
FIGURE 8.15

Le projet DemoTransformations.sln, dans le dossier chapitre08, illustre les classes


des transformations.

3.1 - Le déplacement (TranslateTransform)


Copyright 2012 Patrice REY

Une translation peut être définie au moyen de la classe TranslateTransform. La


propriété X définit le décalage horizontal (en pixels), et la propriété Y définit le
décalage vertical.
Le fichier Translation.xaml (figure 8.16) visualise les translations suivant l’axe X et
suivant l’axe Y, pour un rectangle, à l’aide des glissières.
CHAPITRE 8 □ Le graphisme 237

FIGURE 8.16

Pour effectuer une translation, on affecte à la propriété RenderTransform un objet


TranslateTransform, dont ses propriétés X et Y sont liées aux valeurs des glissières
par databinding.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
<Rectangle Canvas.Left= «54» Canvas.Top= «55» Height= «55» Name= «rectangle1»
Stroke= «Black» StrokeThickness= «1» Width= «119» Fill= «LightGray» >
<Rectangle.RenderTransform>
<TranslateTransform x:Name= «x_tranlation»
X= «{Binding ElementName=x_slider_x, Path=Value}»
Y= «{Binding ElementName=x_slider_y, Path=Value}»>
</TranslateTransform>
</Rectangle.RenderTransform>
</Rectangle>
<Slider Canvas.Left= «50» Canvas.Top= «12» Height= «23» Name= «x_slider_x»
Width= «350» Minimum= «0» Maximum= «200»
SmallChange= «1» LargeChange= «10» />
<Slider Canvas.Left= «12» Canvas.Top= «41» Height= «211» Name= «x_slider_y»
Width= «36» Minimum= «0» Maximum= «200»
SmallChange= «1» LargeChange= «10» Orientation= «Vertical»
IsDirectionReversed= «True» />
</Canvas>

3.2 - La mise à l’échelle (ScaleTransform)

Une réduction ou un agrandissement peuvent être définis au moyen de la classe


ScaleTransform. La propriété ScaleX définit le facteur d’échelle horizontal. La
propriété ScaleY définit le facteur d’échelle vertical. Les propriétés CenterX et
CenterY définissent les coordonnées relatives du point d’ancrage de la figure à
238 Développez des applications Internet avec Silverlight 5
partir duquel la transformation s’applique. Par défaut ce point est (0,0).
Le fichier MiseEchelle.xaml (figure 8.17) visualise les mises à l’échelle suivant l’axe
X et suivant l’axe Y, pour un rectangle, à l’aide des glissières.
FIGURE 8.17

Pour effectuer une mise à l’échelle, on affecte à la propriété RenderTransform un


objet ScaleTransform, dont ses propriétés ScaleX et ScaleY sont liées aux valeurs
des glissières par databinding.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
<Rectangle Canvas.Left= «54» Canvas.Top= «55» Height= «55» Name= «rectangle1»
Stroke= «Black» StrokeThickness= «1» Width= «119» Fill= «LightGray» >
<Rectangle.RenderTransform>
<ScaleTransform x:Name= «x_echelle» CenterX= «0» CenterY= «0»
ScaleX= «{Binding ElementName=x_slider_x, Path=Value}»
ScaleY= «{Binding ElementName=x_slider_y, Path=Value}»>
</ScaleTransform>
</Rectangle.RenderTransform>
</Rectangle>
<Slider Canvas.Left= «50» Canvas.Top= «12» Height= «23» Name= «x_slider_x»
Width= «350» Minimum= «1» Maximum= «4»
SmallChange= «1» LargeChange= «10» />
<Slider Canvas.Left= «12» Canvas.Top= «41» Height= «211» Name= «x_slider_y»
Width= «36» Minimum= «1» Maximum= «4»
Copyright 2012 Patrice REY

SmallChange= «1» LargeChange= «10» Orientation= «Vertical»


IsDirectionReversed= «True» />
</Canvas>

3.3 - La rotation (RotateTransform)

Une rotation peut être définie au moyen de la classe RotateTransform, dans le


CHAPITRE 8 □ Le graphisme 239

sens des aiguilles d’une montre, selon un angle défini par la propriété Angle (en
degrés). Les propriétés CenterX et CenterY définissent les coordonnées relatives du
point d’ancrage de la figure à partir duquel la transformation s’applique. Par défaut
ce point est (0,0).
Le fichier Rotation.xaml (figure 8.18) visualise la rotation pour un rectangle, selon
un angle variant de 0 à 360 degrés, à l’aide d’une glissière.
FIGURE 8.18

Pour effectuer une rotation, on affecte à la propriété RenderTransform un objet


RotateTransform, dont sa propriété Angle est liée aux valeurs de la glissière par
databinding.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
<Ellipse Canvas.Left= «173» Canvas.Top= «5» Height= «238» Name= «ellipse1»
Stroke= «Black» StrokeThickness= «1» Width= «238» StrokeDashArray= «2,8» />
<Rectangle Canvas.Left= «292» Canvas.Top= «124» Height= «26»
Name= «rectangle1» Stroke= «Black» StrokeThickness= «2»
Width= «119» Fill= «LightGray» >
<Rectangle.RenderTransform>
<RotateTransform x:Name= «x_rotation» CenterX= «0» CenterY= «0»
Angle= «{Binding ElementName=x_slider, Path=Value}»>
</RotateTransform>
</Rectangle.RenderTransform>
</Rectangle>
<Slider Canvas.Left= «117» Canvas.Top= «249» Height= «23» Name= «x_slider»
Width= «350» Minimum= «0» Maximum= «360»
SmallChange= «1» LargeChange= «10» />
</Canvas>
240 Développez des applications Internet avec Silverlight 5

3.4 - L’inclinaison (SkewTransform)

Une inclinaison peut être définie au moyen de la classe SkewTransform. La propriété


AngleX détermine l’angle d’inclinaison sur l’axe des X (en degrés et dans le sens des
aiguilles d’une montre). La propriété AngleY détermine l’angle d’inclinaison sur
l’axe des Y. Les propriétés CenterX et CenterY définissent les coordonnées relatives
du point d’ancrage de la figure à partir duquel la transformation s’applique. Par
défaut ce point est en (0,0).
Le fichier Inclinaison.xaml (figure 8.19) visualise l’inclinaison pour un rectangle, à
l’aide des glissières.
FIGURE 8.19

Copyright 2012 Patrice REY


CHAPITRE 8 □ Le graphisme 241

Pour effectuer une inclinaison, on affecte à la propriété RenderTransform un objet


SkewTransform, dont ses propriétés AngleX et AngleY sont liées aux valeurs des
glissières par databinding.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
<Rectangle Canvas.Left= «170» Canvas.Top= «98» Height= «83»
Name= «rectangle1»
Stroke= «Black» StrokeThickness= «2» Width= «86» Fill= «LightGray» >
<Rectangle.RenderTransform>
<SkewTransform x:Name= «x_inclinaison» CenterX= «0» CenterY= «0»
AngleX= «{Binding ElementName=x_slider_x, Path=Value}»
AngleY= «{Binding ElementName=x_slider_y, Path=Value}»>
</SkewTransform>
</Rectangle.RenderTransform>
</Rectangle>
<Slider Canvas.Left= «50» Canvas.Top= «12» Height= «23» Name= «x_slider_x»
Width= «350» Minimum= «0» Maximum= «20»
SmallChange= «1» LargeChange= «10» />
<Slider Canvas.Left= «12» Canvas.Top= «41» Height= «211» Name= «x_slider_y»
Width= «36» Minimum= «0» Maximum= «20»
SmallChange= «1» LargeChange= «10» Orientation= «Vertical»
IsDirectionReversed= «True» />
</Canvas>

3.5 - Regroupement de transformations

La classe TransformGroup définit une transformation issue du regroupement de


plusieurs transformations. L’ordre dans lequel les transformations sont ajoutées
à la propriété Children est important. Par exemple, une rotation suivie d’une
translation ne donnera pas le même résultat qu’une translation suivie d’une
rotation.
Le fichier Regroupement.xaml (figure 8.20) visualise un rectangle dont on fait subir
une translation suivie par une rotation (repère n°1), puis une rotation suivie par
une translation (repère n°2).
Pour effectuer un regroupement de transformations, on affecte à la propriété
RenderTransform un objet TransformGroup composé des transformations
successives à réaliser.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
<Rectangle Canvas.Left= «57» Canvas.Top= «45» Height= «83»
Name= «x_rect1_pointille» Stroke= «Black» StrokeThickness= «2»
Width= «86» Fill= «Transparent» StrokeDashArray= «2,2»></Rectangle>
<Rectangle Canvas.Left= «57» Canvas.Top= «45» Height= «83» Name= «rectangle1»
Stroke= «Black» StrokeThickness= «2» Width= «86» Fill= «LightGray» >
<Rectangle.RenderTransform>
242 Développez des applications Internet avec Silverlight 5
<TransformGroup>
<TranslateTransform X= «150» Y= «20»></TranslateTransform>
<RotateTransform CenterX= «0» CenterY= «0» Angle= «20»>
</RotateTransform>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Canvas.Left= «341» Canvas.Top= «45» Height= «83»
Name= «x_rect2_pointille» Stroke= «Black» StrokeThickness= «2»
Width= «86» Fill= «Transparent» StrokeDashArray= «2,2»>
</Rectangle>
<Rectangle Canvas.Left= «341» Canvas.Top= «45» Height= «83» Name= «x_rect2»
Stroke= «Black» StrokeThickness= «2» Width= «86» Fill= «LightGray» >
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform CenterX= «0» CenterY= «0» Angle= «20»>
</RotateTransform>
<TranslateTransform X= «150» Y= «20»>
</TranslateTransform>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>

FIGURE 8.20

Copyright 2012 Patrice REY

1 2

on effectue une on effectue une


translation suivie par rotation suivie par une
une rotation translation
CHAPITRE 8 □ Le graphisme 243

3.6 - La classe CompositeTransform

La classe CompositeTransform permet d’appliquer successivement les


transformations suivantes en une seule opération:
• en premier: une mise à l’échelle (ScaleX et ScaleY).
• en deuxième: une inclinaison (SkewX et SkewY).
• en troisième: une rotation (RotationX et RotationY).
• en quatrième: un déplacement (TranslateX et TranslateY).
Les propriétés CenterX et CenterY sont communes à toutes les transformations.
L’inconvénient de CompositeTransform est qu’il y a un ordre d’application
des transformations. De plus les propriétés CenterX et CenterY ne sont pas
individualisées.

3.7 - La transformation matricielle

Les matrices sont très largement utilisées en mathématiques dans le domaine


vectoriel. Silverlight offre donc la possibilité d’utiliser des matrices.
Une matrice est un tableau de données qui permet de définir une transformation
de coordonnées. La structure Matrix représente une matrice 2D, à deux colonnes
et trois lignes, dont les propriétés correspondent aux différentes cellules:

M11 M12
M21 M22
OffsetX OffsetY

La matrice unité peut être obtenue au moyen de la propriété statique Matrix.


Identity. Le constructeur de la structure Matrix crée par défaut une matrice unité.

1 0
matrice unité 0 1
0 0

Les coordonnées du point M2(x2,y2), résultant de la multiplication des coordonnées


du point M1(x1,y1) par une matrice, sont:
• x2 = M11*x1 + M21*y1 + OffsetX
• y2 = M12*x1 + M22*y1 + OffsetY
244 Développez des applications Internet avec Silverlight 5
Il faut remarquer que les membres de la structure Matrix correspondent aux
paramètres des transformations de haut niveau vues précédemment:
• M11 est le facteur de mise à l’échelle sur l’axe des X.
• M22 est le facteur de mise à l’échelle sur l’axe des Y.
• M21 est le facteur d’inclinaison sur l’axe des X (dans ce cas, ce n’est pas une
valeur d’angle mais un déplacement).
• M12 est le facteur d’inclinaison sur l’axe des Y (dans ce cas, ce n’est pas une
valeur d’angle mais un déplacement).
• OffsetX est le déplacement en pixels sur l’axe des X.
• OffsetY est le déplacement en pixels sur l’axe des Y.
La classe MatrixTransform, par sa propriété Matrix, permet de définir une
transformation personnalisée mixant ces différents facteurs.
Le fichier Matrice.xaml (figure 8.21) visualise un rectangle dont on fait subir une
translation de 150 pixels suivant l’axe X et 50 pixels suivant l’axe Y, avec une mise à
l’échelle de 2 suivant l’axe X et de 3 suivant l’axe Y (repère n°1).
FIGURE 8.21

2 0
1 0 3
150 50

Copyright 2012 Patrice REY

<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>


<Rectangle Canvas.Left= «57» Canvas.Top= «45» Height= «83»
Name= «x_rect1_pointille» Stroke= «Black» StrokeThickness= «2»
Width= «86» Fill= «Transparent» StrokeDashArray= «2,2»></Rectangle>
CHAPITRE 8 □ Le graphisme 245

<Rectangle Canvas.Left= «57» Canvas.Top= «45» Height= «83» Name= «x_rect1»


Stroke= «Black» StrokeThickness= «2» Width= «86» Fill= «LightGray» >
<Rectangle.RenderTransform>
<MatrixTransform>
<MatrixTransform.Matrix>
<Matrix M11= «2» M12= «0» M21= «0» M22= «3» OffsetX= «150»
OffsetY= «50»></Matrix>
</MatrixTransform.Matrix>
</MatrixTransform>
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>

4 - Les projections en perspective

Silverlight intègre un système de projection permettant d’appliquer à un élément


des transformations 3D appelées projections. L’objectif des projections est de
rendre les interfaces utilisateur plus intuitives.
Chaque UIElement expose une propriété Projection permettant de lui appliquer une
projection. La classe PlaneProjection implémente une projection qui fonctionne
comme une transformation non affine. Le parallélisme des côtés de l’élément
auquel elle est appliquée est rompu, ce qui provoque un effet de perspective.
Les propriétés RotationX, RotationY et RotationZ permettent d’effectuer une
rotation de l’élément en précisant un angle en degrés sur un axe du repère
global. Ces rotations sont par défaut centrées sur le centre de l’élément. Les
propriétés CenterOfRotationX (0.5 par défaut), CenterOfRotationY (0.5 par
défaut) et CenterRotationZ (0 par défaut) permettent de définir le point central
de la rotation. Les coordonnées X et Y sont exprimées en relatif (entre 0 et 1)
par rapport à une projection fictive de l’élément sur un carré. La coordonnée Z
est exprimée en pixels projetés. Les coordonnées s’appliquent au repère local. Les
propriétés GlobalOffsetX, GlobalOffsetY et GlobalOffsetZ permettent d’effectuer
une translation de l’élément dans le repère global, indépendamment de toute
rotation. Les propriétés LocalOffsetX, LocalOffsetY et LocalOffsetZ permettent
d’effectuer une translation de l’élément dans le repère local, en tenant compte
d’une éventuelle rotation.
Le fichier ProjectionPerspective.xaml (figure 8.22) visualise un Canvas contenant un
ensemble d’éléments. On fait tourner ce Canvas, en manipulant une glissière, autour
d’un axe grâce à une projection PlaneProjection. On notera que l’interactivité du
bouton, se trouvant à l’intérieur du Canvas, reste la même quelque soit la position
du Canvas.
246 Développez des applications Internet avec Silverlight 5
FIGURE 8.22

Pour faire tourner le Canvas autour d’un axe, on affecte à sa propriété Projection,
un objet PlaneProjection dont la propriété RotationX obtient sa valeur de la
glissière par databinding.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
<Line Name= «x_axe» X1= «0» Y1= «0» X2= «255» Y2= «0» Width= «255»
Height= «4»
StrokeThickness= «4» Stroke= «Black»
StrokeDashArray= «3,2» Canvas.Left= «12» Canvas.Top= «168»></Line>
<Canvas Name= «x_cnv_contenu» Width= «225» Height= «307» Background= «Silver»
Canvas.Left= «28» Canvas.Top= «12»>
Copyright 2012 Patrice REY

<Canvas.Projection>
<PlaneProjection
RotationX= «{Binding ElementName=x_slider, Path=Value}»>
</PlaneProjection>
</Canvas.Projection>
<Image Canvas.Left= «48» Canvas.Top= «34» Height= «218» Name= «image1»
Stretch= «Fill» Width= «144»
Source= «/DemoTransformations;component/contenu/monte%20cristo.jpg» />
<TextBlock Canvas.Left= «6» Canvas.Top= «6» Height= «27» Name= «textBlock1»
CHAPITRE 8 □ Le graphisme 247

Text= «Le Comte de Monte-Cristo»


FontFamily= «Verdana» FontSize= «16» TextWrapping= «Wrap» Width= «218» />
<Button Canvas.Left= «46» Canvas.Top= «263» Content= «Bouton» Height= «35»
Name= «button1» Width= «144» Cursor= «Hand»/>
</Canvas>
<Slider Canvas.Left= «287» Canvas.Top= «149» Height= «43» Name= «x_slider»
Width= «264» Minimum= «0» Maximum= «360»
SmallChange= «10» LargeChange= «10» />
</Canvas>
CHAPITRE 9 DANS CE CHAPITRE
• Les pinceaux avec la

Les pinceaux
classe Brush
• SolidColorBrush,
LinearGradientBrush,

et les images RadialGradientBrush,


ImageBrush,
VideoBrush
• Opacité et masque
d’opacité
• Les effets graphiques
DropShadowEffect,
BlurEffect,
L es pinceaux servent à définir le fond et le contour ShaderEffect
• Les images avec
d’une figure, sous la forme de motifs plus ou moins BitmapImage et
évolués, allant de la couleur unie à la vidéo. WriteableBitmap
Nous découvrirons l’utilisation de ces nombreuses
classes qui servent pour la définition et la
personnalisation des pinceaux.
L’opacité et le masque d’opacité seront abordés
pour comprendre leur utilité dans le rendu
graphique.
Silverlight permet d’appliquer à son rendu des
effets visuels évolués, appliqués au niveau de
chaque pixel, tout en conservant sa richesse
de composition, les animations et les autres
fonctionnalités de base. Nous verrons les trois
effets graphiques proposés.
La création d’une image point par point se fait en
manipulant l’objet WriteableBitmap. L’utilisation
de cet objet est fondamentale car il représente une
porte ouverte pour la génération des images. Il est
ainsi possible de générer des images qui répondent
à des algorithmes plus ou moins complexes (que
ce soit la génération d’une image aléatoire, d’une
image modifiée par traitement d’image, d’une
image fractale, etc.).
250 Développez des applications Internet avec Silverlight 5
Les pinceaux servent à définir le fond et le contour d’une figure, sous la forme de
motifs plus ou moins évolués, allant de la couleur unie à la vidéo.

1 - Les pinceaux

La classe Brush (figure 9.1) est la classe abstraite de base qui gère les différents
pinceaux. Elle hérite de DependencyObject. Les classes dérivées de Brush sont:
• la classe SolidColorBrush qui définit un motif de couleur unie.
• la classe LinearGradientBrush qui définit un motif en dégradé linéaire de
couleur.
• la classe RadialGradientBrush qui définit un motif en dégradé circulaire de
couleur.
• la classe ImageBrush qui définit un motif basé sur une image.
• la classe VideoBrush qui définit un motif basé sur une vidéo.
• la classe WebBrowserBrush qui définit un motif basé sur une page HTML.
• la classe ImplicitInputBrush qui définit un motif basé sur une image pour un
rendu amélioré en jouant sur les différents caractères de bases (luminosité,
texture, position, ombrage…).
FIGURE 9.1

Copyright 2012 Patrice REY


CHAPITRE 9 □ Les pinceaux et les images 251

Le projet DemoPinceaux.sln, dans le dossier chapitre09, illustre l’utilisation de ces


différentes classes.

1.1 - Le pinceau uni

La classe SolidColorBrush définit un pinceau dont le motif est une couleur unie. Sa
propriété Color indique une couleur unie déterminée.
La figure 9.2 visualise un ensemble d’objets Color prédéfinis, avec leur nom et leur
correspondance dans le schéma ARGB (le canal A pour la transparence, le canal R
pour le rouge, le canal G pour le vert, et le canal B pour le bleu).
Le fichier PinceauSolidColorBrush.xaml (figure 9.3) visualise un rectangle dont le
remplissage (propriété Fill) est réalisé par la couleur unie prédéfinie Gainsboro.
FIGURE 9.3

En XAML, on utilisera une des notations suivantes:


<Rectangle Fill= «Gainsboro» Stroke= «Black» StrokeThickness= «1» />
<Rectangle Stroke= «Black» StrokeThickness= «1» >
<Rectangle.Fill>
<SolidColorBrush Color= «Gainsboro»></SolidColorBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Stroke= «Black» StrokeThickness= «1» >
<Rectangle.Fill>
<SolidColorBrush Color= «#FFDCDCDC»></SolidColorBrush>
</Rectangle.Fill>
</Rectangle>
252 Développez des applications Internet avec Silverlight 5
FIGURE 9.2

Copyright 2012 Patrice REY


CHAPITRE 9 □ Les pinceaux et les images 253

Et par code de programmation, on utilisera la méthode statique Color.FromArgb


pour définir une couleur unie selon ses canaux ARGB. Le type des valeurs des
canaux est le byte. La valeur 255, de type byte (ou FF en hexadécimal), indique
qu’il n’y a pas de transparence.
SolidColorBrush coul_gainsboro =
new SolidColorBrush(Color.FromArgb(255, 220, 220, 220));
x_rect5.Fill = coul_gainsboro;

1.2 - Le pinceau dégradé

Les classes LinearGradientBrush et RadialGradientBrush définissent un pinceau


avec un dégradé de couleurs respectivement linéaire et circulaire. Ces types de
dégradé sont couramment utilisés en infographie pour réaliser des effets.
Le fichier PinceauDegrade.xaml (figure 9.4) visualise plusieurs rectangles dont le
remplissage est fait par des dégradés de couleurs, partant de la couleur DimGray
à la couleur White.
FIGURE 9.4

Le dégradé peut comporter plusieurs couleurs. L’orientation des vagues de couleur


se fait perpendiculairement à un axe défini par des propriétés du pinceau. Le point
254 Développez des applications Internet avec Silverlight 5
culminant de chaque couleur du dégradé occupe une position sur cet axe. La couleur
et sa position relative sur l’axe sont représentées par un objet GradientStop.
L’axe d’un LinearGradientBrush est défini par ses propriétés StartPoint et EndPoint.
Les coordonnées sont exprimées en relatif (de 0 à 1) par rapport à une projection
fictive de la figure sur un carré. StartPoint et EndPoint ont les valeurs par défaut
respectivement de (0,0) pour le coin supérieur haut gauche et (1,1) pour le coin
inférieur bas droit.
Un dégradé linéaire horizontal s’effectuera de la façon suivante (figure 9.5):
<LinearGradientBrush StartPoint= «0,0.5» EndPoint= «1,0.5»>
<GradientStop Color= «DimGray» Offset= «0» />
<GradientStop Color= «White» Offset= «1» />
</LinearGradientBrush>

FIGURE 9.5 (0,0) (1,0)


dégradé linéaire
horizontal
(0,0.5) (1,0.5)

(0,1) (1,1)
Un dégradé linéaire vertical s’effectuera de la façon suivante (figure 9.6):
<LinearGradientBrush EndPoint= «0.5,1» StartPoint= «0.5,0»>
<GradientStop Color= «DimGray» Offset= «0» />
<GradientStop Color= «White» Offset= «1» />
</LinearGradientBrush>
FIGURE 9.6 (0.5,0)
(0,0) (1,0)
dégradé linéaire
vertical
Copyright 2012 Patrice REY

(0,1) (1,1)
(0.5,1)
Un dégradé linéaire avec plusieurs vagues s’effectuera de la façon suivante (figure
9.7):
<LinearGradientBrush EndPoint= «1,1» StartPoint= «0,0»>
<GradientStop Color= «Black» Offset= «0» />
CHAPITRE 9 □ Les pinceaux et les images 255

<GradientStop Color= «DimGray» Offset= «0.25» />


<GradientStop Color= «DimGray» Offset= «0.5» />
<GradientStop Color= «DimGray» Offset= «0.75» />
<GradientStop Color= «White» Offset= «1» />
</LinearGradientBrush>
FIGURE 9.7 (0,0) (1,0)
dégradé linéaire à
plusieurs vagues

(0,1) (1,1)
Le dégradé RadialGradientBrush agit depuis un point central vers la circonférence
d’une ellipse. Le point central est défini par la propriété GradientOrigin, et
correspond à l’emplacement 0 d’un GradientStop. La circonférence correspond à
l’emplacement 1 d’un GradientStop.
Le dégradé prend la forme d’une ellipse (figure 9.8) dont la position est définie par
la propriété Center ((0.5,0.5) par défaut) et les dimensions par RadiusX (0.5 par
défaut) et RadiusY (0.5 par défaut).
<RadialGradientBrush>
<GradientStop Color= «DimGray» Offset= «0» />
<GradientStop Color= «White» Offset= «1» />
</RadialGradientBrush>

FIGURE 9.8 (0,0) (1,0)


dégradé circulaire
par défaut

(0,1) (1,1)

1.3 - Le pinceau image

La classe ImageBrush définit un pinceau composé d’une image bitmap spécifiée


dans la propriété ImageSource. La propriété Stretch indique le mode d’étirement
à appliquer (avec Fill comme valeur par défaut). Il est ainsi possible d’appliquer
une image sur tout élément disposant d’une propriété de type Brush. L’événement
256 Développez des applications Internet avec Silverlight 5
ImageFailed est émis en cas d’erreur de chargement de l’image (fichier invalide ou
bien fichier non trouvé).
Le fichier PinceauAutre.xaml (figure 9.9) visualise un Canvas dont sa propriété
Background est peinte avec une image (repère n°1), un Rectangle dont sa propriété
Fill est peinte avec une image (repère n°2), et un TextBlock dont sa propriété
Foreground est peinte avec une image (repère n°3).
FIGURE 9.9

Comme toujours, pour ajouter un objet complexe à une propriété, en XAML, on


utilise la notation pointée composée du contrôle et de sa propriété (<controle.
Copyright 2012 Patrice REY

propriete>).
<Canvas Canvas.Left= «12» Canvas.Top= «12» Height= «161» Name= «canvas1»
Width= «119»>
<Canvas.Background>
<ImageBrush ImageSource=
«/DemoPinceaux;component/contenu/monte%20cristo.jpg» />
</Canvas.Background>
CHAPITRE 9 □ Les pinceaux et les images 257

</Canvas>
<!-- -->
<Rectangle Canvas.Left= «12» Canvas.Top= «179» Height= «128»
Name= «rectangle1»
Stroke= «Black» StrokeThickness= «2» Width= «119»>
<Rectangle.Fill>
<ImageBrush ImageSource=
«/DemoPinceaux;component/contenu/meuble_peint_3.jpg» />
</Rectangle.Fill>
</Rectangle>
<!-- -->
<TextBlock Canvas.Left= «35» Canvas.Top= «326» Height= «158»
Name= «textBlock2»
Text= «un exemple de texte» Width= «480»
FontFamily= «Verdana» FontSize= «64» TextWrapping= «Wrap»
TextAlignment= «Center»>
<TextBlock.Foreground>
<ImageBrush
ImageSource= «/DemoPinceaux;component/contenu/monte%20cristo.jpg»
Stretch= «None» />
</TextBlock.Foreground>
</TextBlock>

2 - Opacité et masque d’opacité

La classe UIElement expose la propriété Opacity qui permet de définir la


transparence de façon globale au niveau de l’élément, indépendamment de la
composante alpha des couleurs de ses différentes parties. La valeur doit être
comprise entre 0 (transparence maximale) et 1 (opacité totale).
Un masque d’opacité permet de faire varier les zones transparentes d’une figure
en fonction des motifs d’un pinceau. Le principe consiste à réaliser un pinceau
évolué (dégradé ou image) contenant des zones transparentes, et à appliquer ce
pinceau à la propriété OpacityMask d’un objet UIElement. L’élément hérite alors
de la transparence du pinceau. Les couleurs du pinceau sont ignorées.
Le fichier TransparenceOpacite.xaml (figure 9.10) visualise un bouton dont l’opacité
est fixée à 0.6, et une image dotée d’un masque d’opacité dont le pinceau est un
dégradé linéaire à orientation verticale.
Un Canvas a sa propriété Background peinte avec un pinceau de type ImageBrush.
<Canvas Canvas.Left= «12» Canvas.Top= «12» Height= «401» Name= «canvas1»
Width= «576»>
<Canvas.Background>
<ImageBrush ImageSource= «/DemoPinceaux;component/contenu/fond_ciel.jpg»
/></Canvas.Background> ... </Canvas>
258 Développez des applications Internet avec Silverlight 5
FIGURE 9.10

On positionne un bouton dont on fixe sa propriété Opacity à 0.6. La propriété


Opacity modifie la transparence de tout l’élément, et donc le bouton apparait
semi-transparent sur toute sa surface (repère n°1).
<Button Canvas.Left= «14» Canvas.Top= «158» Content= «un bouton avec une
opacité de 0.6» Height= «52» Name= «button1» Width= «279»
FontFamily= «Verdana» FontSize= «14» Opacity= «0.6» Cursor= «Hand» />
Pour effectuer le reflet d’une image, on positionne deux contrôles Image, intitulés
x_img et x_img_reflechi. Pour que le deuxième contrôle (repère n°3) apparaisse
comme une image réfléchie du premier contrôle (repère n°2), on lui applique en
premier une transformation ScaleTransform (<Image.RenderTransform>) dont la
propriété ScaleY est fixée à -1. Puis on lui ajoute un masque d’opacité au travers de
sa propriété OpacityMask (<Image.OpacityMask>). Le pinceau utilisé pour peindre
Copyright 2012 Patrice REY

le masque d’opacité est un LinearGradientBrush, à orientation verticale, dont la


couleur de départ est Transparent, et la couleur de terminaison est #D7000000
(équivalent à un blanc semi-transparent).
<Image Canvas.Left= «398» Canvas.Top= «67» Height= «143» Name= «x_img»
Stretch= «Fill» Width= «120»
Source= «/DemoPinceaux;component/contenu/lino_ventura.jpg» />
<Image Canvas.Left= «399» Canvas.Top= «355» Height= «143»
CHAPITRE 9 □ Les pinceaux et les images 259

Name= «x_img_reflechi»
Source= «/DemoPinceaux;component/contenu/lino_ventura.jpg» Stretch= «Fill»
Width= «120» >
<Image.RenderTransform>
<ScaleTransform ScaleY= «-1»></ScaleTransform>
</Image.RenderTransform>
<Image.OpacityMask>
<LinearGradientBrush StartPoint= «0,0» EndPoint= «0,1»>
<GradientStop Offset= «0» Color= «Transparent»></GradientStop>
<GradientStop Offset= «1» Color= «#D7000000»></GradientStop>
</LinearGradientBrush>
</Image.OpacityMask>
</Image>

3 - Les effets graphiques

Silverlight permet d’appliquer à son rendu des effets visuels évolués, appliqués
au niveau de chaque pixel, tout en conservant sa richesse de composition, les
animations et les autres fonctionnalités de base. Ces effets héritent de la classe
abstraite Effect (figure 9.11) et sont représentés par:
• la classe DropShadowEffect qui génère un effet d’ombre portée à la surface
affichée.
• la classe BlurEffect qui applique un flou à la surface affichée.
• la classe ShaderEffect qui permet de réaliser des effets personnalisés.
FIGURE 9.11

Les propriétés de ces classes permettent de régler les effets. Ces propriétés peuvent
260 Développez des applications Internet avec Silverlight 5
être animées ou être pilotées au moyen du databinding. La propriété Effect de la
classe UIElement permet d’appliquer un effet à un élément visuel et à tous ses
éléments enfants.

3.1 - L’ombre portée

La classe DropShadowEffect génère un effet d’ombre portée à la surface affichée.


Cet effet est bien adapté aux éléments d’interface utilisateur. La personnalisation
de l’effet s’effectue au moyen des propriétés suivantes:
• Color qui définit la couleur de l’ombre.
• ShadowDepth qui définit la distance entre l’élément et l’ombre, avec une valeur
comprise entre 0 et 300 pixels (5 pixels par défaut).
• Opacity qui définit le niveau d’opacité de l’ombre avec une valeur de 0 à 1 (1
par défaut).
• BlurRadius qui définit l’intensité du flou de l’ombre (5 par défaut).
• Direction qui définit la direction de l’ombre avec une valeur d’angle entre 0 et
360 degrés (315 par défaut); la valeur 0 correspond à la droite de l’élément; la
rotation se fait dans le sens inverse des aiguilles d’une montre.
Le fichier OmbrePortee.xaml (figure 9.12) visualise un TextBlock et un Button avec
des effets DropShadowEffect.
FIGURE 9.12

Copyright 2012 Patrice REY

Le premier TextBlock possède un effet DrowShadowEffect par défaut, et le


deuxième TextBlock possède un effet personnalisé avec une couleur SlateGray
pour l’ombre portée, une valeur de 20 pour ShadowDepth et une valeur de 6 pour
CHAPITRE 9 □ Les pinceaux et les images 261

BlurRadius.
<TextBlock FontSize= «24» Margin= «3» Canvas.Left= «18» Canvas.Top= «9»
Height= «42» Width= «570» FontFamily= «Verdana»>
<TextBlock.Effect>
<DropShadowEffect></DropShadowEffect>
</TextBlock.Effect>
<TextBlock.Text>effet DropShadowEffect personnalisé</TextBlock.Text>
</TextBlock>
<TextBlock Canvas.Left= «18» Canvas.Top= «57» FontFamily= «Verdana»
FontSize= «24» Height= «42»
Text= «effet DropShadowEffect par défaut» Width= «570»>
<TextBlock.Effect>
<DropShadowEffect Color= «SlateGray» ShadowDepth= «20» BlurRadius= «6»>
</DropShadowEffect>
</TextBlock.Effect>
</TextBlock>
Le premier Button possède un effet DrowShadowEffect par défaut, et le deuxième
Button possède un effet personnalisé avec une couleur DarkGray pour l’ombre
portée et une valeur de 300 pour Direction.
<Button Canvas.Left= «18» Canvas.Top= «128» Content= «bouton avec
DropShadowEffect par défaut» Height= «43» Name= «button1» Width= «335»
FontFamily= «Verdana» FontSize= «14»>
<Button.Effect>
<DropShadowEffect></DropShadowEffect>
</Button.Effect>
</Button>
<Button Canvas.Left= «18» Canvas.Top= «192» Content= «bouton avec
DropShadowEffect personnalisé» FontFamily= «Verdana»
FontSize= «14» Height= «43» Name= «button2» Width= «335»>
<Button.Effect>
<DropShadowEffect ShadowDepth= «20» Color= «DarkGray» Direction= «300» />
</Button.Effect>
</Button>

3.2 - L’effet de flou

La classe BlurEffect définit un effet de flou. L’intensité du flou est réglable au


moyen de la propriété Radius (valeur de 5 par défaut) qui indique la profondeur
d’échantillonnage servant à déterminer la couleur d’un pixel à partir de celle de ses
voisins. L’intensité de l’effet est proportionnelle à cette valeur.
Le fichier EffetFlou.xaml (figure 9.13) visualise un Button et un Image avec des
effets BlurEffect.
262 Développez des applications Internet avec Silverlight 5
FIGURE 9.13

Le Button possède un effet BlurEffect avec une valeur de 2 pour la propriété


Radius de l’effet (repère n°1). Le contrôle Image possède un effet BlurEffect avec
une valeur de 4 pour la propriété Radius de l’effet (repère n°2).
<Button Content= «bouton avec effet BlurEffect (Radius=2)» Canvas.Left= «23»
Canvas.Top= «25» Height= «42» Width= «260»>
<Button.Effect>
<BlurEffect Radius= «2»></BlurEffect>
</Button.Effect>
</Button>
<Image Canvas.Left= «23» Canvas.Top= «86» Height= «180» Name= «image1»
Stretch= «Fill» Width= «121»
Source= «/DemoPinceaux;component/contenu/monte%20cristo.jpg» />
<Image Canvas.Left= «162» Canvas.Top= «86» Height= «180» Name= «image2»
Source= «/DemoPinceaux;component/contenu/monte%20cristo.jpg»
Stretch= «Fill» Width= «121» >
<Image.Effect>
<BlurEffect Radius= «4»></BlurEffect>
</Image.Effect>
</Image>
Copyright 2012 Patrice REY

3.3 - Les effets personnalisés

La classe ShaderEffect permet de définir un effet qui exploite un pixel shader. Un


pixel shader est un petit programme qui est appliqué à chaque pixel affiché pour
en définir la couleur et l’opacité.
Silverlight assure l’exécution des pixels shaders en rendu software, d’où des
CHAPITRE 9 □ Les pinceaux et les images 263

performances inférieures à ce que cela pourrait donner si l’exécution était faite


par le GPU.
La programmation d’un pixel shader se fait au moyen d’un langage dédié, issu de la
technologie DirectX. Ce langage est le HLSL (High Level Shader Language). Comme
il est exécuté pour chaque pixel, un pixel shader doit être le plus performant
possible. HLSL est un langage de bas niveau, initialement basé sur l’exploitation
directe des registres du GPU.
L’écriture d’un ShaderEffect s’avère donc assez complexe et requiert des
compétences particulières.

4 - Les images avec WriteableBitmap

Nous avons vu dans un précédent chapitre que l’image affichée dans un contrôle
Image, était définie dans la propriété Source au moyen d’un objet généralement
de type BitmapImage. En XAML, un URI est directement assignable à la propriété
Source sans mentionner la classe BitmapImage (par exemple <Image Source =
«mon_image.jpg» />).
L’arbre d’héritage (figure 9.14) montre que les classes BitmapImage et
WriteableImage héritent de BitmapSource, puis de ImageSource.
FIGURE 9.14
264 Développez des applications Internet avec Silverlight 5
La création d’une image point par point se fait en manipulant l’objet
WriteableBitmap comme un tableau, au moyen de la propriété indexeur Item.
Chaque valeur du tableau représente un pixel et contient sa couleur. La méthode
Lock doit être appelée au préalable. Quand l’assignation des données du tableau
est terminée, les méthodes Invalidate et Unlock doivent être successivement
appelées.
Si les données sont issues d’un calcul intensif, il est préférable de réaliser celui-
ci dans un thread spécifique. La classe WriteableBitmap ne peut être manipulée
que dans le thread principal mais peut recevoir ses données à partir d’un tableau
rempli dans un thread secondaire.
Le fichier GenererImage.xaml (figure 9.15) visualise un contrôle Image dans lequel
on lui affecte comme source une image bitmap générée aléatoirement.
FIGURE 9.15

Sur un Canvas sont positionnés un contrôle Image x_img et un bouton x_btn_


alea. Le gestionnaire de l’événement Click du bouton génère une image bitmap et
l’assigne comme source au contrôle x_img.
<Canvas x:Name= «x_cnv_root» Background= «White» Width= «600» Height= «600»>
<Button Canvas.Left= «178» Canvas.Top= «12» Content= «Générer une image
Copyright 2012 Patrice REY

aléatoire» Height= «32» Name= «x_btn_alea» Width= «237» FontFamily=


«Verdana»
FontSize= «14» Click= «x_btn_alea_Click» />
<Border BorderThickness= «2» BorderBrush= «Black» Canvas.Left= «47»
Canvas.Top= «50»>
<Image Canvas.Left= «20» Canvas.Top= «59» Height= «200» Name= «x_img»
Stretch= «Fill» Width= «500» />
</Border></Canvas>
CHAPITRE 9 □ Les pinceaux et les images 265

Une image bitmap wb, de type WriteableBitmap, est instanciée avec une largeur
et une hauteur identique au contrôle x_img. Deux boucles imbriquées permettent
de parcourir les pixels en largeur et en hauteur.
//on cree une bitmap avec la taille du controle x_img
WriteableBitmap wb =
new WriteableBitmap((int)x_img.Width,(int)x_img.Height);
Random rand = new Random();
for (int x = 0; x < wb.PixelWidth; x++) {
for (int y = 0; y < wb.PixelHeight; y++) {
...
}
}
Le format utilisé par le WriteableBitmap de Silverlight est le format ARGB32 (RVB
prémultipliées). Un pixel est représenté par un entier (de type int, donc sur 32 bits)
qui contient les composantes ARGB.
Le tableau de pixels contient une valeur de type int pour coder une couleur ARGB
(alpha red green blue) sur 32 bits avec gestion de l’opacité. La figure 9.16 illustre
le codage utilisé pour une couleur quelconque. Une séquence de 8 bits permet de
coder un nombre entier compris entre 0 et 255 soit 256 valeurs. Par conséquent la
valeur de la composante rouge d’un pixel peut être représentée selon 256 niveaux
différents, allant de 0 pour l’absence de rouge à 255 pour le rouge d’intensité
maximum. Il en est de même pour la composante bleue et verte, tout comme pour
la composante de transparence (canal alpha avec 0 pour la transparence maximale
et 255 pour l’opacité totale).
FIGURE 9.16

composante alpha = 255


une couleur quelconque soit en binaire: 11111111
se décompose en
composante rouge = 251
soit en binaire: 11111011
composante verte = 208
soit un int sur 32 bits qui donne soit en binaire: 11010000
en binaire:
11111111111110111101000010010111 composante bleue = 151
soit en binaire: 10010111

Ici, on génère aléatoirement les composantes des couleurs avec une opacité totale.
for (int x = 0; x < wb.PixelWidth; x++) {
for (int y = 0; y < wb.PixelHeight; y++) {
int alpha = 255;
int red = 0;
int green = 0;
266 Développez des applications Internet avec Silverlight 5
int blue = 0;
//on determine la couleur du pixel
if ((x % 5 == 0) || (y % 7 == 0)) {
red = (int)((double)y / wb.PixelHeight * 255);
green = rand.Next(100, 255);
blue = (int)((double)x / wb.PixelWidth * 255);
} else {
red = (int)((double)x / wb.PixelWidth * 255);
green = rand.Next(100, 255);
blue = (int)((double)y / wb.PixelHeight * 255);
}
...
}
}
Pour coder un pixel sous forme d’un int 32 bits, il faut associer les composantes les
unes à côté des autres. Pour cela on utilisera l’opérateur <<, et la formule employée
sera:
//on fixe le pixel
int pixelColorValue = (alpha << 24) | (red << 16) |
(green << 8) | (blue << 0);
Le pixel sous forme d’entier étant généré, il est ajouté au tableau des pixels, par la
propriété Pixels, en n’oubliant pas que ce tableau est au format unidimensionnel
([ligne]), et non pas au format multidimensionnel ([ligne,colonne]).
//on ajoute le pixel au tableau
wb.Pixels[y * wb.PixelWidth + x] = pixelColorValue;
Dès que le tableau des pixels est rempli, on affecte l’instance wb à la propriété
Source de x_img.
//on cree une bitmap avec la taille du controle x_img
WriteableBitmap wb =
new WriteableBitmap((int)x_img.Width,(int)x_img.Height);
Random rand = new Random();
for (int x = 0; x < wb.PixelWidth; x++) {
for (int y = 0; y < wb.PixelHeight; y++) {
int alpha = 255;
int red = 0;
Copyright 2012 Patrice REY

int green = 0;
int blue = 0;
//on determine la couleur du pixel
if ((x % 5 == 0) || (y % 7 == 0)) {
red = (int)((double)y / wb.PixelHeight * 255);
green = rand.Next(100, 255);
blue = (int)((double)x / wb.PixelWidth * 255);
} else {
red = (int)((double)x / wb.PixelWidth * 255);
CHAPITRE 9 □ Les pinceaux et les images 267

green = rand.Next(100, 255);


blue = (int)((double)y / wb.PixelHeight * 255);
}
//on fixe le pixel
int pixelColorValue = (alpha << 24) | (red << 16) |
(green << 8) | (blue << 0);
//on ajoute le pixel au tableau
wb.Pixels[y * wb.PixelWidth + x] = pixelColorValue;
}
}
//on affiche le bitmap dans un controle image
x_img.Source = wb;
C H A P I T R E 10 DANS CE CHAPITRE
• La classe Storyboard
• DoubleAnimation,
Les animations PointAnimation,
ColorAnimation
• La classe Timeline
• La classe
EasingFunctionBase
• BounceEase, CircleEase,
CubicEase, BackEase,
QuinticEase, QuarticEase,
QuadraticEase,
PowerEase, ElasticEase,
Les animations vous permettent de créer des SineEase, ExponentialEase
• KeyFrame,
interfaces utilisateur dynamiques. Le modèle LinearDoubleKeyFrame,
d’animation de Silverlight est basé sur la possibilité LinearColorKeyFrame,
LinearPointKeyFrame,
d’animer des propriétés par l’intermédiaire d’un DiscreteDoubleKeyFrame,
ciblage particulier. DiscreteColorKeyFrame,
Un grand nombre de classes sont implémentées DiscretePointKeyFrame,
DiscreteObjectKeyFrame,
pour cibler des propriétés de type et de nature SplineDoubleKeyFrame,
différentes. SplineColorKeyFrame,
Dans ce chapitre, vous verrez comment utiliser SplinePointKeyFrame,
EasingDoubleKeyFrame,
ces différentes classes, dont certaines permettent EasingPointKeyFrame,
d’animer des propriétés diverses et variées. EasingColorKeyFrame
L’animation basique suit une courbe d’interpolation • La classe
CompositionTarget
linéaire, et l’animation complexe suit une courbe
d’interpolation personnalisée.
Vous verrez différentes classes d’animations qui
proposent des courbes d’interpolation linéaires,
des courbes d’interpolation discrètes et des courbes
d’interpolation en forme de courbe de Bézier.
Le storyboard (table de montage séquentiel) est
l’objet qui gère plusieurs animations en parallèle.
Silverlight propose aussi l’implémentation de
l’animation par un rendu image par image. Vous
verrez une application mettant en jeu ce type de
rendu généralement classique.
270 Développez des applications Internet avec Silverlight 5
Généralement, une animation est considérée comme une succession d’images qui
sont visualisées les unes après les autres. L’animation dans Silverlight utilise un
modèle radicalement différent.

1 - Les règles de l’animation.

Le modèle d’animation de Silverlight permet d’animer certaines propriétés des


éléments. Leur progression se configure sur une durée donnée au moyen de
différentes stratégies qui sont:
• une progression linéaire entre une valeur de départ et une valeur cible.
• une application d’algorithmes d’interpolation prédéfinis entre une valeur de
départ et une valeur cible afin d’augmenter le réalisme.
• une progression parmi une série de valeurs intermédiaires au moyen de
différents algorithmes d’interpolation.
Le système d’animation s’applique aux propriétés de dépendances des objets
héritant de DependencyProperty. Une très grande partie des propriétés de la
plupart des objets Silverlight, y compris non visuels, sont concernées.
Une animation est définie au moyen d’un objet qui dépend du type de la propriété
de dépendance ciblée. Trois types supportent l’animation: Point, Double et Color.
Les classes d’animation correspondantes ont un préfixe qui correspond au type
supporté: PointAnimation , DoubleAnimation et ColorAnimation.
Les animations basiques font varier sur une durée donnée (propriété Duration)
la valeur d’une propriété cible entre une valeur de départ (propriété From) et
une valeur finale (propriété To), ou à partir de la valeur courante en fonction d’un
incrément (propriété By). Ces animations sont qualifiées d’animation From/To/By.
Les animation d’images clé (animations dites KeyFrames) permettent de définir des
valeurs intermédiaires et leurs paramètres d’interpolation. Les noms de classe sont
suffixés de UsingKeyFrames comme les classes PointAnimationUsingKeyFrames,
DoubleAnimationUsingKeyFrames, ColorAnimationUsingKeyFrames et
ObjectAnimationUsingKeyFrames. La figure 10.1 visualise l’arbre d’héritage de
Copyright 2012 Patrice REY

ces différentes classes.


La classe Storyboard définit un objet qui coordonne des animations et permet de
leur assigner une propriété cible au moyen des propriétés attachées Storyboard.
TargetProperty et Storyboard.TargetName. Un objet Storyboard ne peut cibler
d’une propriété d’un seul objet FrameworkElement.
La traduction en français du terme storyboard est table de montage séquentiel.
Le rôle de l’objet Storyboard peut être comparé à celui d’un séquenceur, et les
CHAPITRE 10 □ Les animations 271

animations peuvent être comparées aux pistes du séquenceur. Nous utiliserons


le terme de storyboard, plus souvent employé, plutôt que le terme de table de
montage séquentiel.
Un objet Storyboard dispose de méthodes permettant de contrôler l’animation
par le code. Ces méthodes sont:
• la méthode Begin qui démarre l’animation.
• la méthode Pause qui met l’animation en pause.
• la méthode Resume qui effectue une reprise de l’animation après la pause.
• la méthode Stop qui arrête l’animation.
FIGURE 10.1

2 - La classe Timeline

Toutes les animations ainsi que la classe Storyboard, héritent de la classe Timeline.
On peut traduire le terme Timeline par chronologie.
Un objet Timeline représente un segment de temps correspondant à l’exécution
d’une animation. Il dispose de propriétés permettant de définir sa durée, son
moment relatif au démarrage, s’il doit être répété, sa réversibilité, son facteur
272 Développez des applications Internet avec Silverlight 5
d’accélération, sa vitesse et son comportement en fin de séquence. De plus il
supporte l’imbrication.
Le Storyboard est un conteneur d’objets Timeline par sa propriété Children. Il est
donc possible d’imbriquer plusieurs Storyboard.
La durée de base d’un objet Timeline est déterminée par la propriété Duration, qui
peut être définie en XAML par une chaîne au format:
[jour.]heures:minutes[:secondes[.fractions]]
Duration = «0:0:0.8»
Duration = «00:5:20»
Un objet Duration peut être initialisé par le code au moyen d’un objet TimeSpan
passé au constructeur et obtenu à partir d’une méthode statique telle que
TimeSpan.FromSeconds.
Duration duree= new Duration(TimeSpan.FromSeconds(0.8));
La propriété BeginTime, de type TimeSpan, indique le temps à partir duquel
l’exécution commence. Ce temps est relatif au Storyboard conteneur, ou à son
déclenchement s’il s’agit de l’objet racine.
<Storyboard BeginTime= «0:0:8» />
La propriété RepeatBehavior permet de configurer la répétition d’un objet Timeline
ainsi que la façon de le répéter. Elle définit:
• soit le nombre d’exécutions de l’objet Timeline (propriété Count).
• soit la durée de l’exécution (propriété Duration); l’exécution est répétée tant
que cette durée n’est pas atteinte.
La propriété statique RepeatBehavior.Forever renvoie un objet RepeatBehavior
qui représente une durée de répétition infinie. Cet objet peut être assigné à la
propriété RepeatBehavior de l’objet Timeline pour répéter en boucle son exécution.
<Storyboard RepeatBehavior= «Forever» />
La propriété booléenne AutoReverse permet d’inverser l’exécution d’un objet
Timeline après une première exécution normale.
<Storyboard AutoReverse= «True» />
Copyright 2012 Patrice REY

La propriété SpeedRatio permet de faire varier la vitesse d’exécution d’un objet


Timeline par un facteur correspondant à sa valeur (1.0 par défaut) et relatif au
conteneur. Les facteurs des différents niveaux se multiplient entre eux: si un
conteneur a un SpeedRatio de 3.0 et que le storyboard imbriqué a un facteur de
2.0, le facteur résultant sera de 6.
La propriété énumérée FillBehavior détermine ce qu’il advient de la valeur de la
CHAPITRE 10 □ Les animations 273

propriété cible à l’issue d’une animation:


• la valeur HoldEnd (par défaut) indique que la valeur finale de l’animation est
conservée.
• la valeur Stop indique que la valeur initiale de la propriété cible avant l’animation
est restaurée.
<Storyboard FillBehavior= «Stop» />

3 - Une animation basique en XAML et par code

Le projet DemoAnimation.sln, dans le dossier chapitre10, contient tous les fichiers


des animations présentées dans ce chapitre. Nous développons ici une animation
basique dans le but de vous montrer les caractéristiques et la puissance des
animations sous Silverlight.
Le fichier AnimationBasique.xaml, dans le projet DemoAnimation.sln dans le
dossier chapitre10, présente une animation qui consiste à agrandir un contrôle
Image, puis à le réduire. La figure 10.2 illustre le résultat obtenu.
FIGURE 10.2

1 2

4 3
274 Développez des applications Internet avec Silverlight 5
D’un point de vue de l’organisation des contrôles, on positionne sur un Canvas
x_cnv_root les contrôles suivants:
• un bouton x_btn_anim1_agrandir qui permettra de lancer une animation pour
agrandir l’image à sa taille réelle.
• un bouton x_btn_anim1_reduire qui permettra de lancer une animation pour
réduire l’image à sa taille réduite initiale.
• un ScrollViewer x_scroll_tortue, avec ses barres de défilement, horizontale et
verticale, visibles.
• un contrôle Image, positionné à l’intérieur du x_scroll_tortue, avec un
alignement horizontal fixé à Left et un alignement vertical fixé à Top par rapport
à son conteneur parent; la propriété Source référence l’image intitulée tortue_
moorea.jpg (dans le dossier contenu) dont sa taille réelle est de 1024 par 768
pixels; ce contrôle a comme taille réduite de départ 204 par 152 pixels.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
...
<!-- animation 1 -->
<Line X1= «0» Y1= «0» X2= «600» Y2= «0» Stroke= «Black» Width= «600»
Height= «7» Canvas.Top= «356» StrokeDashArray= «4,2» Canvas.Left= «0»>
</Line>
<ScrollViewer Canvas.Left= «12» Canvas.Top= «41» Height= «309»
Name= «x_scroll_tortue» Width= «576»
HorizontalScrollBarVisibility= «Visible»>
<Image Height= «152» Name= «x_img_tortue» Stretch= «Fill» Width= «204»
HorizontalAlignment= «Left» VerticalAlignment= «Top»
Source= «/DemoAnimation;component/contenu/tortue_moorea.jpg» />
</ScrollViewer>
<Button Canvas.Left= «12» Canvas.Top= «12» Content= «agrandir image tortue»
Height= «23» Name= «x_btn_anim1_agrandir» Width= «168» FontFamily= «Verdana»
FontSize= «14» Click= «x_btn_anim1_Click» Cursor= «Hand»/>
<Button Canvas.Left= «420» Canvas.Top= «12» Content= «reduire image tortue»
FontFamily= «Verdana» FontSize= «14» Height= «23»
Name= «x_btn_anim1_reduire» Width= «168» Click= «x_btn_anim1_reduire_Click»
Cursor= «Hand»/>
</Canvas>
Un contrôle Image a une propriété Width et Height qui correspondent à la largeur
Copyright 2012 Patrice REY

et la hauteur. Ces propriétés sont de type double. Pour animer ces propriétés, il
faut utiliser une animation qui gère le type double, et donc une animation de type
DoubleAnimation.
Dans les ressources du Canvas (<Canvas.Resources>), on ajoute un Storyboard
intitulé x_story_agrandir_tortue qui consiste à faire passer la taille de l’image de sa
taille réduite à sa taille réelle. Pour animer la propriété Width du contrôle Image,
CHAPITRE 10 □ Les animations 275

on ajoute une animation de type DoubleAnimation qui cible x_img_tortue (donc


la propriété Storyboard.TargetName = «x_img_tortue») au travers de sa propriété
Width (donc la propriété Storyboard.TargetProperty = «Width»). Cette largeur
doit passer de 204 pixels à 1024 pixels, et on choisit un pas de 30 pixels (donc les
propriétés sont From = «204», To = «1024», et By = «30»). On souhaite que cette
chronologie dure 3 secondes (donc la propriété Duration = «0:0:3»).
Pour animer la hauteur de l’image, on procède de la même manière en ajoutant
une autre séquence DoubleAnimation dont la propriété TargetName est x_img_
tortue, la propriété TargetProperty est Height, pour une hauteur allant de 152 à
768 pixels par pas de 30 pixels, et pour une durée de 3 secondes.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<Canvas.Resources>
<Storyboard x:Name= «x_story_agrandir_tortue» >
<DoubleAnimation
Storyboard.TargetName= «x_img_tortue» Storyboard.TargetProperty= «Width»
From= «204» To= «1024» By= «30» Duration= «0:0:3»></DoubleAnimation>
<DoubleAnimation
Storyboard.TargetName= «x_img_tortue»
Storyboard.TargetProperty= «Height»
From= «152» To= «768» By= «30» Duration= «0:0:3»></DoubleAnimation>
</Storyboard>
...
</Canvas.Resources>
...
</Canvas>
Quand l’UserControl est chargé (événement Loaded), on actualise l’activité des
deux boutons de façon à ne pouvoir cliquer que sur un seul bouton simultanément
(agrandir ou réduire). Cliquer sur le bouton d’agrandissement consiste à
démarrer le storyboard x_story_agrandir_tortue par la méthode Begin. Quand
cette chronologie est terminée (3 secondes après), il faut pouvoir cliquer sur le
bouton de réduction (qui est inactif pour l’instant). On ajoute donc au storyboard
d’agrandissement, un gestionnaire pour l’événement Completed. Quand cet
événement se produit (c’est-à-dire que cette chronologie est terminée), on active
le bouton x_btn_anim1_reduire en passant sa propriété IsEnabled à true.
//evenement usercontrol loaded
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
x_btn_anim1_agrandir.IsEnabled = true;
x_btn_anim1_reduire.IsEnabled = false;
}
//animation 1: agrandir la tortue
private void x_btn_anim1_Click(object sender, RoutedEventArgs e) {
276 Développez des applications Internet avec Silverlight 5
x_btn_anim1_agrandir.IsEnabled = false;
x_story_agrandir_tortue.Begin();
x_story_agrandir_tortue.Completed +=
new EventHandler(x_story_agrandir_tortue_Completed);
}
//qd storyboard agrandir est termine
private void x_story_agrandir_tortue_Completed(object sender, EventArgs e) {
x_btn_anim1_reduire.IsEnabled = true;
}
L’implémentation de la chronologie pour la réduction de l’image se fait de la même
façon. On ajoute un Storyboard x_story_reduire_tortue qui anime les mêmes
propriétés. La seule différence est que l’on passe d’une taille de 1024x768 pixels
à une taille de 204x152 pixels. Il faut donc modifier l’incrément en le fixant à -30
(des pas de 30 pixels en réduction). Le clic sur le bouton de réduction permet de
démarrer ce storyboard.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<Canvas.Resources>
...
<Storyboard x:Name= «x_story_reduire_tortue» >
<DoubleAnimation
Storyboard.TargetName= «x_img_tortue» Storyboard.TargetProperty= «Width»
From= «1024» To= «204» By= «-30» Duration= «0:0:3»></DoubleAnimation>
<DoubleAnimation
Storyboard.TargetName= «x_img_tortue»
Storyboard.TargetProperty= «Height»
From= «768» To= «152» By= «-30» Duration= «0:0:3»></DoubleAnimation>
</Storyboard>
</Canvas.Resources>
...
</Canvas>
//animation 1: reduire la tortue
private void x_btn_anim1_reduire_Click(object sender, RoutedEventArgs e) {
x_btn_anim1_reduire.IsEnabled = false;
x_story_reduire_tortue.Begin();
x_story_reduire_tortue.Completed +=
new EventHandler(x_story_reduire_tortue_Completed);
Copyright 2012 Patrice REY

}
//qd storyboard reduire est termine
private void x_story_reduire_tortue_Completed(object sender, EventArgs e) {
x_btn_anim1_agrandir.IsEnabled = true;
}
Maintenant nous allons développer la même animation uniquement par code
de programmation (fichier AnimationBasiqueParCode.xaml, dans le projet
DemoAnimation.sln dans le dossier chapitre10). La figure 10.3 visualise l’obtention
CHAPITRE 10 □ Les animations 277

du même résultat.
FIGURE 10.3

Quand l’UserControl est chargé, on ajoute les deux Storyboard m_story_


agrandir et m_story_reduire par les méthodes AjouterStoryboardAgrandir et
AjouterStoryboardReduire.
//donnees
Storyboard m_story_agrandir = null;
Storyboard m_story_reduire = null;
//evenement usercontrol loaded
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
x_btn_anim1_agrandir.IsEnabled = true;
x_btn_anim1_reduire.IsEnabled = false;
AjouterStoryboardAgrandir();
AjouterStoryboardReduire();
}
On commence par instancier un nouvel objet Storyboard m_story_agrandir.
En ce qui concerne l’animation de la propriété Width, on instancie un objet
DoubleAnimation anim_larg. Ses propriétés From, To, By et Duration sont fixées.
On en fait de même avec un DoubleAnimation anim_haut pour animer la propriété
Height. On utilise la méthode statique Storyboard.SetTarget pour relier l’animation
anim_larg au contrôle x_img_tortue, et l’animation anim_haut au contrôle x_
img_tortue. On utilise la méthode statique Storyboard.SetTargetProperty pour
relier l’animation anim_larg à la propriété cible Width du contrôle, et l’animation
anim_haut à la propriété cible Height du contrôle. Pour cibler une propriété, il faut
utiliser un objet PropertyPath.
Dans le cadre d’une animation, un objet PropertyPath est affecté à la propriété
attachée Storyboard.TargetProperty pour définir la propriété ciblée. La classe
PropertyPath implémente une structure de données pour décrire une propriété
comme un chemin d’accès sous une autre propriété ou un type propriétaire. Les
278 Développez des applications Internet avec Silverlight 5
chemins de propriété sont utilisés dans la liaison de données aux objets, ainsi que
dans les storyboard et les chronologies pour les animations.
//
private void AjouterStoryboardAgrandir() {
m_story_agrandir = new Storyboard();
DoubleAnimation anim_larg = new DoubleAnimation();
anim_larg.From = 204;
anim_larg.To = 1024;
anim_larg.By = 30;
anim_larg.Duration = new Duration(TimeSpan.FromSeconds(3));
Storyboard.SetTarget(anim_larg, x_img_tortue);
Storyboard.SetTargetProperty(anim_larg, new PropertyPath(«Width»));
DoubleAnimation anim_haut = new DoubleAnimation();
anim_haut.From = 152;
anim_haut.To = 768;
anim_haut.By = 30;
anim_haut.Duration = new Duration(TimeSpan.FromSeconds(3));
Storyboard.SetTarget(anim_haut, x_img_tortue);
Storyboard.SetTargetProperty(anim_haut, new PropertyPath(«Height»));
...
}
Ensuite il faut ajouter les deux animations à la propriété Children du Storyboard
m_story_agrandir. Enfin il faut ajouter aux ressources de l’UserControl ou du
Canvas ici, le Storyboard par la propriété Resources à l’aide de la méthode Add. Ne
pas oublier que l’identificateur donné à une ressource doit être unique.
//
private void AjouterStoryboardAgrandir() {
...
m_story_agrandir.Children.Add(anim_larg);
m_story_agrandir.Children.Add(anim_haut);
x_cnv_root.Resources.Add(«x_story_agrandir», m_story_agrandir);
}
La procédure est identique pour le Storyboard m_story_reduire. Le démarrage des
storyboard peut être effectué par la méthode Begin dans les gestionnaires du clic
des boutons.
//
Copyright 2012 Patrice REY

private void AjouterStoryboardReduire() {


m_story_reduire = new Storyboard();
DoubleAnimation anim_larg = new DoubleAnimation();
anim_larg.From = 1024;
anim_larg.To = 204;
anim_larg.By = -30;
anim_larg.Duration = new Duration(TimeSpan.FromSeconds(3));
DoubleAnimation anim_haut = new DoubleAnimation();
CHAPITRE 10 □ Les animations 279

anim_haut.From = 768;
anim_haut.To = 152;
anim_haut.By = -30;
anim_haut.Duration = new Duration(TimeSpan.FromSeconds(3));
m_story_reduire.Children.Add(anim_larg);
m_story_reduire.Children.Add(anim_haut);
Storyboard.SetTarget(anim_larg, x_img_tortue);
Storyboard.SetTarget(anim_haut, x_img_tortue);
Storyboard.SetTargetProperty(anim_larg, new PropertyPath(«Width»));
Storyboard.SetTargetProperty(anim_haut, new PropertyPath(«Height»));
x_cnv_root.Resources.Add(«x_story_reduire», m_story_reduire);
}
//animation 1: agrandir la tortue
private void x_btn_anim1_agrandir_Click(object sender, RoutedEventArgs e) {
x_btn_anim1_agrandir.IsEnabled = false;
m_story_agrandir.Begin();
m_story_agrandir.Completed += new EventHandler(v_story_agrandir_Completed);
}
//qd storyboard agrandir est termine
private void v_story_agrandir_Completed(object sender, EventArgs e) {
x_btn_anim1_reduire.IsEnabled = true;
}
//animation 1: reduire la tortue
private void x_btn_anim1_reduire_Click(object sender, RoutedEventArgs e) {
x_btn_anim1_reduire.IsEnabled = false;
m_story_reduire.Begin();
m_story_reduire.Completed += new EventHandler(m_story_reduire_Completed);
}
//qd storyboard reduire est termine
private void m_story_reduire_Completed(object sender, EventArgs e) {
x_btn_anim1_agrandir.IsEnabled = true;
}

4 - Contrôler une animation

Le contrôle d’une animation est souvent réalisé par l’intermédiaire de boutons.


Le fichier ControlerDisque.xaml, dans le projet DemoAnimation.sln dans le dossier
chapitre10, illustre le contrôle d’une animation à l’aide de boutons.
Comme le visualise la figure 10.4, une image représentant un disque va être
animée par un Storyboard. L’animation consiste à faire tourner le disque sur lui-
même en faisant varier l’angle de rotation de la propriété RotationY d’un objet
PlaneProjection associé à la propriété Projection du contrôle Image.
Sur un Canvas x_cnv_root, on positionne un contrôle Image, dont la propriété
Source référence l’image disque_couleur.png, et dont la propriété Projection
est affectée d’un objet PlaneProjection intitulé x_projection. Quatre boutons
280 Développez des applications Internet avec Silverlight 5
permettent de contrôler l’animation: x_btn_demarrer pour démarrer l’animation,
x_btn_pause pour mettre en pause l’animation, x_btn_reprise pour reprendre le
cours de l’animation après une pause, et x_btn_arret pour stopper l’animation en
la remettant à zéro.
FIGURE 10.4

Copyright 2012 Patrice REY


CHAPITRE 10 □ Les animations 281

<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»


Height= «600»>
<!-- animation 1 -->
...
<Button Canvas.Left= «292» Canvas.Top= «57» Content= «Démarrer» Height= «29»
Name= «x_btn_demarrer» Width= «93»
FontFamily= «Verdana» FontSize= «14» Click= «x_btn_demarrer_Click»/>
<Image Canvas.Left= «28» Canvas.Top= «12» Height= «230» Name= «image1»
Stretch= «Fill» Width= «230»
Source= «/DemoAnimation;component/contenu/disque_couleur.png» >
<Image.Projection>
<PlaneProjection x:Name= «x_projection»></PlaneProjection>
</Image.Projection>
</Image>
<Ellipse Canvas.Left= «29» Canvas.Top= «12» Height= «229» Name= «ellipse1»
Stroke= «Black» StrokeThickness= «1»
Width= «229» StrokeDashArray= «5,2»/>
<Button Canvas.Left= «292» Canvas.Top= «92» Content= «Pause»
FontFamily= «Verdana» FontSize= «14» Height= «29»
Name= «x_btn_pause» Width= «93» Click= «x_btn_pause_Click»/>
<Button Canvas.Left= «292» Canvas.Top= «127» Content= «Reprise»
FontFamily= «Verdana» FontSize= «14» Height= «29»
Name= «x_btn_reprise» Width= «93» Click= «x_btn_reprise_Click» />
<Button Canvas.Left= «292» Canvas.Top= «162» Content= «Arrêt»
FontFamily= «Verdana» FontSize= «14» Height= «29»
Name= «x_btn_arret» Width= «93» Click= «x_btn_arret_Click» />
</Canvas>
Un Storyboard x_story_rotation est ajouté aux ressources du Canvas. Il est composé
d’une chronologie de type DoubleAnimation qui cible la propriété RotationY de la
projection x_projection. Cette animation fait tourner le disque sur 360 degrés, et
se répète indéfiniment (propriété RepeatBehavior fixée à Forever). La durée de
l’animation est fixée à 5 secondes (propriété Duration).
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<!-- animation 1 -->
<Canvas.Resources>
<Storyboard x:Name= «x_story_rotation»>
<DoubleAnimation From= «0» To= «360» Duration= «00:00:05»
RepeatBehavior= «Forever»
Storyboard.TargetName= «x_projection»
Storyboard.TargetProperty= «RotationY»></DoubleAnimation>
</Storyboard>
</Canvas.Resources>
...
</Canvas>
Les boutons doivent être actifs ou inactifs en fonction du déroulement de
282 Développez des applications Internet avec Silverlight 5
l’animation. La figure 10.5 montre l’état des boutons en fonction du cycle de
déroulement de l’animation. Quand l’UserControl est chargé (événement Loaded),
on actualise l’état d’activité des boutons.
//usercontrol charge
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
x_btn_demarrer.IsEnabled = true;
x_btn_pause.IsEnabled = false;
x_btn_reprise.IsEnabled = false;
x_btn_arret.IsEnabled = false;
}
FIGURE 10.5
Démarrer

Pause

Démarrer Reprise Démarrer

Pause Arrêt Pause

Reprise Reprise
Démarrer
Arrêt Arrêt
Pause

Reprise

Arrêt

Les méthodes Begin, Pause, Resume et Stop permettent de contrôler le déroulement


de l’animation. A chaque appui sur un bouton, on applique la méthode demandée
au x_story_rotation, et on actualise l’état d’activité des boutons en fonction du
cycle de déroulement illustré par la figure 10.5.
//btn demarrer
private void x_btn_demarrer_Click(object sender, RoutedEventArgs e) {
x_story_rotation.Begin();
x_btn_demarrer.IsEnabled = false;
x_btn_pause.IsEnabled = true;
Copyright 2012 Patrice REY

x_btn_reprise.IsEnabled = false;
x_btn_arret.IsEnabled = true;
}
//btn pause
private void x_btn_pause_Click(object sender, RoutedEventArgs e) {
x_story_rotation.Pause();
x_btn_demarrer.IsEnabled = false;
x_btn_pause.IsEnabled = false;
x_btn_reprise.IsEnabled = true;
CHAPITRE 10 □ Les animations 283

x_btn_arret.IsEnabled = true;
}
//btn reprise
private void x_btn_reprise_Click(object sender, RoutedEventArgs e) {
x_story_rotation.Resume();
x_btn_demarrer.IsEnabled = false;
x_btn_pause.IsEnabled = true;
x_btn_reprise.IsEnabled = false;
x_btn_arret.IsEnabled = true;
}
//btn arret
private void x_btn_arret_Click(object sender, RoutedEventArgs e) {
x_story_rotation.Stop();
x_btn_demarrer.IsEnabled = true;
x_btn_pause.IsEnabled = false;
x_btn_reprise.IsEnabled = false;
x_btn_arret.IsEnabled = false;
}

5 - Les animations réalistes

Les animations intègrent un mécanisme d’atténuation ou d’accélération progressive


permettant de simuler des phénomènes physiques tels que les rebonds d’une balle
ou la chute d’une feuille. Ce mécanisme nommé fonction d’accélération permet de
configurer la courbe d’interpolation d’une animation.

5.1 - Principe

Les animations exposent une propriété EasingFunction de type IEasingFunction.


L’interface IEasingFunction définit la fonctionnalité de base d’une fonction
d’accélération, en exposant la méthode Ease. L’implémentation de cette méthode
définit la fonction d’interpolation de l’animation, c’est-à-dire la façon dont la valeur
animée évolue durant l’animation.
Par exemple, dans une interpolation linéaire (figure 10.6), l’abscisse représente le
temps de l’animation, et l’ordonnée représente la différence entre la valeur initiale
et la valeur finale de la propriété cible. Les valeurs de l’abscisse et de l’ordonnée
sont normalisées entre 0 et 1.
L’interface IEasingFunction est implémentée par la classe abstraite
EasingFunctionBase dont héritent des classes qui implémentent des interpolations.
La classe EasingFunctionBase fournit la classe de base pour toutes les fonctions
d’accélération. Vous pouvez créer vos propres fonctions d’accélération
284 Développez des applications Internet avec Silverlight 5
personnalisées en héritant de cette classe.
FIGURE 10.6
Valeurs
= f(t)

Temps t

Les classes qui héritent de la classe abstraite EasingFunctionBase sont (figure


10.7):
• La classe BounceEase qui représente une fonction d’accélération qui crée un
effet de rebondissement animé.
• La classe BackEase qui représente une fonction d’accélération qui rétracte le
mouvement d’une animation un peu avant qu’elle ne commence à s’animer.
• La classe CircleEase qui représente une fonction d’accélération qui crée une
animation qui accélère et/ou ralentit à l’aide d’une fonction circulaire.
• La classe ElasticEase qui représente une fonction d’accélération qui crée une
animation qui ressemble à un ressort oscillant d’avant en arrière jusqu’à ce
qu’il se stabilise.
• La classe SineEase qui représente une fonction d’accélération qui crée une
animation qui accélère et/ou ralentit à l’aide d’une formule sinus.
• La classe ExponentialEase qui représente une fonction d’accélération qui crée
une animation qui accélère et/ou ralentit à l’aide d’une formule exponentielle.
• Les classes QuadraticEase, QuinticEase, QuarticEase, CubicEase, PowerEase
qui représentent des fonctions d’accélération qui créent une animation qui
accélère et/ou ralentit à l’aide d’une formule particulière.
Copyright 2012 Patrice REY

La classe EasingFunctionBase expose la propriété énumérée EasingMode qui


peut prendre les valeurs EaseIn, EaseOut et EaseInOut. Dans le mode EaseIn,
l’interpolation suit le modèle physique modélisé par la classe. Dans le mode
EaseOut, l’interpolation est inversée par rapport au modèle (mode par défaut).
Dans le mode EaseInOut, l’interpolation est en mode EaseIn pour la première
moitié et EaseOut pour la seconde. Les figures 10.8 et 10.9 illustrent les trois
modes pour la classe BounceEase.
CHAPITRE 10 □ Les animations 285

FIGURE 10.7

FIGURE 10.8

Valeurs Valeurs
mode EaseIn mode EaseOut
= f(t) = f(t)

Temps t Temps t
286 Développez des applications Internet avec Silverlight 5
FIGURE 10.9

Valeurs
= f(t)

mode EaseInOut

Temps t

5.2 - L’effet de rebondissement avec BounceEase

Le fichier RebondBalle.xaml, dans le projet DemoAnimation.sln dans le dossier


chapitre10, illustre l’animation d’une balle de tennis que l’on laisse tomber, et
dont la fonction d’accélération utilisée est celle d’un effet de rebondissement
animé (effet de la classe BounceEase). La figure 10.10 visualise le résultat obtenu.
La fonction d’accélération BounceEase (repère n°1) est utilisée dans son mode
d’interpolation par défaut (mode EaseOut). Cette fonction BounceEase reproduit
les effets d’un rebondissement animé (repère n°2 et n°3).
FIGURE 10.10

1 2 3

Copyright 2012 Patrice REY


CHAPITRE 10 □ Les animations 287

Sur le Canvas x_cnv_root, on positionne une image x_img_balle représentant


une balle de tennis (propriété Source qui référence l’image balle_tennis.png).
Le déplacement vertical de cette image le long d’une droite verticale, va être
animé par un storyboard avec l’effet BounceEase. Le rectangle x_rect_sol sert à
matérialiser le sol pour les rebonds de la balle.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<!-- animation 1 -->
...
<Line X1= «0» Y1= «0» X2= «0» Y2= «600» Stroke= «Black» Width= «2»
Height= «600» Canvas.Top= «12» StrokeDashArray= «4,2»
Canvas.Left= «67» StrokeThickness= «2»></Line>
<Button Canvas.Left= «166» Canvas.Top= «332» Content= «Jouer l’animation
(mode BounceEase)» Height= «29» Name= «x_btn_demarrer» Width= «408»
FontFamily= «Verdana» FontSize= «14» Click= «x_btn_demarrer_Click»
Cursor= «Hand» />
<Image Canvas.Left= «25» Canvas.Top= «20» Height= «80» Name= «x_img_balle»
Stretch= «Fill» Width= «80»
Source= «/DemoAnimation;component/contenu/balle_tennis.png» />
<Rectangle Canvas.Left= «12» Canvas.Top= «450» Height= «11»
Name= «x_rect_sol» Stroke= «Black» StrokeThickness= «1»
Width= «147» Fill= «DarkGray» />
<Image Canvas.Left= «166» Canvas.Top= «20» Height= «288» Name= «image1»
Stretch= «Fill» Width= «408»
Source= «/DemoAnimation;component/contenu/bounce_ease.jpg» />
</Canvas>
Dans les ressources du Canvas, on ajoute un Storyboard x_story_balle. La
propriété que l’on souhaite animer est la propriété attachée Canvas.Top de l’image
x_img_balle. Cette propriété étant de type double, on ajoute une animation
DoubleAnimation qui cible la propriété attachée Canvas.Top (TargetProperty)
de l’image x_img_balle (TargetName). La plage des valeurs de la propriété à
animer est comprise entre 20 et 370 pixels. La durée de l’animation est fixée à
trois secondes. Pour utiliser la fonction d’accélération prédéfinie BounceEase,
on ajoute un objet BounceEase à la propriété EasingFunction de l’animation
(<DoubleAnimation.EasingFunction>). Cette fonction d’accélération est fixée dans
son mode d’interpolation EaseOut (propriété EasingMode). La propriété Bounces
définit le nombre de rebonds (on la fixe à la valeur 3 qui est la valeur par défaut).
La propriété Bounciness définit une valeur qui spécifie le niveau d’élasticité de
l’animation de rebond. Des valeurs faibles affectées à cette propriété génèrent des
rebonds avec peu de perte de hauteur entre les rebonds (plus élastiques) alors que
des valeurs élevées entraînent des rebonds amortis (moins élastiques). On la fixe à
sa valeur par défaut (valeur de 2).
288 Développez des applications Internet avec Silverlight 5
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<!-- animation 1 -->
<Canvas.Resources>
<Storyboard x:Name= «x_story_balle» >
<DoubleAnimation
Storyboard.TargetName= «x_img_balle»
Storyboard.TargetProperty= «(Canvas.Top)»
From= «20» To= «370» Duration= «0:0:3»>
<DoubleAnimation.EasingFunction>
<BounceEase EasingMode= «EaseOut» Bounces= «3» Bounciness= «2»>
</BounceEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</Canvas.Resources>
...
</Canvas>
Dans le gestionnaire de l’événement Click du bouton, on démarre le Storyboard x_
story_balle par la méthode Begin. Et on ajoute un gestionnaire pour l’événement
Completed du storyboard (pour ne pas avoir la possibilité de cliquer sur le bouton
tant que l’animation n’est pas terminée).
//jouer l’animation
private void x_btn_demarrer_Click(object sender, RoutedEventArgs e) {
x_btn_demarrer.IsEnabled = false;
x_story_balle.Completed += new EventHandler(x_story_balle_Completed);
x_story_balle.Begin();
}
//evenement completed
private void x_story_balle_Completed(object sender, EventArgs e) {
x_btn_demarrer.IsEnabled = true;
}

5.3 - L’effet du ressort oscillant avec ElasticEase

La classe ElasticEase représente une fonction d’accélération qui crée une


animation qui ressemble à un ressort oscillant d’avant en arrière jusqu’à ce qu’il
Copyright 2012 Patrice REY

se stabilise. La figure 10.11 visualise cette fonction d’accélération dans ses trois
modes d’interpolation.
Le fichier RessortElastique.xaml, dans le projet DemoAnimation.sln dans le dossier
chapitre10, illustre l’animation d’un ressort oscillant (figure 10.12). Cette animation
utilise l’effet ElasticEase (repère n°1) avec son mode d’interpolation par défaut
(EaseOut). Quand le ressort est lâché, les oscillations se produisent en fonction des
CHAPITRE 10 □ Les animations 289

valeurs fournies par l’effet ElasticEase (repère n°2 et n°3).


FIGURE 10.11

Valeurs Valeurs
mode EaseIn mode EaseOut
= f(t) = f(t)

Temps t Temps t

Valeurs
= f(t)

mode EaseInOut

Temps t
FIGURE 10.12

2 3
290 Développez des applications Internet avec Silverlight 5
Sur le Canvas x_cnv_root, on positionne une image x_img_ressort représentant
un ressort accroché par le haut et tendu par le bas (propriété Source qui référence
l’image ressort.png). Une fois le ressort lâché, le bas du ressort va remonter vers
le haut puis redescendre vers le bas, en effectuant des oscillations. Ces oscillations
vont être animées par un storyboard avec l’effet ElasticEase. Le rectangle x_rect_
sol sert à matérialiser le bas où le ressort est attaché au départ.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<!-- animation 1 -->
...
<Line X1= «0» Y1= «0» X2= «0» Y2= «600» Stroke= «Black» Width= «2»
Height= «600» Canvas.Top= «12» StrokeDashArray= «4,2»
Canvas.Left= «67» StrokeThickness= «2»></Line>
<Button Canvas.Left= «166» Canvas.Top= «332» Content= «Jouer l’animation
(mode ElasticEase)» Height= «29» Name= «x_btn_demarrer» Width= «408»
FontFamily= «Verdana» FontSize= «14» Click= «x_btn_demarrer_Click»
Cursor= «Hand» />
<Image Canvas.Left= «25» Canvas.Top= «20» Height= «424» Name= «x_img_ressort»
Stretch= «Fill» Width= «80»
Source= «/DemoAnimation;component/contenu/ressort.png» />
<Rectangle Canvas.Left= «16» Canvas.Top= «450» Height= «11»
Name= «x_rect_sol»
Stroke= «Black» StrokeThickness= «1»
Width= «102» Fill= «DarkGray» />
<Image Canvas.Left= «166» Canvas.Top= «20» Height= «288» Name= «image1»
Stretch= «Fill» Width= «408»
Source= «/DemoAnimation;component/contenu/elastic_ease.jpg» />
</Canvas>
Dans les ressources du Canvas, on ajoute un Storyboard x_story_ressort. La
propriété que l’on souhaite animer est la propriété Height de l’image x_img_ressort
(pour simuler des oscillations). Cette propriété étant de type double, on ajoute
une animation DoubleAnimation qui cible la propriété Height (TargetProperty) de
l’image x_img_ressort (TargetName). La plage des valeurs de la propriété à animer
est comprise entre 424 et 200 pixels (amplitude dans laquelle les oscillations sont
effectuées pour la simulation). La durée de l’animation est fixée à trois secondes.
Pour utiliser la fonction d’accélération prédéfinie ElasticEase, on ajoute un objet
Copyright 2012 Patrice REY

ElasticEase à la propriété EasingFunction de l’animation (<DoubleAnimation.


EasingFunction>). Cette fonction d’accélération est fixée dans son mode
d’interpolation EaseOut (propriété EasingMode). La propriété Oscillations définit le
nombre de fois où la cible glisse d’avant en arrière sur la destination de l’animation
(elle est fixée à 3, sa valeur par défaut). La propriété Springiness définit la rigidité
du ressort. Plus la valeur de souplesse est faible, plus le ressort devient rigide et
CHAPITRE 10 □ Les animations 291

plus l’élasticité diminue rapidement en intensité avec chaque oscillation. Sa valeur


est fixée à 3 (valeur par défaut).
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<!-- animation 1 -->
<Canvas.Resources>
<Storyboard x:Name= «x_story_ressort» >
<DoubleAnimation
Storyboard.TargetName= «x_img_ressort»
Storyboard.TargetProperty= «Height»
From= «424» To= «200» Duration= «0:0:3»>
<DoubleAnimation.EasingFunction>
<ElasticEase EasingMode= «EaseOut» Oscillations= «3»
Springiness= «3»></ElasticEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</Canvas.Resources>
...
</Canvas>
Dans le gestionnaire de l’événement Click du bouton, on démarre le Storyboard x_
story_ressort par la méthode Begin. Et on ajoute un gestionnaire pour l’événement
Completed du storyboard (pour ne pas avoir la possibilité de cliquer de nouveau
sur le bouton tant que l’animation n’est pas terminée).
//btn jouer
private void x_btn_demarrer_Click(object sender, RoutedEventArgs e) {
x_btn_demarrer.IsEnabled = false;
x_story_ressort.Begin();
x_story_ressort.Completed += new EventHandler(x_story_ressort_Completed);
}
//evenement completed
private void x_story_ressort_Completed(object sender, EventArgs e) {
x_btn_demarrer.IsEnabled = true;
}

5.4 - L’effet du freinage avec PowerEase

La classe PowerEase représente une fonction d’accélération qui crée une animation
qui accélère et/ou ralentit à l’aide de la formule f(t) = tp où p est égal à la propriété
Power. La figure 10.13 visualise cette fonction d’accélération dans ses trois modes
d’interpolation.
Le fichier Freinage.xaml, dans le projet DemoAnimation.sln dans le dossier
chapitre10, illustre l’animation d’une voiture qui freine pour éviter de percuter un
292 Développez des applications Internet avec Silverlight 5
mur (figure 10.14). Cette animation utilise l’effet PowerEase (repère n°1) avec son
mode d’interpolation par défaut (EaseOut). Quand la voiture s’approche du mur,
elle freine en fonction des valeurs fournies par l’effet PowerEase (repère n°2 et
n°3).
FIGURE 10.13

Valeurs Valeurs
mode EaseIn mode EaseOut
= f(t) = f(t)

Temps t Temps t

Valeurs
= f(t)

mode EaseInOut

Temps t

Sur le Canvas x_cnv_root, on positionne une image x_img_voiture représentant une


voiture qui freine à l’approche d’un mur. Ce freinage est animé par un storyboard
avec l’effet PowerEase. Le rectangle x_rect_mur sert à matérialiser le mur vertical
devant lequel la voiture doit s’arrêter.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<!-- animation 1 -->
Copyright 2012 Patrice REY

...
<Line X1= «0» Y1= «0» X2= «600» Y2= «0» Stroke= «Black» Width= «600»
Height= «2» Canvas.Top= «80» StrokeDashArray= «4,2»
Canvas.Left= «0» StrokeThickness= «2»></Line>
<Button Canvas.Left= «101» Canvas.Top= «110» Content= «Jouer l’animation
(mode PowerEase)» Height= «29» Name= «x_btn_demarrer» Width= «408»
FontFamily= «Verdana» FontSize= «14» Click= «x_btn_demarrer_Click»
Cursor= «Hand» />
<Image Canvas.Left= «14» Canvas.Top= «35» Height= «46» Name= «x_img_voiture»
CHAPITRE 10 □ Les animations 293
FIGURE 10.14

Stretch= «Fill» Width= «111»


Source= «/DemoAnimation;component/contenu/voiture.png» />
<Image Canvas.Left= «101» Canvas.Top= «159» Height= «288» Name= «image1»
Stretch= «Fill» Width= «408»
Source= «/DemoAnimation;component/contenu/power_ease.jpg» />
<Rectangle Canvas.Left= «576» Canvas.Top= «10» Height= «71»
Name= «x_rect_mur»
Stroke= «Black» StrokeThickness= «1»
Width= «11» Fill= «DarkGray» />
</Canvas>
Dans les ressources du Canvas, on ajoute un Storyboard x_story_voiture. La
propriété que l’on souhaite animée est la propriété attachée Canvas.Left de l’image
x_img_voiture (pour simuler le freinage). Cette propriété étant de type double, on
294 Développez des applications Internet avec Silverlight 5
ajoute une animation DoubleAnimation qui cible la propriété attachée Canvas.Left
(TargetProperty) de l’image x_img_voiture (TargetName). La plage des valeurs de
la propriété à animer est comprise entre 14 et 463 pixels. La durée de l’animation
est fixée à trois secondes. Pour utiliser la fonction d’accélération prédéfinie
PowerEase, on ajoute un objet PowerEase à la propriété EasingFunction de
l’animation (<DoubleAnimation.EasingFunction>). Cette fonction d’accélération est
fixée dans son mode d’interpolation EaseOut (propriété EasingMode). La propriété
Power définit la puissance exponentielle de l’interpolation de l’animation. On fixe
sa valeur à 2 (valeur par défaut).
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<!-- animation 1 -->
<Canvas.Resources>
<Storyboard x:Name= «x_story_voiture» >
<DoubleAnimation
Storyboard.TargetName= «x_img_voiture»
Storyboard.TargetProperty= «(Canvas.Left)»
From= «14» To= «463» Duration= «0:0:3»>
<DoubleAnimation.EasingFunction>
<PowerEase EasingMode= «EaseOut» Power= «2»></PowerEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</Canvas.Resources>
...
</Canvas>
Dans le gestionnaire de l’événement Click du bouton, on démarre le Storyboard x_
story_voiture par la méthode Begin. Et on ajoute un gestionnaire pour l’événement
Completed du storyboard (pour ne pas avoir la possibilité de cliquer de nouveau
sur le bouton tant que l’animation n’est pas terminée).
//btn jouer
private void x_btn_demarrer_Click(object sender, RoutedEventArgs e) {
x_btn_demarrer.IsEnabled = false;
x_story_voiture.Begin();
x_story_voiture.Completed += new EventHandler(x_story_voiture_Completed);
Copyright 2012 Patrice REY

}
//evenement completed
private void x_story_voiture_Completed(object sender, EventArgs e) {
x_btn_demarrer.IsEnabled = true;
}
CHAPITRE 10 □ Les animations 295

5.5 - Fonction d’accélération personnalisée

Une classe d’interpolation personnalisée peut être créée en dérivant la classe


EasingFunctionBase et en redéfinissant la méthode EaseInCore. Celle-ci reçoit en
paramètre le temps sous une forme normalisée (valeur entre 0 et 1, l’étendue pour
aller de 0 à 1 représentant la durée de l’animation). Elle doit renvoyer une valeur
normalisée (entre 0 et 1) qui s’applique à la valeur de la propriété animée en tenant
compte des propriétés From, To et By. Seul le mode EaseIn doit être implémenté
au moyen de cette fonction. Les modes EaseOut et EaseInOut sont déterminés
automatiquement par la méthode Ease de la classe EasingFunctionBase. Par
exemple une fonction linéaire s’obtient en renvoyant la valeur passée:
public class FonctionLineaire : EasingFunctionBase {
protected override double EaseInCore(double normalizedTime) {
return normalizedTargetValue;
}
}
Par exemple une fonction par palier (figure 10.15) s’obtient en renvoyant une
valeur qui passe par une suite de montée suivie de palier. On déclare une classe
FonctionPallier qui hérite de EasingFunctionBase. La méthode EaseInCore doit
être redéfinie pour fournir la valeur normalisée de la fonction. La déclaration de la
classe est:
public class FonctionPallier : EasingFunctionBase {
//redefinition de la methode
protected override double EaseInCore(double normalizedTime) {
...
return normalizedTime;
}
}//end class

FIGURE 10.15

Valeurs
= f(t) mode EaseIn

Temps t
296 Développez des applications Internet avec Silverlight 5
Nous avons besoin de propriétés pour gérer les montées et les paliers. On ajoute
une propriété de dépendance NombrePalier qui représentera le nombre de paliers
sur la durée de l’animation (ici une valeur de 4 par défaut). On ajoute une propriété
de dépendance SeuilPalier qui représentera la valeur du seuil du palier (ici une
valeur de 0.25 par défaut). La méthode EaseInCore calcule alors, en fonction de
normalizedTime, la valeur, de type double, à appliquer.
public class FonctionPallier : EasingFunctionBase {
//redefinition de la methode
protected override double EaseInCore(double normalizedTime) {
double normalizedTargetValue = 1.0;
for (int i = 0; i < NombrePalier; i++) {
double v_palier_depart = (double)i / NombrePalier;
double v_palier_longeur = 1.0 / NombrePalier;
double thresholdStart = v_palier_depart + LongueurPalier *
v_palier_longeur;
double v_palier_fin = v_palier_depart + v_palier_longeur;
if ((normalizedTime >= v_palier_depart) &&
(normalizedTime < thresholdStart)) {
normalizedTargetValue = v_palier_depart +
(normalizedTime - v_palier_depart) / LongueurPalier;
break;
}
if ((normalizedTime >= thresholdStart) &&
(normalizedTime < v_palier_fin)) {
normalizedTargetValue = v_palier_fin;
break;
}
}
return normalizedTargetValue;
}
//propriete de dependance
private static readonly DependencyProperty NombrePalierPropriete =
DependencyProperty.Register(
«NombrePalier»,
typeof(int),
typeof(FonctionPallier),
new PropertyMetadata(4));
public int NombrePalier {
Copyright 2012 Patrice REY

get {
return (int)base.GetValue(NombrePalierPropriete);
}
set {
base.SetValue(NombrePalierPropriete, value);
}
}
//propriete de dependance
private static readonly DependencyProperty SeuilPalierPropriete =
CHAPITRE 10 □ Les animations 297

DependencyProperty.Register(
«SeuilPalier»,
typeof(double),
typeof(FonctionPallier),
new PropertyMetadata(0.25));
public double SeuilPalier {
get {
return (double)base.GetValue(SeuilPalierPropriete);
}
set {
base.SetValue(SeuilPalierPropriete, value);
}
}
}//end class
Le fichier FonctionPersonnalisee.xaml, dans le projet DemoAnimation.sln dans
le dossier chapitre10, illustre une animation dans laquelle des images tournent
autour de l’axe X (figure 10.16). Cette animation utilise une fonction d’accélération
par palier FonctionPallier avec son mode d’interpolation EaseIn (repère n°1). Le
repère n°2 montre quelques clichés pour le passage de la première à la deuxième
image. Le repère n°3 montre le positionnement de ces clichés sur la courbe à
paliers.
On positionne sur le Canvas x_cnv_root, quatre images x_img1, x_img2, x_
img3 et x_img4. Ces quatre images ont une propriété Projection fixée avec un
PlaneProjection, dont la propriété RotationX est fixée par les valeurs de 0, 90, 180
et 270 respectivement.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<!-- animation 1 -->
...
<Image Name= «x_img1» Stretch= «Fill»
Source= «/DemoAnimation;component/contenu/poisson_1.png»
Canvas.Left= «201» Canvas.Top= «47»>
<Image.Projection>
<PlaneProjection CenterOfRotationZ= «-100» RotationX= «0»/>
</Image.Projection>
</Image>
<Image Name= «x_img2» Stretch= «Fill»
Source= «/DemoAnimation;component/contenu/poisson_2.png»
Canvas.Left= «201» Canvas.Top= «47»>
<Image.Projection>
<PlaneProjection CenterOfRotationZ= «-100» RotationX= «90»/>
</Image.Projection>
</Image>
<Image Name= «x_img3» Stretch= «Fill»
Source= «/DemoAnimation;component/contenu/poisson_3.png»
298 Développez des applications Internet avec Silverlight 5
FIGURE 10.16

f(t)
mode EaseIn
Copyright 2012 Patrice REY

Temps t
CHAPITRE 10 □ Les animations 299

Canvas.Left= «201» Canvas.Top= «47»>


<Image.Projection>
<PlaneProjection CenterOfRotationZ= «-100» RotationX= «180»/>
</Image.Projection>
</Image>
<Image Name= «x_img4» Stretch= «Fill»
Source= «/DemoAnimation;component/contenu/poisson_4.png»
Canvas.Left= «201» Canvas.Top= «47»>
<Image.Projection>
<PlaneProjection CenterOfRotationZ= «-100» RotationX= «270»/>
</Image.Projection>
</Image>
<Button Canvas.Left= «72» Canvas.Top= «244» Content= «Jouer l’animation (avec
fonction personnalisée par pallier)» Height= «29» Name= «x_btn_demarrer»
Width= «464»
FontFamily= «Verdana» FontSize= «14» Click= «x_btn_demarrer_Click»
Cursor= «Hand» />
<Image Canvas.Left= «72» Canvas.Top= «279» Height= «292» Name= «image»
Stretch= «Fill» Width= «464»
Source= «/DemoAnimation;component/contenu/palier_ease.jpg» />
</Canvas>
Aux ressources du Canvas, on ajoute un Storyboard x_story_img qui est composé
de 4 pistes de type DoubleAnimation. Les animations x_anim1, x_anim2, x_anim3
et x_anim4 ciblent la propriété RotationX de chaque image (TargetProperty a pour
valeur (Image.Projection).(RotationX)).
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<!-- animation 1 -->
<Canvas.Resources>
<Storyboard x:Name= «x_story_img»>
<DoubleAnimation
x:Name= «x_anim1»
Storyboard.TargetName= «x_img1»
Storyboard.TargetProperty= «(Image.Projection).(RotationX)»
Duration= «0:0:20»
From= «0» To= «360» />
<DoubleAnimation
x:Name= «x_anim2»
Storyboard.TargetName= «x_img2»
Storyboard.TargetProperty= «(Image.Projection).(RotationX)»
Duration= «0:0:20»
From= «90» To= «450» />
<DoubleAnimation
x:Name= «x_anim3»
Storyboard.TargetName= «x_img3»
Storyboard.TargetProperty= «(Image.Projection).(RotationX)»
Duration= «0:0:20»
300 Développez des applications Internet avec Silverlight 5
From= «180» To= «550» />
<DoubleAnimation
x:Name= «x_anim4»
Storyboard.TargetName= «x_img4»
Storyboard.TargetProperty= «(Image.Projection).(RotationX)»
Duration= «0:0:20»
From= «270» To= «630»/>
</Storyboard>
</Canvas.Resources>
...
</Canvas>
Dans le gestionnaire de l’événement Click du bouton, on instancie un objet fonction_
acc de type FonctionPallier. On fixe son mode d’interpolation à EasingMode.
EaseIn. On assigne à la propriété EasingFunction des quatre animations l’objet
fonction_acc. A partir de là on peut démarrer le storyboard.
//btn jouer
private void x_btn_demarrer_Click(object sender, RoutedEventArgs e) {
x_btn_demarrer.IsEnabled = false;
FonctionPallier fonction_acc = new FonctionPallier();
fonction_acc.EasingMode = EasingMode.EaseIn;
x_story_img.Stop();
x_anim1.EasingFunction = fonction_acc;
x_anim2.EasingFunction = fonction_acc;
x_anim3.EasingFunction = fonction_acc;
x_anim4.EasingFunction = fonction_acc;
x_story_img.Begin();
x_story_img.Completed += new EventHandler(x_story_img_Completed);
}
//
private void x_story_img_Completed(object sender, EventArgs e) {
x_btn_demarrer.IsEnabled = true;
}

6 - Les animations d’images clés

Une animation basique utilise deux valeurs cibles, avec une valeur de début
définie par From, et une valeur de fin définie par To. Une animation d’images clés
Copyright 2012 Patrice REY

(KeyFrame) permet d’augmenter le nombre de valeurs cibles avec la première


valeur correspondant à la valeur de début, la dernière valeur correspondant à la
valeur de fin, et les autres valeurs correspondant à des valeurs intermédiaires.
Chaque valeur est associée à un instant donné. L’animation définit une série de
transitions successives entre chacune de ces valeurs aux instants indiqués.
CHAPITRE 10 □ Les animations 301

Les classes d’animations d’images clés sont:


• DoubleAnimationUsingKeyFrames,
• PointAnimationUsingKeyFrames,
• ColorAnimationUsingKeyFrames,
• ObjectAnimationUsingKeyFrames.
Les valeurs sont définies au moyen d’objets KeyFrame qui déterminent également
le mode d’interpolation avec la valeur précédente. Les objets KeyFrame (figure
10.17) sont représentés par les classes abstraites DoubleKeyFrame, PointKeyFrame,
ColorKeyFrame et ObjectKeyFrame.
FIGURE 10.17

Les classes dérivées ont leur nom qui répondent au modèle suivant:
<interpolation><type>KeyFrame.
Par exemple la classe LinearDoubleKeyFrame définit une valeur cible pour une
propriété de type double avec une méthode d’interpolation linéaire. Si la propriété
cible est de type Color, ce sera la classe LinearColorKeyFrame. Si la propriété cible
302 Développez des applications Internet avec Silverlight 5
est de type Point, ce sera la classe LinearPointKeyFrame.
Par exemple la classe DiscreteObjectKeyFrame définit une valeur cible pour une
propriété de type object avec une méthode d’interpolation par palier (Discrete).
Si la propriété cible est de type Color, ce sera la classe DiscreteColorKeyFrame. Si
la propriété cible est de type Point, ce sera la classe DiscretePointKeyFrame. Si la
propriété cible est de type double, ce sera la classe DiscreteDoubleKeyFrame.
Par exemple la classe EasingColorKeyFrame définit une valeur cible pour une
propriété de type Color avec une méthode d’interpolation réaliste (Easing). Si la
propriété cible est de type double, ce sera la classe EasingDoubleKeyFrame. Si la
propriété cible est de type Point, ce sera la classe EasingPointKeyFrame.
Par exemple la classe SplineColorKeyFrame définit une valeur cible pour
une propriété de type Color avec une méthode d’interpolation en courbe
de Bézier (Spline). Si la propriété cible est de type double, ce sera la classe
SplineDoubleKeyFrame. Si la propriété cible est de type Point, ce sera la classe
SplinePointKeyFrame.
Un objet KeyFrame définit sa valeur au moyen de la propriété Value, et l’instant
auquel la valeur doit être atteinte dans sa propriété KeyTime (qui s’exprime sous
la forme d’un objet TimeSpan). L’animation regroupe les objets KeyFrame dans sa
propriété KeyFrames. Si le premier objet KeyFrame n’est pas sur le temps zéro, une
transition est automatiquement effectuée depuis la valeur courante.

6.1 - Avec l’interpolation linéaire

Le fichier AnimImgCleLineaire.xaml, dans le projet DemoAnimation.sln dans le


dossier chapitre10, illustre une animation utilisant des images clés (figure 10.18).
Sur une chronologie de 4 secondes, un rectangle x_rect se déplace horizontalement
(propriété Canvas.Left) et sa couleur de remplissage (propriété Fill) se modifie.
Dans les ressources du Canvas x_cnv_root, on ajoute un Storyboard x_story_rect
composé de deux animations d’images clés. Pour faire varier la couleur de
remplissage du rectangle, on utilise une animation ColorAnimationUsingKeyFrames,
intitulée x_anim_color. Elle cible la propriété Color du SolidColorBrush de la
Copyright 2012 Patrice REY

propriété Fill du rectangle (TargetProperty = «(Rectangle.Fill).(SolidColorBrush.


Color)»).
Une interpolation linéaire, pour animer une propriété de type Color, se fera par
un objet LinearColorKeyFrame. A l’instant zéro (KeyTime = «00:00:00»), la valeur
du LinearColorKeyFrame sera de DarkGray. A l’instant deux secondes (KeyTime
= «00:00:02»), la valeur du LinearColorKeyFrame sera de LightGray. A l’instant
CHAPITRE 10 □ Les animations 303

quatre secondes (KeyTime = «00:00:04»), la valeur du LinearColorKeyFrame sera


de SlateGray.
FIGURE 10.18

<Canvas.Resources>
<Storyboard x:Name= «x_story_rect»>
<ColorAnimationUsingKeyFrames x:Name= «x_anim_color»
Storyboard.TargetName= «x_rect»
Storyboard.TargetProperty= «(Rectangle.Fill).(SolidColorBrush.Color)»>
<LinearColorKeyFrame KeyTime= «00:00:00» Value= «DarkGray»>
</LinearColorKeyFrame>
<LinearColorKeyFrame KeyTime= «00:00:02» Value= «LightGray»>
</LinearColorKeyFrame>
<LinearColorKeyFrame KeyTime= «00:00:04» Value= «SlateGray»>
</LinearColorKeyFrame>
</ColorAnimationUsingKeyFrames>
...
</Storyboard>
</Canvas.Resources>
304 Développez des applications Internet avec Silverlight 5
Pour faire varier la position horizontale du rectangle (propriété Canvas.Left de type
double), on utilise une animation DoubleAnimationUsingKeyFrames, intitulée
x_anim_left. Elle cible la propriété Canvas.Left du rectangle (TargetProperty =
«(Canvas.Left)»). Une interpolation linéaire, pour animer une propriété de type
double, se fera par un objet LinearDoubleKeyFrame. A l’instant zéro (KeyTime
= «00:00:00»), la valeur du LinearDoubleKeyFrame sera de 12. A l’instant
deux secondes (KeyTime = «00:00:02»), la valeur du LinearDoubleKeyFrame
sera de 200. A l’instant quatre secondes (KeyTime = «00:00:04»), la valeur du
LinearDoubleKeyFrame sera de 450.
<Canvas.Resources>
<Storyboard x:Name= «x_story_rect»>
...
<DoubleAnimationUsingKeyFrames x:Name= «x_anim_left»
Storyboard.TargetName= «x_rect»
Storyboard.TargetProperty= «(Canvas.Left)»>
<LinearDoubleKeyFrame KeyTime= «00:00:00» Value= «12»>
</LinearDoubleKeyFrame>
<LinearDoubleKeyFrame KeyTime= «00:00:02» Value= «200»>
</LinearDoubleKeyFrame>
<LinearDoubleKeyFrame KeyTime= «00:00:04» Value= «450»>
</LinearDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Canvas.Resources>

6.2 - Avec l’interpolation par palier

La méthode d’interpolation par palier (Discrete) définit un changement de valeur


sans transition à chaque image clé. La valeur ne subit aucune modification entre
deux affectations de la propriété Value des objets KeyFrame. A noter que la durée
de l’animation doit être supérieure au KeyTime de la dernière valeur pour que
celle-ci soit effective.
Cette méthode d’interpolation est également applicable au type double par
l’intermédiaire de la classe DiscreteDoubleKeyFrame, et aux coordonnées
Copyright 2012 Patrice REY

d’un point avec la classe DiscretePointKeyFrame. Elle permet aussi d’intégrer


dans un scénario d’animation des variations de propriétés non modifiables
progressivement, telles que des propriétés énumérées, au moyen de la classe
DiscreteObjectKeyFrame.
Le fichier AnimImgCleParPalier.xaml, dans le projet DemoAnimation.sln dans le
dossier chapitre10, illustre une animation utilisant des images clés (figure 10.19).
Sur une chronologie de 8 secondes, les quatre Canvas, contenant chacun un
CHAPITRE 10 □ Les animations 305

contrôle TextBlock et un contrôle Image, sont visibles durant 2 secondes chacun


à leur tour.
FIGURE 10.19
306 Développez des applications Internet avec Silverlight 5
Sur le Canvas x_cnv_root sont positionnés quatre Canvas, x_cnv_france, x_cnv_
usa, x_cnv_japon et x_cnv_canada, avec à l’intérieur de chacun leur drapeau et le
nom de leur pays.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<!-- animation 1 -->
<Canvas Canvas.Left= «202» Canvas.Top= «12» Height= «67»
Name= «x_cnv_france» Width= «253»>
...
</Canvas>
<Canvas Canvas.Left= «202» Canvas.Top= «85» Height= «67» Name= «x_cnv_usa»
Width= «253»>
...
</Canvas>
<Canvas Canvas.Left= «202» Canvas.Top= «155» Height= «67»
Name= «x_cnv_japon» Width= «253»>
...
</Canvas>
<Canvas Canvas.Left= «202» Canvas.Top= «228» Height= «67»
Name= «x_cnv_canada» Width= «253»>
...
</Canvas>
</Canvas>
La propriété Visibility d’un contrôle est d’un type énuméré (Visible ou
Collapsed). Pour animer une telle propriété, il faut utiliser une animation de
type ObjectAnimationUsingKeyFrames. Cette animation cible la propriété
Visibility (TargetProperty) d’un contrôle (TargetName). Chaque image clé,
de type DiscreteObjectKeyFrame, indique l’instant donné par sa propriété
KeyTime, et sa valeur en affectant un objet Visibility (<DiscreteObjectKeyFrame.
Value>). Si le contrôle doit être visible, alors on écrit l’énumération avec sa valeur
<Visibility>Visible</Visibility>, et inversement pour être non visible.
<Canvas.Resources>
<Storyboard x:Name= «x_story_drapeau» >
<ObjectAnimationUsingKeyFrames x:Name= «x_anim_france» Duration= «00:00:08»
Storyboard.TargetName= «x_cnv_france»
Storyboard.TargetProperty= «Visibility»>
Copyright 2012 Patrice REY

<DiscreteObjectKeyFrame KeyTime= «00:00:00»>


<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime= «00:00:02»>
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
CHAPITRE 10 □ Les animations 307

</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames x:Name= «x_anim_usa» Duration= «00:00:08»
Storyboard.TargetName= «x_cnv_usa»
Storyboard.TargetProperty= «Visibility»>
<DiscreteObjectKeyFrame KeyTime= «00:00:00»>
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime= «00:00:02»>
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime= «00:00:04»>
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
...
</Storyboard>
</Canvas.Resources>

6.3 - Avec l’interpolation par courbe de Bézier

La méthode d’interpolation par courbe de Bézier utilise l’équation d’une courbe de


Bézier cubique pour déterminer ses valeurs instantanées de transition.
Une courbe de Bézier cubique est constituée d’un point initial et d’un point final,
chacun associé à un point de contrôle. Le point de contrôle définit une tangente
fictive non visuelle qui détermine la courbure au niveau du point d’extrémité.
L’amplitude de la courbure est d’autant plus importante que ces deux points sont
écartés.
La courbe d’interpolation d’un objet d’animation Spline se définit au moyen
d’un objet de type KeySpline spécifié dans la propriété du même nom. Un objet
KeySpline définit une courbe de Bézier fictive avec un point initial de coordonnées
(0,0) et un point final de coordonnées (1,1), et dont les points de contrôle sont
paramétrables au moyen des propriétés ControlPoint1 et ControlPoint2, de type
Point. La forme de la courbe obtenue définit le taux d’accroissement de la valeur
instantanée. L’abscisse X représente la proportion du temps et l’ordonnée Y
représente la proportion de la valeur. Une pente forte induira un accroissement
rapide de la valeur instantanée et un plateau induira un effet inverse.
308 Développez des applications Internet avec Silverlight 5
FIGURE 10.20

(1,0)

(1,0.5)
(0,0.5)

(0,1)

Copyright 2012 Patrice REY


CHAPITRE 10 □ Les animations 309

Le fichier AnimImgCleParCourbe.xaml, dans le projet DemoAnimation.sln dans le


dossier chapitre10, illustre une animation utilisant des images clés (figure 10.20).
Le déplacement horizontal de la fusée se fait à une vitesse qui est fonction de la
courbe d’interpolation. On utilise la courbe de gauche pour la première moitié du
trajet, et la courbe de droite pour la seconde moitié du trajet. Sur la figure 10.20,
quelques points de référence sur ces deux courbes sont marqués et fléchés par
rapport au déplacement de la fusée.
L’animation dure dix secondes. La propriété à animer est Canvas.Left, de type
double. On utilise donc un objet DoubleAnimationUsingKeyFrames. Les images
clés pour animer une propriété de type double avec une interpolation par courbe
de Bézier sont du type SplineDoubleKeyFrame.
Au bout des cinq premières secondes, la fusée a parcouru la moitié du chemin. Sa
position horizontale est Canvas.Left = 196. Les points de contrôle qui élaborent la
courbe d’interpolation sont KeySpline = «1,0 0,1» (comme ils sont mentionnés sur
la figure 10.20 à gauche).
Au bout des dix premières secondes, la fusée a parcouru la totalité du chemin. Sa
position horizontale est Canvas.Left = 496. Les points de contrôle qui élaborent la
courbe d’interpolation sont KeySpline = «0,0.5 1,0.5» (comme ils sont mentionnés
sur la figure 10.20 à droite).
<Canvas.Resources>
<Storyboard x:Name= «x_story_fusee1» >
<DoubleAnimationUsingKeyFrames x:Name= «x_anim_fusee1» Duration= «00:00:10»
Storyboard.TargetName= «x_img1»
Storyboard.TargetProperty= «(Canvas.Left)»>
<SplineDoubleKeyFrame KeyTime= «00:00:05» KeySpline= «1,0 0,1»
Value= «196»>
</SplineDoubleKeyFrame>
<SplineDoubleKeyFrame KeyTime= «00:00:10» KeySpline= «0,0.5 1,0.5»
Value= «496»>
</SplineDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Canvas.Resources>

6.4 - Avec l’interpolation réaliste

Les classes d’images clés EasingDoubleKeyFrame, EasingColorKeyFrame et


EasingPointKeyFrame peuvent utiliser une interpolation réaliste définie au
moyen de leur propriété EasingFunction par un objet supportant l’interface
IEasingFunction (comme nous l’avons vu dans un précédent paragraphe).
310 Développez des applications Internet avec Silverlight 5
Le fichier AnimImgCleRealisme.xaml, dans le projet DemoAnimation.sln dans le
dossier chapitre10, illustre une animation utilisant des images clés (figure 10.21).
Dans un contrôle Ellipse dont le remplissage est un dégradé circulaire, l’animation
permet de déplacer l’origine du dégradé. C’est un peu comme si l’on déplaçait un
spot de lumière noire sur un fond blanc.
FIGURE 10.21

Copyright 2012 Patrice REY

Sur un Canvas x_cnv_root, on positionne une ellipse x_ellipse dont le remplissage


est un RadialGradientBrush x_radial_gradient. Sa propriété GradientOrigin, de
type double, est fixée à (0.7,0.3).
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<!-- animation 1 -->
...
CHAPITRE 10 □ Les animations 311

<Button Canvas.Left= «133» Canvas.Top= «226» Content= «Jouer l’animation»


Height= «29» Name= «x_btn_demarrer» Width= «357»
FontFamily= «Verdana» FontSize= «14» Click= «x_btn_demarrer_Click»
Cursor= «Hand» />
<Ellipse Canvas.Left= «193» Canvas.Top= «12» Height= «208» Name= «x_ellipse»
Stroke= «Black» StrokeThickness= «1» Width= «243»>
<Ellipse.Fill>
<RadialGradientBrush x:Name= «x_radial_gradient»
RadiusX= «1» RadiusY= «1» GradientOrigin= «0.7,0.3»>
<GradientStop Color= «Black» Offset= «0» />
<GradientStop Color= «White» Offset= «0.239» />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Canvas>
Dans les ressources du Canvas, on ajoute un Storyboard x_story. La propriété
ciblée est GradientOrigin, de type Point. On utilise donc une animation de type
PointAnimationUsingKeyFrames. On ajoute un certain nombre d’objets KeyFrame,
de type LinearPointKeyFrame et EasingPointKeyFrame. Ici, le EasingKeyFrame
a pour valeur les coordonnées (0.3,0.7) à l’instant 5 secondes. Sa propriété
EasingFunction est composée d’une interpolation CircleEase.
Tous ces KeyFrame permettent de simuler le déplacement d’un spot de lumière
noire sur un fond blanc.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<!-- animation 1 -->
<Canvas.Resources>
<Storyboard x:Name= «x_story» >
<PointAnimationUsingKeyFrames Storyboard.TargetName= «x_radial_gradient»
Storyboard.TargetProperty= «GradientOrigin» >
<LinearPointKeyFrame Value= «0.7,0.3» KeyTime= «0:0:0»>
</LinearPointKeyFrame>
<EasingPointKeyFrame Value= «0.3,0.7» KeyTime= «0:0:5»>
<EasingPointKeyFrame.EasingFunction>
<CircleEase></CircleEase>
</EasingPointKeyFrame.EasingFunction>
</EasingPointKeyFrame>
<LinearPointKeyFrame Value= «0.5,0.9» KeyTime= «0:0:8»>
</LinearPointKeyFrame>
<LinearPointKeyFrame Value= «0.9,0.6» KeyTime= «0:0:10»>
</LinearPointKeyFrame>
<LinearPointKeyFrame Value= «0.8,0.2» KeyTime= «0:0:12»>
</LinearPointKeyFrame>
<LinearPointKeyFrame Value= «0.7,0.3» KeyTime= «0:0:14»>
</LinearPointKeyFrame>
</PointAnimationUsingKeyFrames>
312 Développez des applications Internet avec Silverlight 5
</Storyboard>
</Canvas.Resources>
...
</Canvas>

7 - Le rendu image par image

La classe statique CompositionTarget représente la surface d’affichage d’une


application Silverlight. L’objectif de cette classe est d’être l’hôte de l’événement
Rendering, qui peut être utilisé comme un rappel image par image pour
les dispositions complexes ou les scénarios de composition. La gestion de
CompositionTarget.Rendering et l’utilisation d’un DispatcherTimer constituent
deux façons possibles de gérer les boucles de jeu ou d’autres tâches de minutage
de niveau inférieur. L’événement Rendering se produit lorsque le processus de
rendu Silverlight principal restitue une image.
Le fichier RenduImageParImage.xaml, dans le projet DemoAnimation.sln dans
le dossier chapitre10, illustre une animation de jeu utilisant le rendu image par
image avec la classe CompositionTarget (figure 10.22).
FIGURE 10.22

Canvas 320 pixels


x_cnv_jeu Copyright 2012 Patrice REY

580 pixels
CHAPITRE 10 □ Les animations 313

La surface de jeu est représentée par un Canvas x_cnv_jeu de dimensions 580


par 320 pixels, et est remplie par un dégradé de gris. Une bordure x_border_jeu
entoure le Canvas x_cnv_jeu. Le bouton x_btn_demarrer démarre le rendu image
par image, et le bouton x_btn_stopper arrête le rendu image par image.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Width= «600»
Height= «600»>
<Button Canvas.Left= «422» Canvas.Top= «12» Content= «Démarrer l’animation»
Height= «29» Name= «x_btn_demarrer» Width= «166»
FontFamily= «Verdana» FontSize= «14» Click= «x_btn_demarrer_Click»
Cursor= «Hand» />
<Button Canvas.Left= «422» Canvas.Top= «47» Content= «Arrêter l’animation»
Height= «29» Name= «x_btn_stopper» Width= «166»
FontFamily= «Verdana» FontSize= «14» Click= «x_btn_stopper_Click»
Cursor= «Hand» />
<Image Canvas.Left= «12» Canvas.Top= «12» Height= «70» Name= «image1»
Stretch= «Fill» Width= «40»
Source= «/DemoAnimation;component/contenu/bombe.png» />
<Image Canvas.Left= «58» Canvas.Top= «12» Height= «70» Name= «image2»
Stretch= «Fill» Width= «40»
Source= «/DemoAnimation;component/contenu/dynamite.png» />
<!-- jeu -->
<Border Name= «x_border_jeu» BorderThickness= «2» BorderBrush= «Black»
Canvas.Left= «10» Canvas.Top= «88» Width= «584» Height= «324»>
<Canvas Name= «x_cnv_jeu» Canvas.Left= «12» Canvas.Top= «104» Height= «320»
Width= «580» HorizontalAlignment= «Center» VerticalAlignment= «Center»>
<Canvas.Background>
<LinearGradientBrush EndPoint= «0.5,1» StartPoint= «0.5,0»>
<GradientStop Color= «White» Offset= «0» />
<GradientStop Color= «Silver» Offset= «1» />
</LinearGradientBrush>
</Canvas.Background>
</Canvas>
</Border>
</Canvas>
On déclare une classe Imagette qui représente un contrôle affichant l’image au
choix entre une bombe et une dynamite. L’énumération Imagette.Choix propose
l’image d’un point d’interrogation (valeur par défaut Choix.non_defini), l’image
d’une bombe (Choix.bombe), et l’image d’une dynamite (Choix.dynamite). Le
constructeur avec paramètre initialise le contrôle en fonction d’un choix (bombe
ou dynamite).
<UserControl x:Class= «DemoAnimation.Imagette»
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:d= «http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc= «http://schemas.openxmlformats.org/markup-compatibility/2006»
314 Développez des applications Internet avec Silverlight 5
mc:Ignorable= «d» d:DesignHeight= «70» d:DesignWidth= «40»>
<Grid x:Name= «x_grid_root» Background= «Transparent» Width= «40»
Height= «70»>
<Image Height= «70» HorizontalAlignment= «Center» Name= «x_img»
Stretch= «Fill» VerticalAlignment= «Center» Width= «40»
Source= «/DemoAnimation;component/contenu/non_defini.png» />
</Grid>
</UserControl>
public partial class Imagette : UserControl {
//donnees
public enum Choix { non_defini, bombe, dynamite };
//constructeur
public Imagette(Choix un_choix) {
InitializeComponent();
BitmapImage bitmap = null;
switch (un_choix) {
case Choix.bombe:
bitmap = new BitmapImage(
new Uri(«/DemoAnimation;component/contenu/bombe.png»,
UriKind.Relative));
break;
case Choix.dynamite:
bitmap = new BitmapImage(
new Uri(«/DemoAnimation;component/contenu/dynamite.png»,
UriKind.Relative));
break;
}
x_img.Source = bitmap;
}
}//end class
Pour mesurer le temps, nous avons besoin d’une horloge. On déclare une classe
Horloge possédant les propriétés TempsEcoule (qui stocke le temps écoulé
entre deux relevés d’horloge) et CumulTempsEcoule (qui stocke le temps écoulé
depuis la création d’une instance Horloge). La méthode RemiseAZero provoque la
réinitialisation de l’horloge par sa mise à zéro. La méthode ReleveHeure permet de
relever l’heure à un instant donné.
public class Horloge {
Copyright 2012 Patrice REY

//relevé heure
private DateTime m_reference;
//temps ecoulé entre 2 relevés
public TimeSpan TempsEcoule {
get;
private set;
}
//cumul du temps ecoulé tant qu’il n’y a
//pas eu de remise à zéro
CHAPITRE 10 □ Les animations 315

public TimeSpan CumulTempsEcoule {


get;
private set;
}
//constructeur
public Horloge() {
this.RemiseAZero();
}
//remise à zéro de l’horloge
public void RemiseAZero() {
this.m_reference = DateTime.Now;
this.TempsEcoule = TimeSpan.Zero;
this.CumulTempsEcoule = TimeSpan.Zero;
}
//relevé de l’heure
public void ReleveHeure() {
DateTime releve_horloge = DateTime.Now;
this.TempsEcoule = releve_horloge - this.m_reference;
this.CumulTempsEcoule += this.TempsEcoule;
this.m_reference = releve_horloge;
}
}
Pour les besoins du jeu, nous avons besoin:
• d’une variable booléenne v_rendu_encours, initialisée à false, qui nous indique
si le rendu image par image est en fonctionnement.
• d’une liste m_liste_imagette qui stocke les contrôles Imagette instanciés et mis
à jour au cours du jeu.
• d’un intervalle de temps m_intervalle_imagette, de type TimeSpan, pour
pouvoir générer des contrôles Imagettes toutes les deux secondes.
• d’un générateur de nombres aléatoires m_hasard.
• d’une horloge m_horloge pour la chronologie du jeu.
//
private bool v_rendu_encours = false;
private List<Imagette> m_liste_imagette = null;
private Horloge m_horloge;
private TimeSpan m_intervalle_imagette;
private Random m_hasard = null;
//
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
x_btn_demarrer.IsEnabled = true;
x_btn_stopper.IsEnabled = false;
v_rendu_encours = false;
m_intervalle_imagette = TimeSpan.Zero;
}
Le gestionnaire d’événement Click du bouton x_btn_demarrer permet de démarrer
316 Développez des applications Internet avec Silverlight 5
le jeu avec un rendu image par image. La liste m_liste_imagette est instanciée,
la variable v_rendu_encours passe à true, le générateur m_hasard et l’horloge
m_horloge sont instanciés, le Canvas x_cnv_jeu est vidé, et un gestionnaire pour
l’événement Rendering est ajouté par l’intermédiaire de la méthode statique
CompositionTarget.Rendering.
//bouton demarrer le rendu
private void x_btn_demarrer_Click(object sender, RoutedEventArgs e) {
m_liste_imagette = new List<Imagette>();
v_rendu_encours = true;
if (v_rendu_encours == true) {
m_liste_imagette.Clear();
x_cnv_jeu.Children.Clear();
m_hasard = new Random();
m_horloge = new Horloge();
x_btn_demarrer.IsEnabled = false;
x_btn_stopper.IsEnabled = true;
CompositionTarget.Rendering +=
new EventHandler(CompositionTarget_Rendering);
}
}
Le rendu image par image est géré par la méthode CompositionTarget_Rendering.
On commence par faire un relevé d’horloge par m_horloge.ReleveHeure().
Ensuite on ajoute des contrôles Imagette à cadence fixe par la méthode
AjouterImagetteACadenceFixe.
//rendu image par image
private void CompositionTarget_Rendering(object sender, EventArgs e) {
this.m_horloge.ReleveHeure();
AjouterImagetteACadenceFixe();
...
}
Le but est d’ajouter un contrôle Imagette toutes les deux secondes, dont l’image
représentée est choisie aléatoirement entre celle d’une bombe et celle d’une
dynamite. On ajoute à m_intervalle_imagette le temps qui s’est écoulé depuis le
dernier relevé d’horloge (propriété TempsEcoule). Si cet intervalle est supérieur à
deux secondes, alors on réinitialise cet intervalle à zéro, et on instancie un contrôle
Copyright 2012 Patrice REY

Imagette.
L’objet Imagette instancié est ajouté au Canvas x_cnv_jeu, avec une position fixée
par les méthodes statiques Canvas.SetLeft et Canvas.SetTop, et il est nommé
(propriété Name) par un identificateur unique. Le meilleur moyen de fixer un
identificateur unique consiste à associer l’heure courante au format heure/minute/
seconde (en utilisant DateTime.Now qui renvoie l’heure courante). Puis cet objet
CHAPITRE 10 □ Les animations 317

est ajouté à la liste m_liste_imagette.


//ajoute des imagettes toutes les deux secondes
private void AjouterImagetteACadenceFixe() {
this.m_intervalle_imagette += this.m_horloge.TempsEcoule;
if (this.m_intervalle_imagette.TotalSeconds >= 2) {
int nbre_alea = m_hasard.Next(1, 3);
Imagette une_imagette = null;
if (nbre_alea == 1) {
une_imagette = new Imagette(Imagette.Choix.bombe);
} else {
une_imagette = new Imagette(Imagette.Choix.dynamite);
}
int nbre_alea_pos = m_hasard.Next(0, 541);
Canvas.SetLeft(une_imagette, nbre_alea_pos);
Canvas.SetTop(une_imagette, -70);
string imagette_nom = «bombe_» + DateTime.Now.Hour.ToString();
imagette_nom += DateTime.Now.Minute.ToString();
imagette_nom += DateTime.Now.Second.ToString();
une_imagette.Name = imagette_nom;
x_cnv_jeu.Children.Add(une_imagette);
m_liste_imagette.Add(une_imagette);
this.m_intervalle_imagette = TimeSpan.Zero;
}
}
Un parcours de la liste permet de récupérer les objets Imagette et d’augmenter
leur positionnement vertical de façon à les faire avancer vers le bas du Canvas
(avec un pas ici de 0.5).
// rendu image par image
private void CompositionTarget_Rendering(object sender, EventArgs e) {
...
for (int xx = 0; xx < m_liste_imagette.Count; xx++) {
double pos_y = Canvas.GetTop(m_liste_imagette[xx]);
pos_y += 0.5;
Canvas.SetTop(m_liste_imagette[xx], pos_y);
}
...
}
A ce stade, quand on démarre l’animation, on obtient le résultat visualisé sur la
figure 10.23. Les contrôles Imagette naissent en haut et en dehors du Canvas, puis
se déplacent vers le bas.
Pour éviter une surcharge d’éléments visuels sur le Canvas, il faut faire en sorte
que les contrôles qui sont sortis du Canvas vers le bas, soient retirés de l’arbre
visuel du Canvas.
318 Développez des applications Internet avec Silverlight 5
FIGURE 10.23

les objets
Imagette
naissent en
haut puis
descendent
vers le bas

Pour cela, on effectue un parcours des éléments contenus dans l’arbre visuel
du Canvas. Chaque élément, de type Imagette, dont la position verticale est
supérieure à 320 pixels, est retiré de la liste (par la méthode RemoveAt), et est
retiré physiquement du Canvas (par la méthode RetirerImagetteCanvas).
// rendu image par image
private void CompositionTarget_Rendering(object sender, EventArgs e) {
...
int cpt_imagette = m_liste_imagette.Count;
for (int xx = 0; xx < cpt_imagette; xx++) {
double pos_y = Canvas.GetTop(m_liste_imagette[xx]);
if (pos_y >= 320) {
string nom = m_liste_imagette[xx].Name;
RetirerImagetteCanvas(nom);
m_liste_imagette.RemoveAt(xx);
cpt_imagette--;
Copyright 2012 Patrice REY

}
}
}
La méthode RetirerImagetteCanvas reçoit en paramètre l’identificateur unique
du contrôle Imagette à retirer. Un parcours de l’arbre visuel du Canvas (propriété
Children) permet de cibler le contrôle Imagette recherché, en obtenant l’index
correspondant. Une fois le parcours terminé, la méthode RemoveAt permet de
CHAPITRE 10 □ Les animations 319

supprimer l’élément à l’index fourni.


//retirer une imagette
private void RetirerImagetteCanvas(string nom) {
int index_a_retirer = -1;
for (int yy = 0; yy < x_cnv_jeu.Children.Count; yy++) {
if (x_cnv_jeu.Children[yy].GetType() == typeof(Imagette)) {
Imagette img = (Imagette)x_cnv_jeu.Children[yy];
if (img.Name == nom) {
index_a_retirer = yy;
}
}
}
x_cnv_jeu.Children.RemoveAt(index_a_retirer);
}
La figure 10.24 montre le résultat obtenu. Tous les contrôles Imagette positionnés
en dehors du Canvas en bas, sont supprimés de l’arbre visuel du Canvas. Cela
permet d’alléger le parcours des objets contenus dans le Canvas.
FIGURE 10.24

les objets
Imagette en
dehors du
Canvas sont
supprimés de
l’arbre visuel
du Canvas

Maintenant il ne nous reste plus qu’à faire en sorte de voir uniquement les objets
contenus dans les limites du Canvas. Pour cela, il faut clipper le Canvas par une
région rectangulaire dont sa dimension sera identique à celle du Canvas.
La propriété Clip du Canvas x_cnv_jeu reçoit un objet de type Geometry (c’est-à-dire
une géométrie). On instancie donc un objet rect_clip de type RectangleGeometry
320 Développez des applications Internet avec Silverlight 5
(qui représente une géométrie rectangulaire). Sa propriété Rect reçoit les
dimensions sous la forme d’un objet de type Rect. Une des surcharges du type
Rect nous permet de spécifier le coin haut gauche (coordonnées x=0 et y=0), la
longueur (580 pixels) et la largeur (320 pixels).
//
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
...
RectangleGeometry rect_clip = new RectangleGeometry();
rect_clip.Rect = new Rect(0, 0, 580, 320);
x_cnv_jeu.Clip = rect_clip;
}
La figure 10.25 montre le résultat obtenu. On voit bien que les contrôles Imagette
naissent en haut du Canvas, puis descendent en bas du Canvas. Le fait de clipper
le Canvas permet de masquer la zone non visible.
FIGURE 10.25

la zone de type RectangleGeometry qui


clippe le Canvas
Copyright 2012 Patrice REY

Le gestionnaire de l’événement Click du bouton x_btn_stopper permet


d’arrêter le rendu image par image. Pour cela il suffit de retirer le gestionnaire
CompositionTarget_Rendering de l’événement Rendering.
//stopper le rendu
private void x_btn_stopper_Click(object sender, RoutedEventArgs e) {
CompositionTarget.Rendering -= CompositionTarget_Rendering;
v_rendu_encours = false;
CHAPITRE 10 □ Les animations 321

x_btn_demarrer.IsEnabled = true;
x_btn_stopper.IsEnabled = false;
}

8 - L’accélération matérielle

Le paramètre booléen enableGpuAcceleration définit une valeur qui indique


s’il faut utiliser l’accélération matérielle GPU (Graphic Processor Unit) pour les
compositions mises en cache pour optimiser potentiellement l’affichage graphique.
Vous devez définir ce paramètre au démarrage de l’application. Vous ne pouvez
pas le modifier une fois que le contenu Silverlight a été chargé.
L’accélération matérielle est activée uniquement sur Windows Vista, Windows 7
et Windows XP. Sur Windows XP, une date de pilote postérieure à novembre 2004
pour les cartes NVidia, ATI, Intel est obligatoire pour l’accélération matérielle.
Pour activer l’accélération matérielle, affectez la valeur true au paramètre
enableGpuAcceleration.
<div id= «silverlightControlHost»>
<object data= «data:application/x-silverlight-2,»
type= «application/x-silverlight-2»
width= «100%» height= «100%»>
<param name= «source» value= «ClientBin/MecanismeAffichageV1.xap» />
<param name= «onError» value= «onSilverlightError» />
<param name= «background» value= «white» />
<param name= «minRuntimeVersion» value= «5.0.61118.0» />
<param name= «autoUpgrade» value= «true» />
<param name= «enableGPUAcceleration» value= «true» />
</object>
</div>
Le paramètre booléen enableFrameRateCounter définit une valeur qui indique
s’il faut afficher la fréquence d’images actuelle sur la barre d’état du navigateur
d’hébergement. Si les paramètres booléens enableGpuAcceleration et
enableFrameRateCounter du plugin sont fixés à true, un compteur supplémentaire
affiche en haut de l’écran les valeurs suivantes (figure 10.26):
• la fréquence d’images.
• la quantité de mémoire graphique utilisée (GPU) en Ko.
• le nombre de surfaces accélérées par le GPU.
• le nombre de surfaces intermédiaires (créées au niveau du GPU pour
représenter des surfaces en rendu software).
<div id= «silverlightControlHost»>
<object data= «data:application/x-silverlight-2,»
322 Développez des applications Internet avec Silverlight 5
type= «application/x-silverlight-2»
width= «100%» height= «100%»>
<param name= «source» value= «ClientBin/MecanismeAffichageV1.xap» />
<param name= «onError» value= «onSilverlightError» />
<param name= «background» value= «white» />
<param name= «minRuntimeVersion» value= «5.0.61118.0» />
<param name= «autoUpgrade» value= «true» />
<param name= «enableGPUAcceleration» value= «true» />
<param name= «enableFrameRateCounter» value= «true» />
</object>
</div>
Le projet MecanismeAffichageV1.sln, dans le dossier chapitre10, illustre l’affichage
du compteur (figure 10.26).
FIGURE 10.26

la fréquence
d'images

la quantité de
mémoire
graphique utilisée
(GPU) en Ko
le nombre de
surfaces
accélérées par le
GPU

le nombre de
surfaces
intermédiaires Copyright 2012 Patrice REY

9 - Le cache de composition

La technique du cache de composition consiste à mettre en cache un arbre visuel


sous la forme d’un bitmap dans la mémoire de la carte graphique, ce qui permet
CHAPITRE 10 □ Les animations 323

alors de bénéficier de l’accélération matérielle et donc de réduire la consommation


du CPU.
L’activation du cache pour un UIElement se fait en assignant un objet BitmapCache
à sa propriété CacheMode. Une fois le cache constitué, les opérations de rendu sont
alors plus rapides, en particulier dans le cas de transformations ou d’animations.
L’interactivité clavier souris est conservée.
Le projet MecanismeAffichageV2.sln, dans le dossier chapitre10, illustre l’affichage
du compteur et la mise en cache (figure 10.27). Le Grid x_grid_cache possède une
mise en cache et le Grid x_grid ne possède pas de mise en cache.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke» Height= «478»>
<Canvas.Resources>
<Storyboard x:Name= «x_story»>
<DoubleAnimation Duration= «00:00:05»
Storyboard.TargetName= «x_scale_grid_cache»
Storyboard.TargetProperty= «ScaleX»
From= «1» To= «8» By= «1» AutoReverse= «True»
RepeatBehavior= «Forever»></DoubleAnimation>
<DoubleAnimation Duration= «00:00:05»
Storyboard.TargetName= «x_scale_grid_cache»
Storyboard.TargetProperty= «ScaleY»
From= «1» To= «8» By= «1» AutoReverse= «True»
RepeatBehavior= «Forever»></DoubleAnimation>
<DoubleAnimation Duration= «00:00:05»
Storyboard.TargetName= «x_scale_grid»
Storyboard.TargetProperty= «ScaleX»
From= «1» To= «8» By= «1» AutoReverse= «True»
RepeatBehavior= «Forever»></DoubleAnimation>
<DoubleAnimation Duration= «00:00:05»
Storyboard.TargetName= «x_scale_grid»
Storyboard.TargetProperty= «ScaleY»
From= «1» To= «8» By= «1» AutoReverse= «True»
RepeatBehavior= «Forever»></DoubleAnimation>
</Storyboard>
</Canvas.Resources>
<Grid Canvas.Left= «253» Canvas.Top= «166» Name= «x_grid_cache»>
<Grid.RowDefinitions>
<RowDefinition Height= «100» />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width= «100» />
</Grid.ColumnDefinitions>
<Grid.RenderTransform>
<ScaleTransform x:Name= «x_scale_grid_cache» CenterX= «50» CenterY= «100»
ScaleX= «1» ScaleY= «1»></ScaleTransform>
</Grid.RenderTransform>
<Grid.CacheMode>
324 Développez des applications Internet avec Silverlight 5
FIGURE 10.27

Grid x_grid_cache

Grid x_grid

<BitmapCache
RenderAtScale="10">
</BitmapCache>

Copyright 2012 Patrice REY


CHAPITRE 10 □ Les animations 325

<BitmapCache RenderAtScale= «4»></BitmapCache>


</Grid.CacheMode>
<Rectangle Stroke= «DarkGray» StrokeThickness= «2» Fill= «Black»
Grid.Column= «0» Grid.Row= «0» ></Rectangle>
<Rectangle Stroke= «DimGray» StrokeThickness= «2» Fill= «DarkGray»
Grid.Column= «0» Grid.Row= «0» Width= «80» Height= «80» />
</Grid>
<!-- -->
<Grid Canvas.Left= «253» Canvas.Top= «269» Name= «x_grid»>
<Grid.RowDefinitions>
<RowDefinition Height= «100» />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width= «100» />
</Grid.ColumnDefinitions>
<Grid.RenderTransform>
<ScaleTransform x:Name= «x_scale_grid» CenterX= «50» CenterY= «0»
ScaleX= «1» ScaleY= «1»></ScaleTransform>
</Grid.RenderTransform>
<Rectangle Stroke= «DarkGray» StrokeThickness= «2» Fill= «Black»
Grid.Column= «0» Grid.Row= «0» />
<Rectangle Stroke= «DimGray» StrokeThickness= «2» Fill= «DarkGray»
Grid.Column= «0» Grid.Row= «0» Width= «80» Height= «80» />
</Grid>
</Canvas>
Pour éviter l’effet de pixellisation lors des animations, il faut assigner une valeur à la
propriété RenderAtScale (de l’objet BitmapCache) qui applique le facteur d’échelle
spécifié à l’arbre visuel avant de constituer le cache.
C H A P I T R E 11 DANS CE CHAPITRE
• Les formats audio et

L’audio et
vidéo
• La classe
MediaElement

la vidéo • DispatcherTimer
• La prise en charge de
l’audio et de la vidéo
• VideoBrush
• L’acquisition audio et
vidéo
• Les classes
CaptureSource et
Ce chapitre est dédié à l’une des caractéristiques CaptureDevice
• AudioCaptureDevice
les plus importantes de Silverlight qu’est la prise en • VideoCaptureDevice
charge de l’audio et de la vidéo.
Comme Silverlight est présent sur différents
systèmes et navigateurs, la prise en charge des
formats audio et vidéo doit être la plus large
possible. L’utilisation de l’audio et de la vidéo
est très courant dans le domaine du multimédia.
Silverlight prend en charge un grand nombre de
format de fichier audio et vidéo.
Dans ce chapitre, vous verrez comment lire les
fichiers audio et vidéo au travers de leurs différents
formats.
Ecouter une piste sonore et savoir comment
obtenir ses caractéristiques (durée, etc.) sont des
manipulations courantes à connaître.
Visionner une piste vidéo, obtenir ses
caractéristiques et savoir lire la position du curseur
vidéo en temps réel, sont des manipulations
courantes à connaître.
L’acquisition audio et vidéo, par l’intermédiaire
d’une webcam, sera abordée car son utilisation
est couramment employée pour développer des
applications Internet pointues.
328 Développez des applications Internet avec Silverlight 5
L’utilisation de l’audio et de la vidéo est très courant dans le domaine du multimédia.
Silverlight prend en charge un grand nombre de format de fichier audio et vidéo.

1 - Les formats audio et vidéo supportés

Les formats audio pris en charge par Silverlight sont Windows Media Audio (WMA)
versions 7, 8, and 9, et MP3 (ISO MPEG-1 Layer III).
Les formats vidéo pris en charge sont Windows Media Video 7 (WMV1), Windows
Media Video 8 (WMV2), Windows Media Video 9 (WMV3), Windows Media Video
Advanced Profile (non-VC-1 WMVA), Windows Media Video Advanced Profile (VC-
1 WMVC1), et H.264 vidéo and AAC audio (aussi connu sous le nom MPEG-4 Part
10 or MPEG-4 AVC).
Les formats suivants ne sont pas pris en charge: contenu de vidéo entrelacée,
Windows Media Screen, Windows Media Audio Professional sans perte, Windows
Media Voice, combinaison de Windows Media Video et MP3 (vidéo WMV + audio
MP3), et combinaison de WMV et AAC-LC.

2 - Le contrôle MediaElement

La classe MediaElement (figure 11.1) représente un objet qui contient des


données audio, vidéo ou les deux. Un contrôle MediaElement peut lire plusieurs
types de médias audio et vidéo. Un MediaElement est fondamentalement une
région rectangulaire qui peut afficher de la vidéo sur sa surface ou lire un contenu
audio (auquel cas aucune vidéo n’est affichée, mais le MediaElement joue encore
un rôle d’objet lecteur avec les API appropriées). Dans la mesure où il s’agit d’un
UIElement, un MediaElement prend en charge des opérations d’entrée, telles que
les événements de souris et du clavier, et peut capturer le focus ou la souris. Vous
pouvez spécifier la hauteur et la largeur de la surface d’affichage vidéo à l’aide des
propriétés Height et Width. Toutefois, pour des performances optimales, évitez de
définir explicitement la largeur et la hauteur d’un MediaElement. Laissez plutôt les
Copyright 2012 Patrice REY

valeurs non définies. Une fois que vous spécifiez une source, le média apparaîtra
à sa taille naturelle, et la disposition recalculera la taille. Si vous devez modifier la
taille d’affichage d’un média, il vaut mieux encoder à nouveau ce dernier selon la
taille souhaitée à l’aide d’un outil d’encodage multimédia.
Par défaut, les médias définis par la propriété Source sont lus immédiatement
après le chargement de l’objet MediaElement. Pour empêcher le démarrage
automatique des médias, affectez à la propriété AutoPlay la valeur false.
CHAPITRE 11 □ L’audio et la vidéo 329
FIGURE 11.1

3 - La prise en charge de l’audio

La figure 11.2 visualise le résultat obtenu de l’UserControl JouerMusique.xaml,


dans le projet DemoAudioVideo.sln dans le dossier chapitre11. Ce projet consiste à
choisir une musique dans une sélection, puis à l’écouter, tout en ayant la possibilité
de modifier le volume et la balance à la volée ainsi que d’avoir les informations
liées à la piste sonore (durée de la piste, position du curseur sonore en cours de
lecture).
Sur le Canvas x_cnv_root, un ComboBox x_select permet de sélectionner des
pistes sonores, au format MP3, stockées dans le dossier musiques en tant que
ressources (figure 11.3).
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
<ComboBox Height= «29» Width= «275» Canvas.Left= «317» Canvas.Top= «2»
SelectedIndex= «0» Name= «x_select» FontFamily= «Verdana» FontSize= «14»
SelectionChanged= «x_select_SelectionChanged» >
<ComboBoxItem Content= «choisir une musique ...»></ComboBoxItem>
<ComboBoxItem Content= «allemagne_polka.mp3»></ComboBoxItem>
<ComboBoxItem Content= «grece_musique_traditionnelle.mp3»></ComboBoxItem>
<ComboBoxItem Content= «hawai_ukele.mp3»></ComboBoxItem>
<ComboBoxItem Content= «iran_percus_flute.mp3»></ComboBoxItem>
<ComboBoxItem Content= «israel_antique.mp3»></ComboBoxItem>
<ComboBoxItem Content= «jingle_bells.mp3»></ComboBoxItem>
<ComboBoxItem Content= «joyeux_aniversaire.mp3»></ComboBoxItem>
<ComboBoxItem Content= «marche_nuptiale.mp3»></ComboBoxItem>
330 Développez des applications Internet avec Silverlight 5
FIGURE 11.2

ComboBox x_select

TextBlock Slider x_slider_volume


x_infos
Slider x_slide_balance

Slider
x_slider_position TextBlock x_position_encours

Copyright 2012 Patrice REY

4
CHAPITRE 11 □ L’audio et la vidéo 331

<ComboBoxItem Content= «musique_cirque.mp3»></ComboBoxItem>


<ComboBoxItem Content= «musique_mariage.mp3»></ComboBoxItem>
<ComboBoxItem Content= «musique_troubadour.mp3»></ComboBoxItem>
<ComboBoxItem Content= «suisse_tyrolienne.mp3»></ComboBoxItem>
</ComboBox>
...
</Canvas>

FIGURE 11.3

Un TextBlock x_infos permet l’affichage des informations liées à la piste sonore


écoutée. Les glissières x_slider_volume et x_slider_balance permettent de modifier
le volume du son et la balance des écouteurs (par le gestionnaire ValueChanged
respectif). La glissière x_slider_position prend des valeurs qui sont proportionnelles
à la durée totale de la piste sonore. Sa propriété IsTestVisible est fixée à false pour
empêcher toute interaction de l’utilisateur sur son comportement. Le TextBlock
x_position_encours affiche la position horaire du curseur sonore de la piste lue.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»>
...
<Border BorderThickness= «1» CornerRadius= «5» BorderBrush= «Black»
Background= «White» Width= «307»>
<TextBlock Canvas.Left= «12» Canvas.Top= «18» Height= «140» Name= «x_infos»
Text= «infos» Width= «290» Margin= «5» TextWrapping= «Wrap» />
</Border>
<Rectangle Width= «589» Height= «38» Canvas.Left= «3» Canvas.Top= «164»
StrokeThickness= «1» Stroke= «Black» Fill= «DarkGray»></Rectangle>
<Slider Canvas.Left= «10» Canvas.Top= «166» Width= «576» Height= «36»
Name= «x_slider_position» IsHitTestVisible= «False»></Slider>
332 Développez des applications Internet avec Silverlight 5
<TextBlock Canvas.Left= «205» Canvas.Top= «213» Height= «23»
Name= «x_position_encours» Text= «00:00:00» Width= «167»
TextAlignment= «Center» FontFamily= «Verdana» FontSize= «14» />
<TextBlock Canvas.Left= «317» Canvas.Top= «48» Height= «23»
Name= «x_text_volume» Text= «Volume:» />
<Slider Name= «x_slider_volume» Width= «269» Height= «31» Canvas.Left= «317»
Canvas.Top= «66» Cursor= «Hand» Minimum= «0»
Maximum= «1» SmallChange= «0.25» LargeChange= «0.25»
ValueChanged= «x_slider_volume_ValueChanged» Value= «0.5»></Slider>
<TextBlock Canvas.Left= «317» Canvas.Top= «97» Height= «23»
Name= «x_text_balance» Text= «Balance:» />
<Slider Canvas.Left= «317» Canvas.Top= «121» Cursor= «Hand» Height= «31»
LargeChange= «0.25» Maximum= «1» Minimum= «-1»
Name= «x_slider_balance» SmallChange= «0.25» Value= «0» Width= «269»
ValueChanged= «x_slider_balance_ValueChanged» />
</Canvas>
Le gestionnaire x_select_SelectionChanged gère le choix d’une piste sonore à lire.
Quand une piste sonore est choisie, on instancie un objet MediaElement m_media,
que l’on nomme x_mediaelement pour l’arborescence visuelle, et on l’ajoute aux
enfants du Canvas (propriété Children) car le MediaElement est un conteneur.
On fixe sa propriété Source en fonction de l’objet ComboBoxItem sélectionné dans
x_select. La chaîne fournie pour la propriété Source est interprétée comme un
URI, et donc on référence un URI relatif pour pointer dans le dossier musiques. La
propriété AutoPlay est mise à false pour empêcher la lecture automatique.
Le volume (propriété Volume) est fixé à v_volume_encours (0.5 par défaut). Vous
pouvez vous représenter la propriété Volume comme la valeur de position d’un
contrôle de curseur mélangeur audio, 0 étant la valeur la plus basse et 1 la valeur
la plus élevée.
La balance (propriété Balance) est fixée à v_balance_encours (0 par défaut). La
valeur -1 représente 100 % du volume dans les haut-parleurs de gauche et la valeur
1 représente 100 % du volume dans les haut-parleurs de droite. La valeur 0 indique
que le volume est distribué de manière égale entre les haut-parleurs côté gauche
et côté droit.
On ajoute au contrôle MediaElement les gestionnaires d’événements:
Copyright 2012 Patrice REY

• MediaOpened qui se produit lorsque le flux de données multimédia est validé


et ouvert, et lorsque les en-têtes de fichier sont lus.
• MediaFailed qui se produit lorsqu’une erreur est associée à la propriété Source
du média.
• MediaEnded qui se produit lorsque le MediaElement ne lit plus de données
audio ou vidéo.
CHAPITRE 11 □ L’audio et la vidéo 333

//selection d’une piste sonore


private void x_select_SelectionChanged(object sender,
SelectionChangedEventArgs e) {
if (x_select != null && x_select.SelectedIndex >= 1) {
...
m_media = new MediaElement();
m_media.Name = «x_mediaelement»;
x_cnv_root.Children.Add(m_media);
x_infos.Text = «»;
ComboBoxItem recup_combo_item = (ComboBoxItem)x_select.SelectedItem;
string recup_string = (string)recup_combo_item.Content;
x_infos.Text = recup_string + RC;
m_media.Source = new Uri(«musiques/» + recup_string, UriKind.Relative);
m_media.AutoPlay = false;
m_media.Volume = v_volume_encours;
m_media.Balance = v_balance_encours;
m_media.MediaOpened += new RoutedEventHandler(m_media_MediaOpened);
m_media.MediaFailed +=
new EventHandler<ExceptionRoutedEventArgs>(m_media_MediaFailed);
m_media.MediaEnded += new RoutedEventHandler(m_media_MediaEnded);
}
}
En cas d’erreur liée à l’ouverture de la piste sonore demandée, le TextBlock x_infos
affiche le détail de l’erreur.
//erreur d’ouverture de fichier sonore
private void m_media_MediaFailed(object sender, ExceptionRoutedEventArgs e) {
x_infos.Text += e.ErrorException.Message + RC;
}
Quand le flux audio est ouvert sans erreur, le TextBlock x_infos affiche que le
flux audio est ouvert, et que la lecture vient de démarrer (méthode Play). Pour
afficher la durée totale de la piste sonore (repère n°2 de la figure 11.2), il faut
récupérer la valeur de la propriété NaturalDuration (son format est de type
heure:minute:seconde.milliseconde).
//flux audio ouvert correctement
private void m_media_MediaOpened(object sender, RoutedEventArgs e) {
x_infos.Text += «-> flux audio ouvert» + RC;
m_media.Play();
x_infos.Text += «-> lecture en cours» + RC;
x_infos.Text += «-> durée totale = « +
m_media.NaturalDuration.ToString() + RC;
...
}
Quand le flux audio est en fin de lecture, le TextBlock x_infos indique que la lecture
est terminée.
334 Développez des applications Internet avec Silverlight 5
//fin de lecture de la piste sonore
private void m_media_MediaEnded(object sender, RoutedEventArgs e) {
x_infos.Text += «-> fin de lecture» + RC;
}
Les gestionnaires x_slider_volume_ValueChanged et x_slider_balance_
ValueChanged gèrent le changement des valeurs des glissières en cas de
modification du volume et de la balance.
//deplacement du slider volume
private void x_slider_volume_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e) {
if (m_media != null) {
m_media.Volume = x_slider_volume.Value;
v_volume_encours = x_slider_volume.Value;
}
}
//deplacement du slider balance
private void x_slider_balance_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e) {
if (m_media != null) {
m_media.Balance = x_slider_balance.Value;
v_balance_encours = x_slider_balance.Value;
}
}
Le fait de sélectionner une piste sonore dans x_select provoque l’instanciation
d’un nouveau contrôle MediaElement. Comme les contrôles doivent posséder
un identificateur unique, lors du changement de la piste sonore, il faut retirer le
m_media en cours, de l’arborescence visuelle du Canvas. Pour cela on cherche
le MediaElement, intitulé x_mediaelement (propriété Name), par la méthode
FindName sur les enfants du Canvas. Puis on le retire de l’arborescence visuelle
par la méthode Remove.
//selection d’une piste sonore
private void x_select_SelectionChanged(object sender,
SelectionChangedEventArgs e) {
if (x_select != null && x_select.SelectedIndex >= 1) {
if (m_media != null) {
Copyright 2012 Patrice REY

MediaElement media =
(MediaElement)x_cnv_root.FindName(«x_mediaelement»);
x_cnv_root.Children.Remove(media);
}
m_media = new MediaElement();
m_media.Name = «x_mediaelement»;
x_cnv_root.Children.Add(m_media);
... }
}
CHAPITRE 11 □ L’audio et la vidéo 335

La propriété Position définit la position actuelle dans la progression en temps


de lecture du média. Pour lire cette propriété Position, on utilise un objet
DispatcherTimer, qui est une minuterie intégrée dans la file d’attente de l’objet
Dispatcher, et qui est traitée à un intervalle de temps et selon une priorité spécifiés.
Pour utiliser un objet DispatcherTimer, il faut ajouter une référence à l’espace de
noms System.Windows.Threading.
Sa propriété Interval définit la durée séparant les graduations de la minuterie
(que l’on fixe à 0.1 seconde). L’événement Tick se produit lorsque l’intervalle de
la minuterie s’est écoulé. On ajoute donc un gestionnaire pour l’événement Tick.
On termine en démarrant la minuterie par la méthode Start. De façon à ce que la
glissière x_slider_position se déplace proportionnellement à la durée de la piste
sonore, on fixe sa valeur minimale à 0 (propriété Minimum) et sa valeur maximale
(propriété Maximum) au nombre de secondes correspondant à la durée de la piste
sonore (propriété TimeSpan de NaturalDuration, exprimée en secondes par la
propriété TotalSeconds).
//flux audio ouvert correctement
private void m_media_MediaOpened(object sender, RoutedEventArgs e) {
...
x_slider_position.Minimum = 0;
x_slider_position.Maximum = m_media.NaturalDuration.TimeSpan.TotalSeconds;
m_dispatcher = new DispatcherTimer();
m_dispatcher.Interval = TimeSpan.FromSeconds(0.1);
m_dispatcher.Tick += new EventHandler(m_dispatcher_Tick);
m_dispatcher.Start();
}
Quand une graduation de la minuterie s’est écoulée (événement Tick), on récupère
au format texte la propriété Position en cours du MediaElement (que l’on affiche
dans x_position_encours). Et on ajuste la position de la glissière x_slider_position
en fonction du nombre de secondes de la propriété Position.
//quand la minuterie s’est ecoulee
private void m_dispatcher_Tick(object sender, EventArgs e) {
string texte_piste = m_media.Position.ToString().Substring(0, 8);
x_position_encours.Text = texte_piste;
x_slider_position.Value = m_media.Position.TotalSeconds;
}
Ainsi, à chaque piste sonore sélectionnée, on ajoute un MediaElement qui lit la
piste sonore, qui affiche les informations liées à cette piste, et qui ajuste la glissière
représentant la durée de la piste sonore de façon proportionnelle.
336 Développez des applications Internet avec Silverlight 5

4 - La prise en charge de la vidéo

La figure 11.4 visualise le résultat obtenu avec l’UserControl JouerVideo.xaml,


dans le projet DemoAudioVideo.sln dans le dossier chapitre11. Ce projet consiste à
visionner une vidéo, tout en ayant la possibilité de modifier le volume et la balance
à la volée ainsi que d’avoir les informations liées à la vidéo (durée totale de la
vidéo, position du curseur vidéo en cours de lecture). Des boutons permettent de
lire la vidéo, de la mettre en pause, et de l’arrêter. Un bouton permet de passer le
plugin en mode plein écran puis de revenir au mode normal.
Dans le Canvas x_cnv_root, on ajoute un contrôle Border qui reçoit un contrôle
MediaElement dans sa propriété Content. Ce MediaElement x_mediaelement
est doté de gestionnaires pour les événements MediaOpened, MediaFailed et
MediaEnded.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»
HorizontalAlignment= «Center» VerticalAlignment= «Center» Width= «600»
Height= «600»>
<Border Name= «x_border_media» Width= «580» Height= «300» Canvas.Left= «10»
Canvas.Top= «3» BorderThickness= «2» CornerRadius= «5» BorderBrush= «Black»
Padding= «5» Background= «LightGray»>
<MediaElement Name= «x_mediaelement»
MediaOpened= «x_mediaelement_MediaOpened»
MediaFailed= «x_mediaelement_MediaFailed»
MediaEnded= «x_mediaelement_MediaEnded»></MediaElement>
</Border>
...
</Canvas>
Un TextBlock x_infos permet l’affichage des informations liées à la vidéo visionnée.
Les glissières x_slider_volume et x_slider_balance permettent de modifier le volume
du son et la balance des écouteurs (par le gestionnaire ValueChanged respectif).
Le ProgressBar x_progress prend des valeurs qui sont proportionnelles à la durée
totale de la vidéo. Le TextBlock x_position_encours affiche la position horaire du
curseur vidéo. Le bouton x_btn_lecture permet de démarrer le visionnage de la
Copyright 2012 Patrice REY

vidéo, le bouton x_btn_pause permet la mise en pause de la vidéo, et le bouton x_


btn_arret permet d’arrêter la vidéo (en repositionnant le curseur vidéo au départ
de la séquence).
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»
HorizontalAlignment= «Center» VerticalAlignment= «Center» Width= «600»
Height= «600»> ...
<Border BorderThickness= «1» CornerRadius= «5» BorderBrush= «Black»
CHAPITRE 11 □ L’audio et la vidéo 337
FIGURE 11.4

MediaElement x_mediaelement TextBlock x_position_encours


1 2

Slider Slider ProgressBar TextBlock


x_slider_volume x_slide_balance x_progress x_infos

Passage dans le mode de pause

Passage au mode plein écran puis


retour au mode écran normal

4
338 Développez des applications Internet avec Silverlight 5
Background= «White» Width= «574» Canvas.Left= «12»
Canvas.Top= «445» Height= «77»>
<TextBlock Canvas.Left= «12» Canvas.Top= «18» Height= «63» Name= «x_infos»
Text= «infos» Width= «560» Margin= «5»
TextWrapping= «Wrap» HorizontalAlignment= «Left» VerticalAlignment= «Top»
/>
</Border>
<TextBlock Canvas.Left= «407» Canvas.Top= «319» Height= «23»
Name= «x_position_encours» Text= «00:00:00 sur 00:00:00» Width= «176»
TextAlignment= «Right» FontFamily= «Verdana» FontSize= «14» />
<TextBlock Canvas.Left= «12» Canvas.Top= «372» Height= «23»
Name= «x_text_volume» Text= «Volume:» />
<Slider Name= «x_slider_volume» Width= «269» Height= «31» Canvas.Left= «12»
Canvas.Top= «396» Cursor= «Hand» Minimum= «0»
Maximum= «1» SmallChange= «0.25» LargeChange= «0.25»
ValueChanged= «x_slider_volume_ValueChanged» Value= «0.5»></Slider>
<TextBlock Canvas.Left= «317» Canvas.Top= «372» Height= «23»
Name= «x_text_balance» Text= «Balance:» />
<Slider Canvas.Left= «317» Canvas.Top= «396» Cursor= «Hand» Height= «31»
LargeChange= «0.25» Maximum= «1» Minimum= «-1»
Name= «x_slider_balance» SmallChange= «0.25» Value= «0» Width= «269»
ValueChanged= «x_slider_balance_ValueChanged» />
<Button Canvas.Left= «14» Canvas.Top= «317» Content= «Lecture» Height= «23»
Name= «x_btn_lecture» Width= «75» Click= «x_btn_lecture_Click»
Cursor= «Hand»/>
<Button Canvas.Left= «176» Canvas.Top= «317» Content= «Arrêt» Height= «23»
Name= «x_btn_arret» Width= «75» Click= «x_btn_arret_Click» Cursor= «Hand» />
<Button Canvas.Left= «95» Canvas.Top= «317» Content= «Pause» Height= «23»
Name= «x_btn_pause» Width= «75» Click= «x_btn_pause_Click» Cursor= «Hand»/>
<ProgressBar Name= «x_progress» Width= «571» Height= «16» Canvas.Top= «348»
Canvas.Left= «12» Value= «50» Foreground= «Red» Background= «Lime»>
</ProgressBar>
<Button Canvas.Left= «257» Canvas.Top= «317» Cursor= «Hand» Height= «23»
Name= «x_btn_ecran» Width= «157»
Content= «Passer en plein écran» Click= «x_btn_ecran_Click»/>
</Canvas>
La variable v_volume_encours stocke le niveau sonore de la vidéo, la variable
v_balance_encours stocke le niveau de la balance des haut-parleurs, la variable
m_dispatcher représente la minuterie utilisée, et la variable v_etat_video_encours
Copyright 2012 Patrice REY

stocke une valeur de l’énumération EtatVideo (qui représente les états dans
lesquels se trouve la vidéo comme la lecture, la pause, l’arrêt et l’ouverture après
le chargement sans erreur).
public enum EtatVideo { non_defini, ouvert, lecture, pause, arret };
private EtatVideo v_etat_video_encours;
private DispatcherTimer m_dispatcher = null;
private double v_volume_encours = 0.5;
CHAPITRE 11 □ L’audio et la vidéo 339

private double v_balance_encours = 0;


Quand l’UserControl est chargé, on fixe la propriété Source de x_mediaelement
(pointant sur un fichier vidéo stocké comme ressource dans le dossier video) avec
un AutoPlay à false.
//evenement loaded
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
x_mediaelement.Source =
new Uri(«video/Les_Mysteres_de_lamour.wmv», UriKind.Relative);
x_mediaelement.AutoPlay = false;
v_etat_video_encours = EtatVideo.non_defini;
x_btn_lecture.IsEnabled = false;
x_btn_pause.IsEnabled = false;
x_btn_arret.IsEnabled = false;
}
Après le chargement sans erreur du fichier vidéo (fichier au format WMV), on
affiche la durée totale de la vidéo (propriété NaturalDuration), et la dimension
en largeur de la vidéo (propriété NaturalVideoWidth) et en hauteur (propriété
NaturalVideoHeight). La minuterie m_dispatcher est instanciée. Les bornes
minimales et maximales du ProgressBar sont calculées.
//flux ouvert
private void x_mediaelement_MediaOpened(object sender, RoutedEventArgs e) {
v_etat_video_encours = EtatVideo.ouvert;
x_infos.Text = «-> flux video ouvert» + RC;
x_infos.Text += «-> durée de la video: « +
x_mediaelement.NaturalDuration.ToString() + RC;
x_infos.Text += «-> dimensions réelles de la video: « +
x_mediaelement.NaturalVideoWidth.ToString();
x_infos.Text += «x» + x_mediaelement.NaturalVideoHeight.ToString() + RC;
GererLesBoutons();
x_progress.Minimum = 0;
x_progress.Maximum = x_mediaelement.NaturalDuration.TimeSpan.TotalSeconds;
m_dispatcher = new DispatcherTimer();
m_dispatcher.Interval = TimeSpan.FromSeconds(0.1);
m_dispatcher.Tick += new EventHandler(m_dispatcher_Tick);
m_dispatcher.Start();
}
La méthode GererLesBoutons permet de gérer l’activation et la désactivation des
boutons en fonction de la variable v_etat_video_encours.
//gerer l’utilisation des boutons
private void GererLesBoutons() {
if (v_etat_video_encours == EtatVideo.ouvert) {
x_btn_lecture.IsEnabled = true;
x_btn_pause.IsEnabled = false;
340 Développez des applications Internet avec Silverlight 5
x_btn_arret.IsEnabled = false;
}
if (v_etat_video_encours == EtatVideo.lecture) {
x_btn_lecture.IsEnabled = false;
x_btn_pause.IsEnabled = true;
x_btn_arret.IsEnabled = true;
}
if (v_etat_video_encours == EtatVideo.arret) {
x_btn_lecture.IsEnabled = true;
x_btn_pause.IsEnabled = false;
x_btn_arret.IsEnabled = false;
}
if (v_etat_video_encours == EtatVideo.pause) {
x_btn_lecture.IsEnabled = true;
x_btn_pause.IsEnabled = false;
x_btn_arret.IsEnabled = true;
}
}
Le bouton x_btn_lecture démarre la lecture de la vidéo par la méthode Play, le
bouton x_btn_pause met la vidéo en pause par la méthode Pause (repère n°3 de la
figure 11.4), et le bouton x_btn_arret arrête la lecture de la vidéo par la méthode
Stop.
//btn lecture
private void x_btn_lecture_Click(object sender, RoutedEventArgs e) {
v_etat_video_encours = EtatVideo.lecture;
x_mediaelement.Volume = v_volume_encours;
x_mediaelement.Balance = v_balance_encours;
x_mediaelement.Play();
GererLesBoutons();
}
//btn arret
private void x_btn_arret_Click(object sender, RoutedEventArgs e) {
v_etat_video_encours = EtatVideo.arret;
x_mediaelement.Stop();
GererLesBoutons();
}
//btn pause
private void x_btn_pause_Click(object sender, RoutedEventArgs e) {
Copyright 2012 Patrice REY

v_etat_video_encours = EtatVideo.pause;
x_mediaelement.Pause();
GererLesBoutons();
}
L’événement Tick de la minuterie permet d’effectuer un relevé de la position du
curseur vidéo, et d’afficher cette position par rapport à la durée totale de la vidéo
(repère n°2 de la figure 11.4). La valeur de la progression de la vidéo est mise à jour
dans le ProgressBar.
CHAPITRE 11 □ L’audio et la vidéo 341

//tick de la minuterie ecoulee


private void m_dispatcher_Tick(object sender, EventArgs e) {
string texte_piste_encours =
x_mediaelement.Position.ToString().Substring(0, 8);
string texte_piste_totale =
x_mediaelement.NaturalDuration.ToString().Substring(0, 8);
x_position_encours.Text = texte_piste_encours + « sur « +
texte_piste_totale;
x_progress.Value = x_mediaelement.Position.TotalSeconds;
}
Quand la vidéo est arrivée en fin de lecture, l’événement MediaEnded se produit.
La méthode Stop rembobine la vidéo. La variable v_etat_video_encours est remise
à son état initial. La valeur du ProgressBar est remise à zéro, et l’état des boutons
est réactualisé.
//fin de lecture
private void x_mediaelement_MediaEnded(object sender, RoutedEventArgs e) {
x_mediaelement.Stop();
v_etat_video_encours = EtatVideo.arret;
x_progress.Value = 0;
GererLesBoutons();
}
Le passage au mode plein écran puis le retour au mode écran normal (repère
n°4 de la figure 11.4), se fait par un clic sur le bouton x_btn_ecran. La propriété
booléenne IsFullScreen permet de savoir à tout moment si le plugin est en mode
plein écran ou pas. Le test de cette propriété permet d’actualiser la propriété
Content du bouton en fonction du mode d’écran dans lequel on se trouve.
//mise en plein ecran
private void x_btn_ecran_Click(object sender, RoutedEventArgs e) {
Application.Current.Host.Content.IsFullScreen =
!Application.Current.Host.Content.IsFullScreen;
if (Application.Current.Host.Content.IsFullScreen == true) {
x_btn_ecran.Content = «Revenir en écran normal»;
} else {
x_btn_ecran.Content = «Passer en plein écran»;
}
}

5 - Le pinceau vidéo

La classe VideoBrush définit un pinceau composé d’une vidéo. Elle fonctionne


en projetant dans un bitmap le rendu d’un contrôle MediaElement dont le nom
est spécifié dans la propriété SourceName. La vidéo est utilisée comme motif du
342 Développez des applications Internet avec Silverlight 5
pinceau, applicable par exemple à la propriété Fill d’un élément Rectangle.
Un VideoBrush est un type d’objet Brush semblable à un LinearGradientBrush ou
un ImageBrush. Toutefois, au lieu de peindre une zone avec une couleur unie, un
dégradé ou une image, il peint celle-ci avec un contenu vidéo. Ce contenu vidéo
est fourni par un objet MediaElement. À l’instar des autres types de pinceau,
vous pouvez utiliser un VideoBrush pour peindre le Fill d’une forme comme un
Rectangle ou le contenu de la géométrie d’un Path, le Background d’un Canvas, ou
le Foreground d’un TextBlock.
Le MediaElement, référencé par le VideoBrush, est caché (propriété Visibility
fixée à Collapsed). La figure 11.5 visualise le résultat obtenu avec l’UserControl
LeVideoBrush.xaml, dans le projet DemoAudiVideo.sln dans le dossier chapitre11.
Un MediaElement x_mediaelement est positionné sur un Canvas. Sa propriété
Source référence un fichier vidéo. Sa propriété AutoPlay est fixée à false. Sa
propriété Visibility est fixée à Collapsed.
Un rectangle x_rect_video est découpé (propriété Clip = «M50,50 L200,50 L300,0
L600,100 L580,200 L500,311 C0,300 0,200 100,200 z» avec l’utilisation du mini-
langage). Sa propriété Fill est assignée par un objet VideoBrush x_video_brush.
Cet objet VideoBrush a sa propriété SourceName fixée à x_mediaelement.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»
HorizontalAlignment= «Center» VerticalAlignment= «Center»
Width= «600» Height= «600»>
<MediaElement Name= «x_mediaelement»
MediaOpened= «x_mediaelement_MediaOpened»
MediaEnded= «x_mediaelement_MediaEnded»
Source= «video/Les_Mysteres_de_lamour.wmv» Visibility= «Collapsed»
AutoPlay= «False»></MediaElement>
<Rectangle Name= «x_rect_video» Stroke= «Black» Width= «600» Height= «311»
Canvas.Left= «0» Canvas.Top= «0»
Clip= «M50,50 L200,50 L300,0 L600,100 L580,200 L500,311
C0,300 0,200 100,200 z» >
<Rectangle.Fill>
<VideoBrush x:Name= «x_video_brush»
SourceName= «x_mediaelement»></VideoBrush>
</Rectangle.Fill>
Copyright 2012 Patrice REY

</Rectangle>
<Button Canvas.Left= «14» Canvas.Top= «317» Content= «Lecture» Height= «23»
Name= «x_btn_lecture» Width= «75» Click= «x_btn_lecture_Click»
Cursor= «Hand»/>
<Button Canvas.Left= «95» Canvas.Top= «317» Content= «Arrêt» Height= «23»
Name= «x_btn_arret» Width= «75» Click= «x_btn_arret_Click» Cursor= «Hand»
/>
</Canvas>
CHAPITRE 11 □ L’audio et la vidéo 343
FIGURE 11.5

Rectangle
x_rect_video

La géométrie du rectangle est obtenue par sa propriété Clip. La vidéo sert de


remplissage. Les boutons x_btn_lecture et x_btn_arret permettent de lire la vidéo
dans ce rectangle découpé.

6 - La vidéo réfléchie

La figure 11.6 visualise le résultat obtenu pour une vidéo réfléchie avec l’UserControl
VideoReflechie.xaml (dans le projet DemoAudiVideo.sln dans le dossier chapitre11).
Pour réaliser une vidéo réfléchie, on commence par ajouter un rectangle x_rect_
video_reflect dont sa propriété Fill est affectée d’un objet VideoBrush, avec son
SourceName faisant référence à un objet MediaElement.
Pour inverser la vidéo, on applique à sa propriété RelativeTransform, une
transformation de type ScaleTransform dont la propriété CenterY est fixée à 0.5 et
la propriété ScaleY est fixée à -1 (effet de miroir horizontal).
Pour estomper le bas du rectangle, on applique un masque d’opacité (propriété
OpacityMask) composé d’un LinearGradientBrush, avec une couleur noire de
départ et une couleur transparente en fin.
<Rectangle Name= «x_rect_video_reflect» Stroke= «Black» Width= «600»
Height= «311» Canvas.Left= «0» Canvas.Top= «350» >
344 Développez des applications Internet avec Silverlight 5
<Rectangle.Fill>
<VideoBrush SourceName= «x_mediaelement»>
<VideoBrush.RelativeTransform>
<ScaleTransform ScaleY= «-1» CenterY= «0.5»></ScaleTransform>
</VideoBrush.RelativeTransform>
</VideoBrush>
</Rectangle.Fill>
<Rectangle.OpacityMask>
<LinearGradientBrush StartPoint= «0,0» EndPoint= «0,1»>
<GradientStop Color= «Black» Offset= «0»></GradientStop>
<GradientStop Color= «Transparent» Offset= «0.6»></GradientStop>
</LinearGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
FIGURE 11.6

Rectangle
x_rect_video

Copyright 2012 Patrice REY

Rectangle
x_rect_video_reflect
CHAPITRE 11 □ L’audio et la vidéo 345

7 - L’acquisition vidéo

La classe VideoCaptureDevice (figure 11.7), qui hérite de la classe CaptureDevice,


représente une caméra installée dans le système. La propriété héritée FriendlyName
renvoie son nom usuel. La propriété DesiredFormat permet d’indiquer le format
vidéo à utiliser pour l’enregistrement. Ce format conditionne les caractéristiques
et la qualité d’image. Un format est décrit par un objet VideoFormat notamment
au moyen des propriétés suivantes:
• FramesPerSecond qui définit le nombre d’images par seconde (par exemple 60
images par seconde).
• PixelWidth qui définit la largeur de l’image en nombre de pixels (par exemple
640 pixels).
• PixelHeight qui définit la hauteur de l’image en nombre de pixels (par exemple
480 pixels).
FIGURE 11.7
346 Développez des applications Internet avec Silverlight 5
La propriété SupportedFormats de la classe VideoCaptureDevice permet de choisir
le format à utiliser pour la propriété DesiredFormat.
La boite de dialogue de configuration de Silverlight, qui s’ouvre par le menu
contextuel du clic droit (figure 11.8 repère n°1), dispose d’un onglet Webcam/mic
(figure 11.8 repère n°3) qui permet à l’utilisateur de choisir la caméra par défaut
parmi celles installées sur le poste de travail.
FIGURE 11.8

2
1

choix de la source
audio

choix de la source
vidéo

La classe CaptureDeviceConfiguration renvoie la caméra par défaut au moyen de la


méthode GetDefaultVideoCaptureDevice. La propriété IsDefaultDevice d’un objet
VideoCaptureDevice indique s’il s’agit de la caméra par défaut. La capture audio ou
vidéo est gérée par la classe CaptureSource. Sa propriété VideoCaptureDevice doit
Copyright 2012 Patrice REY

indiquer la caméra sélectionnée. La capture des images de la caméra est affichée


en affectant l’objet CaptureSource à la source d’un pinceau VideoBrush, lui-même
affecté à la propriété Fill d’un élément Rectangle.
L’enregistrement audio ou vidéo doit être déclenché par une action de l’utilisateur.
Silverlight fournit une boite de dialogue standard ouvrable par la méthode statique
RequestDeviceAccess de la classe CaptureDeviceConfiguration. Silverlight mémorise
CHAPITRE 11 □ L’audio et la vidéo 347

la réponse de l’utilisateur dans la propriété AllowedDeviceAccess de cette classe. La


capture est déclenchée par la méthode Start de l’objet CaptureSource, et elle est
arrêtée par la méthode Stop de ce même objet. La méthode CaptureImageAsync
de la classe CaptureSource permet de prendre un cliché instantané de la capture
vidéo sous la forme d’un objet WriteableBitmap, obtenu dans la propriété Result
de l’argument de l’événement CaptureImageCompleted.
La figure 11.9 visualise le résultat obtenu pour une capture vidéo avec l’UserControl
CaptureWebcam.xaml (dans le projet DemoAudiVideo.sln dans le dossier
chapitre11).
Un premier bouton permet d’accéder à la capture vidéo (figure 11.9 repère n°1 et
n°2). Un deuxième bouton permet de prendre un cliché instantané de la capture
vidéo. Et un troisième bouton (figure 11.9 repère n°3) permet d’arrêter la capture
vidéo.
FIGURE 11.9

1 3

2
348 Développez des applications Internet avec Silverlight 5
Sur le Canvas x_cnv_root sont positionnés un bouton x_btn_ouvrir pour ouvrir la
capture vidéo, un bouton x_btn_fermer pour arrêter la capture vidéo, un bouton
x_btn_photo pour prendre un cliché instantané de la capture vidéo, un rectangle
x_rect_webcam pour restituer la capture vidéo en temps réel, un Image x_img_
photo pour restituer le cliché instantané capturé à partir du flux vidéo, et un
StackPanel x_stack_photo pour stocker des contrôles Image qui représentent les
clichés instantanés réalisés.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»
HorizontalAlignment= «Center» VerticalAlignment= «Center»
Width= «600» Height= «630»>
<Rectangle Canvas.Left= «12» Canvas.Top= «12» Height= «245»
Name= «x_rect_webcam» Stroke= «Black» StrokeThickness= «1»
Width= «318» Fill= «DarkGray» />
<Button Canvas.Left= «368» Canvas.Top= «36» Content= «Ouvrir la capture
vidéo» Height= «32» Name= «x_btn_ouvrir» Width= «220»
FontSize= «14» Click= «x_btn_ouvrir_Click» Cursor= «Hand»/>
<Button Canvas.Left= «368» Canvas.Top= «74» Content= «Fermer la capture
vidéo» Cursor= «Hand» FontSize= «14» Height= «32»
Name= «x_btn_fermer» Width= «220» Click= «x_btn_fermer_Click»/>
<Button Canvas.Left= «183» Canvas.Top= «280» Content= «Prendre une photo»
Cursor= «Hand» FontSize= «14» Height= «32»
Name= «x_btn_photo» Width= «147» Click= «x_btn_photo_Click»/>
<Border BorderThickness= «2» Canvas.Left= «12» Canvas.Top= «318»
BorderBrush= «Black» Background= «DarkGray» Name= «x_border_photo»>
<Image Canvas.Left= «12» Canvas.Top= «327» Height= «245» Name= «x_img_photo»
Stretch= «Fill» Width= «318» />
</Border>
<ScrollViewer Canvas.Left= «345» Canvas.Top= «130» Height= «435»>
<StackPanel Width= «220» HorizontalAlignment= «Center»
VerticalAlignment= «Top» Name= «x_stack_photo»></StackPanel>
</ScrollViewer>
</Canvas>
Le gestionnaire de l’événement Click pour le bouton x_btn_lecture permet
d’ouvrir le flux vidéo en provenance de la caméra. Si la permission est accordée
pour l’utilisation de la caméra (méthodes statiques CaptureDeviceConfiguration.
AllowDeviceAccess et CaptureDeviceConfiguration.RequestDeviceAccess), la
Copyright 2012 Patrice REY

capture est positionnée à l’arrêt (méthode Stop). On récupère la configuration de la


caméra autorisée par défaut par la méthode statique CaptureDeviceConfiguration.
GetDefaultVideoCaptureDevice. La méthode Start démarre la capture qui est
affectée à un VideoBrush, qui lui-même est assigné à la propriété Fill d’un rectangle
x_rect_webcam (pour la restitution du flux vidéo en temps réel).
private CaptureSource capture = new CaptureSource();
//btn ouvrir la capture
CHAPITRE 11 □ L’audio et la vidéo 349

private void x_btn_ouvrir_Click(object sender, RoutedEventArgs e) {


if (CaptureDeviceConfiguration.AllowedDeviceAccess ||
CaptureDeviceConfiguration.RequestDeviceAccess()) {
//la permission de la capture video accordee
//il faut commencer par appeler stop() meme si aucune
//capture a demarre
capture.Stop();
//obtention de la webcam par defaut
capture.VideoCaptureDevice =
CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
if (capture.VideoCaptureDevice == null) {
MessageBox.Show(«votre ordinateur ne possède pas de webcam video»);
} else {
x_btn_ouvrir.IsEnabled = false;
x_btn_fermer.IsEnabled = true;
x_btn_photo.IsEnabled = true;
//on demarre une session de capture
capture.Start();
//on mappe la video en cours a un videobrush
VideoBrush videoBrush = new VideoBrush();
videoBrush.Stretch = Stretch.Uniform;
videoBrush.SetSource(capture);
//on utilise le videobrush pour peindre un rectangle
x_rect_webcam.Fill = videoBrush;
}
}
}
La figure 11.10 visualise la prise de cliché instantané. Le gestionnaire de l’événement
Click du bouton x_btn_photo gère la prise de cliché. Si la capture vidéo est en cours
(propriété State est égale à CaptureState.Started), on démarre une prise de cliché
instantané asynchrone par la méthode CaptureImageAsync. Et on lui ajoute un
gestionnaire pour l’événement CaptureImageCompleted.
//btn prendre une photo
private void x_btn_photo_Click(object sender, RoutedEventArgs e) {
if (capture.State == CaptureState.Started) {
capture.CaptureImageCompleted +=
new EventHandler<CaptureImageCompletedEventArgs>
(capture_CaptureImageCompleted);
capture.CaptureImageAsync();
}
}
L’événement CaptureImageCompleted se produit dès que la capture du cliché
est terminée. On récupère le cliché dans la propriété Result de l’argument
CaptureImageCompletedEventArgs. Ce cliché est affecté à un objet Image
instancié, puis est affecté au StackPanel x_stack_photo. Ne pas oublier de retirer
350 Développez des applications Internet avec Silverlight 5
le gestionnaire CaptureImageCompleted de la capture vidéo (afin d’éviter une
multitude de prise de cliché).
//image capturee suite a une demande asynchrone
private void capture_CaptureImageCompleted(object sender,
CaptureImageCompletedEventArgs e) {
x_img_photo.Source = e.Result;
Image img = new Image();
img.Source = e.Result;
img.Width = 218;
img.Height = 145;
x_stack_photo.Children.Insert(0, img);
capture.CaptureImageCompleted -=
new EventHandler<CaptureImageCompletedEventArgs>
(capture_CaptureImageCompleted);
}

FIGURE 11.10

1 2
La fermeture complète de la capture vidéo peut être faite par la méthode Stop. De
façon à rendre visuellement cette capture interrompue, le rectangle de visualisation
de la capture vidéo x_rect_webcam est rempli par une couleur unie.
//btn fermer la capture
Copyright 2012 Patrice REY

private void x_btn_fermer_Click(object sender, RoutedEventArgs e) {


capture.Stop();
x_btn_ouvrir.IsEnabled = true;
x_btn_fermer.IsEnabled = false;
x_btn_photo.IsEnabled = false;
x_rect_webcam.Fill = new SolidColorBrush(Colors.DarkGray);
x_img_photo.Source = null;
}
C H A P I T R E 12 DANS CE CHAPITRE
• Les styles
• Les classes Style et
Les styles Setter
• Le style implicite et le
et les modèles style hérité
• Style et databinding
• Les modèles
• La classe
ControlTemplate
• ContentPresenter
• La classe
VisualStateManager
Un style Silverlight permet d’appliquer
• VisualStateGroup,
VisualState,
automatiquement des valeurs de propriétés à VisualTransition
un ensemble d’objets cibles. L’utilisation d’un
style défini en ressource facilite la création et la
maintenance d’interface utilisateur par la simple
centralisation des couleurs et des polices.
Vous verrez dans ce chapitre les différentes façons
d’utiliser les styles. Le style implicite, le style hérité
et la liaison de données avec les styles seront
abordés au travers de plusieurs exemples.
Les contrôles disposent d’une caractéristique
importante, ils ne gèrent pas eux-mêmes leur
représentation visuelle. Celle-ci est confiée à un
modèle (un template), qui contient une hiérarchie
d’éléments visuels. Il est ainsi possible de
personnaliser l’apparence visuelle d’un contrôle.
Vous verrez le fonctionnement des modèles ainsi
que la façon de modifier un modèle pour en
changer l’apparence visuelle.
Un contrôle possède généralement un ensemble
d’états comme le focus et le survol par la souris pour
un bouton. Vous verrez comment personnaliser les
états et la logique de transition entre les états.
352 Développez des applications Internet avec Silverlight 5
Les styles représentent le meilleur moyen d’appliquer automatiquement des
valeurs de propriétés à un ensemble d’objets cibles.

1 - Les styles

Un style Silverlight permet d’appliquer automatiquement des valeurs de propriétés


à un ensemble d’objets cibles, au moyen d’objets de type Setter. L’utilisation
d’un style défini en ressource facilite la création et la maintenance d’interface
utilisateur par la simple centralisation des couleurs et des polices, à la façon des
CSS (cascading style sheets). De plus les styles peuvent être hérités.

1.1 - Définition d’un style

Comme le montre la figure 12.1, dans un style, le nom et la valeur de la propriété


cible se définissent au moyen des propriétés Property et Value d’un objet Setter.
Property est le nom de la propriété de l’élément auquel le style est appliqué. Value
est la valeur appliquée à la propriété.
Un style est typiquement stocké dans un dictionnaire de ressources dans lequel il
est identifié par une clé.
FIGURE 12.1

Copyright 2012 Patrice REY


CHAPITRE 12 □ Les styles et les modèles 353

La propriété Setters d’un style (qui est une propriété implicite en XAML) permet
de définir une liste d’objets Setter et donc des valeurs de propriétés pour l’objet
cible. Un objet FrameworkElement peut indiquer le style qu’il souhaite utiliser au
moyen de sa propriété Style qui référence un objet de ce type. Un style peut cibler
un type particulier spécifié dans sa propriété TargetType.
L’UserControl AppliquerStyle.xaml (dans la solution DemoStyle.sln dans le dossier
chapitre12) illustre l’utilisation des styles avec leur personnalisation.
FIGURE 12.2

1 2

3 4

Dans les ressources de l’UserControl, on ajoute un dégradé linéaire à base de


gris, nommé par la clé k_degrade_lineaire, et un dégradé circulaire à base de gris,
nommé k_degrade_circulaire.
<!-- ressources usercontrol -->
<UserControl.Resources>
<LinearGradientBrush x:Key= «k_degrade_lineaire» EndPoint= «1,0.5»
StartPoint= «0,0.5»>
<GradientStop Color= «DimGray» Offset= «0»/>
<GradientStop Color= «White» Offset= «1» />
</LinearGradientBrush>
<RadialGradientBrush x:Key= «k_degrade_circulaire»>
<GradientStop Color= «White» Offset= «0.348» />
<GradientStop Color= «DimGray» Offset= «1» />
</RadialGradientBrush>
...
</UserControl.Resources>
On définit un style, nommé k_rect_bordure_epaisse, qui cible les contrôles de
type Rectangle, et qui assigne à la propriété Stroke la valeur Black, à la propriété
StrokeThickness la valeur 4, et à la propriété Fill la valeur k_degrade_lineaire par
354 Développez des applications Internet avec Silverlight 5
databinding (StaticResource).
On définit un style, nommé k_rect_bordure_pointille, qui cible les contrôles de
type Rectangle, et qui assigne à la propriété Stroke la valeur Black, à la propriété
StrokeDashArray la collection de valeurs «6,2», à la propriété StrokeThickness
la valeur 4, et à la propriété Fill la valeur k_degrade_circulaire par databinding
(StaticResource).
<!-- ressources usercontrol -->
<UserControl.Resources>
...
<Style x:Key= «k_rect_bordure_epaisse» TargetType= «Rectangle»>
<Setter Property= «Stroke» Value= «Black» ></Setter>
<Setter Property= «StrokeThickness» Value= «4»></Setter>
<Setter Property= «Fill»
Value= «{StaticResource k_degrade_lineaire}»></Setter>
</Style>
<Style x:Key= «k_rect_bordure_pointille» TargetType= «Rectangle»>
<Setter Property= «Stroke» Value= «Black» ></Setter>
<Setter Property= «StrokeDashArray» Value= «6,2»></Setter>
<Setter Property= «StrokeThickness» Value= «4»></Setter>
<Setter Property= «Fill»
Value= «{StaticResource k_degrade_circulaire}»></Setter>
</Style>
</UserControl.Resources>
Le Rectangle x_rect1 (figure 12.2 repère n°1) se voit appliquer le style k_rect_
bordure_epaisse par l’assignation de sa propriété Style par la ressource statique
(Style= «{ StaticResource k_rect_bordure_epaisse }»). Le Rectangle x_rect2 (figure
12.2 repère n°2) se voit appliquer le style k_rect_bordure_pointille par l’assignation
de sa propriété Style par la ressource statique (Style= «{ StaticResource k_rect_
bordure_ pointille }»).
<!-- contenu -->
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»
HorizontalAlignment= «Center» VerticalAlignment= «Center»
Width= «600» Height= «630»>
<Rectangle Canvas.Left= «60» Canvas.Top= «45» Height= «55» Name= «x_rect1»
Width= «110» Style= «{StaticResource k_rect_bordure_epaisse}» />
Copyright 2012 Patrice REY

<Rectangle Canvas.Left= «385» Canvas.Top= «22» Height= «106» Name= «x_rect2»


Width= «119» Style= «{StaticResource k_rect_bordure_pointille}»>
</Rectangle>
...
</Canvas>
Dans la définition XAML des contrôles Rectangle, les propriétés déjà définies dans
le style à appliquer aux contrôles ne sont pas redéfinies. Si par exemple, pour le
Rectangle x_rect3, on redéfinit la propriété StrokeThickness, c’est cette redéfinition
CHAPITRE 12 □ Les styles et les modèles 355

qui est prise en compte (figure 12.2 repère n°3) avec une propriété StrokeThickness
égale à 1 et non pas à 4 (comme dans le style). Si par exemple, pour le Rectangle
x_rect4, on redéfinit la propriété StrokeDashArray, c’est cette redéfinition qui est
prise en compte (figure 12.2 repère n°4) avec une propriété StrokeDashArray égale
à «1,1» et non pas à «6,2» (comme dans le style).
<!-- contenu -->
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»
HorizontalAlignment= «Center» VerticalAlignment= «Center»
Width= «600» Height= «630»>
...
<Rectangle Canvas.Left= «60» Canvas.Top= «194» Height= «55» Name= «x_rect3»
Style= «{StaticResource k_rect_bordure_epaisse}» Width= «110»
StrokeThickness= «1»/>
<Rectangle Canvas.Left= «385» Canvas.Top= «164» Height= «106»
Name= «x_rect4»
Style= «{StaticResource k_rect_bordure_pointille}» Width= «119»
StrokeDashArray= «1,1»/>
</Canvas>

1.2 - Le style implicite

Le fait de spécifier un style à chaque objet FrameworkElement peut s’avérer


fastidieux. Il est possible d’appliquer automatiquement un style à tous les objets
d’un type donné sauf pour ceux ayant explicitement renseigné leur propriété Style.
Pour cela, Silverlight tire parti du fait que la clé d’un élément de dictionnaire
de ressources n’est pas forcément une chaîne de caractères, mais peut être de
n’importe quel type. En l’occurrence, si la clé d’un style dans un dictionnaire de
ressources est un objet Type, le style s’applique automatiquement à tous les
objets de ce type. Si le style dispose d’une propriété TargetType (de la classe Type)
et ne spécifie pas de clé, sa clé prend automatiquement la valeur indiquée dans la
propriété TargetType. De ce fait, un style sans clé s’applique automatiquement à
tous les objets du type défini dans TargetType.
L’UserControl StyleImplicite.xaml (dans la solution DemoStyle.sln dans le dossier
chapitre12) illustre l’utilisation des styles implicites.
Dans l’exemple, on définit un style implicite qui cible les contrôles TextBlock (par
sa propriété TargetType). On définit un style, nommé par la clé k_comic_sans_MS,
qui cible aussi les contrôles TextBlock.
<!-- ressources usercontrol -->
<UserControl.Resources>
<Style TargetType= «TextBlock»>
<Setter Property= «FontFamily» Value= «Verdana»></Setter>
356 Développez des applications Internet avec Silverlight 5
<Setter Property= «FontSize» Value= «14»></Setter>
<Setter Property= «Foreground» Value= «Black»></Setter>
<Setter Property= «TextWrapping» Value= «Wrap»></Setter>
</Style>
<Style x:Key= «k_comic_sans_MS» TargetType= «TextBlock»>
<Setter Property= «FontFamily» Value= «Comic Sans MS»></Setter>
<Setter Property= «FontSize» Value= «18»></Setter>
<Setter Property= «Foreground» Value= «Black»></Setter>
<Setter Property= «TextWrapping» Value= «Wrap»></Setter>
</Style>
</UserControl.Resources>

FIGURE 12.3

Sur le Canvas x_cnv_root, on positionne deux contrôles TextBlock avec un texte


dans leur propriété Text. Le premier TextBlock n’a aucun attribut Style explicitement
renseigné. Le style qui s’appliquera à son texte sera le style par défaut qui cible les
TextBlock (Verdana 14). Le deuxième TextBlock a sa propriété Style explicitement
renseignée par Style= «{ StaticResource k_comic_sans_MS }». Le style appliqué à
ce texte sera celui qui correspond à la clé k_comic_sans_MS (c’est-à-dire Comic
Sans MS 18).
<!-- contenu -->
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»
HorizontalAlignment= «Center» VerticalAlignment= «Center»
Width= «600» Height= «630»>
<TextBlock Canvas.Left= «51» Canvas.Top= «23» Height= «60» Width= «506»>
Copyright 2012 Patrice REY

ce texte est le contenu d’un TextBlock avec le style par défaut, dont les
caractéristiques sont Verdana, taille 14, couleur noire, et
TextWrapping=Wrap
</TextBlock>
<TextBlock Canvas.Left= «51» Canvas.Top= «89» Height= «88» Width= «506»
Style= «{StaticResource k_comic_sans_MS}»>
ce texte est le contenu d’un TextBlock avec le style k_comic_sans_MS,
dont les caractéristiques sont Comic sans MS, taille 18, couleur noire, et
TextWrapping=Wrap
CHAPITRE 12 □ Les styles et les modèles 357

</TextBlock>
</Canvas>

1.3 - L’héritage de style

La propriété BasedOn permet de faire de l’héritage de style, et de bénéficier dans


un style hérité des objets Setter définis dans les styles ancêtres.
L’UserControl StyleHeritee.xaml (dans la solution DemoStyle.sln dans le dossier
chapitre12) illustre l’utilisation des styles hérités (figure 12.4).
FIGURE 12.4

1 2

On définit un style nommé k_bouton_base qui cible les contrôles Button, en


spécifiant les propriétés FontFamily, FontSize, Foreground et BorderThickness.
Le style nommé k_bouton_carre cible les boutons (propriété TargetType =
«Button») et hérite du style k_bouton_base (propriété BasedOn= «{ StaticResource
k_bouton_base }»). Ce style apporte la propriété Width à une valeur de 100 et la
propriété Height à une valeur de 100.
Le style nommé k_bouton_vertical cible les boutons (propriété TargetType =
«Button») et hérite du style k_bouton_base (propriété BasedOn= «{ StaticResource
k_bouton_base }»). Ce style apporte la propriété Width à une valeur de 105, la
propriété Height à une valeur de 136, et la propriété BorderBrush à une valeur
Black.
<!-- ressources usercontrol -->
<UserControl.Resources>
<Style x:Key= «k_bouton_base» TargetType= «Button»>
<Setter Property= «FontFamily» Value= «Verdana»></Setter>
<Setter Property= «FontSize» Value= «16»></Setter>
<Setter Property= «Foreground» Value= «Black»></Setter>
<Setter Property= «BorderThickness» Value= «5»></Setter>
</Style>
<Style x:Key= «k_bouton_carre» TargetType= «Button»
358 Développez des applications Internet avec Silverlight 5
BasedOn= «{StaticResource k_bouton_base}»>
<Setter Property= «Width» Value= «100»></Setter>
<Setter Property= «Height» Value= «100»></Setter>
</Style>
<Style x:Key= «k_bouton_vertical» TargetType= «Button»
BasedOn= «{StaticResource k_bouton_base}»>
<Setter Property= «Width» Value= «105»></Setter>
<Setter Property= «Height» Value= «136»></Setter>
<Setter Property= «BorderBrush» Value= «Black»></Setter>
</Style>
</UserControl.Resources>
Le bouton x_btn1 a le style k_bouton_carre appliqué (figure 12.4 repère n°1) et le
bouton x_btn2 a le style k_bouton_vertical appliqué (figure 12.4 repère n°2).
<!-- contenu -->
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»
HorizontalAlignment= «Center» VerticalAlignment= «Center»
Width= «600» Height= «630»>
<Button Canvas.Left= «97» Canvas.Top= «26» Name= «x_btn1»
Style= «{StaticResource k_bouton_carre}»>
<Button.Content>
<Image Source= «/DemoStyle;component/contenu/livre.png»
Stretch= «None»></Image>
</Button.Content>
</Button>
<Button Canvas.Left= «325» Canvas.Top= «12» Name= «x_btn2»
Style= «{StaticResource k_bouton_vertical}»>
<Image Source= «/DemoStyle;component/contenu/monte%20cristo.jpg»
Stretch= «Fill» Height= «109» Width= «74» />
</Button>
</Canvas>

1.4 - Style et databinding

Le mécanisme de la liaison de données (databinding) permet d’associer des


données aux éléments visuels de l’interface utilisateur pour automatiser l’affichage
et la saisie. Les éléments visuels pouvant eux-mêmes être source de données, le
databinding permet également d’associer des éléments visuels pour augmenter la
Copyright 2012 Patrice REY

richesse des interfaces utilisateur et la productivité de développement.


L’UserControl StyleEtDatabinding.xaml (dans la solution DemoStyle.sln dans le
dossier chapitre12) illustre l’utilisation du databinding avec les styles (figure 12.5).
Le bouton x_btn1 (figure 12.5 repère n°1) utilise le style k_btn_style_1 (style avec
une police Verdana, taille 18 et poids gras). Pour une meilleure réutilisation, on
ajoute une police FontFamily avec une clé x:Key = «k_police», une taille, de type
CHAPITRE 12 □ Les styles et les modèles 359

double (avec l’ajout d’une référence à l’espace de noms xmlns:system = «clr-nam


espace:System;assembly=mscorlib»), avec une clé x:Key = «k_police_taille», et un
poids FontWeight avec une clé x:Key = «k_police_poids». Le style, au travers de
ses objets Setter, fait appel aux ressources statiques k_police, k_police_taille et
k_police_poids.
FIGURE 12.5

<Canvas.Resources>
<!-- -->
<FontFamily x:Key= «k_police»>Verdana</FontFamily>
<system:Double x:Key= «k_police_taille»>18</system:Double>
<FontWeight x:Key= «k_police_poids»>Bold</FontWeight>
<Style x:Key= «k_btn_style_1» TargetType= «Button»>
<Setter Property= «Padding» Value= «5» />
<Setter Property= «Margin» Value= «5» />
<Setter Property= «FontFamily» Value= «{StaticResource k_police}» />
<Setter Property= «FontSize» Value= «{StaticResource k_police_taille}» />
<Setter Property= «FontWeight» Value= «{StaticResource k_police_poids}» />
</Style>
...
</Canvas.Resources>
<Canvas x:Name= «x_cnv_root»>
<Button Canvas.Left= «7» Canvas.Top= «7» Content= «bouton avec le style
k_btn_style_1 (verdana 18 bold)» Height= «44» Name= «x_btn1» Width= «576»
Style= «{StaticResource k_btn_style_1}» />
...
</Canvas>

Le bouton x_btn2 (figure 12.5 repère n°2) utilise le style k_btn_style_2. Ce style a
des objets Setter qui font appel à des propriétés d’un objet PoliceEcriture. La classe
PoliceEcriture est déclarée par code. Elle possède les propriétés PoliceFonte pour
instancier une fonte d’écriture, PoliceTaille pour instancier une taille d’écriture, et
360 Développez des applications Internet avec Silverlight 5
PolicePoids pour instancier un poids d’écriture.
Pour accéder à cette classe en XAML, on ajoute une référence à l’espace
de noms contenant cette classe, en l’intitulant <local> (xmlns:local = «clr-
namespace:DemoStyle»). On ajoute un objet PoliceEcriture avec la clé k_police_
personnalise (<local:PoliceEcriture>) en spécifiant une police Comic Sans MS, de
taille 16 et de poids Normal. L’objet Setter, qui référence la propriété FontFamily,
a sa propriété Value qui est liée par databinding à k_police_personnalise pour
la propriété PoliceFonte. L’objet Setter, qui référence la propriété FontSize, a
sa propriété Value qui est liée par databinding à k_police_personnalise pour la
propriété PoliceTaille. Et l’objet Setter, qui référence la propriété FontWeight, a
sa propriété Value qui est liée par databinding à k_police_personnalise pour la
propriété PolicePoids.
<Canvas.Resources>
<local:PoliceEcriture x:Key= «k_police_personnalise»
PoliceFonte= «Comic Sans MS» PoliceTaille= «16»
PolicePoids= «Normal»></local:PoliceEcriture>
<Style x:Key= «k_btn_style_2» TargetType= «Button»>
<Setter Property= «Padding» Value= «5» />
<Setter Property= «Margin» Value= «5» />
<Setter Property= «FontFamily»
Value= «{Binding Source={StaticResource k_police_personnalise},
Path=PoliceFonte}» />
<Setter Property= «FontSize»
Value= «{Binding Source={StaticResource k_police_personnalise},
Path=PoliceTaille}» />
<Setter Property= «FontWeight»
Value= «{Binding Source={StaticResource k_police_personnalise},
Path=PolicePoids}» />
</Style>
...
</Canvas.Resources>
<Canvas x:Name= «x_cnv_root»>
<Button Canvas.Left= «7» Canvas.Top= «73» Content= «bouton avec le style
k_btn_style_2 (comic sans ms 16 normal)» Height= «44» Name= «x_btn2»
Style= «{StaticResource k_btn_style_2}» Width= «576» />
...
Copyright 2012 Patrice REY

</Canvas>
public class PoliceEcriture {
public FontFamily PoliceFonte { get; set; }
public double PoliceTaille { get; set; }
public FontWeight PolicePoids { get; set; }
public PoliceEcriture() {
this.PoliceFonte = new FontFamily(«Verdana»);
this.PoliceTaille = 14;
CHAPITRE 12 □ Les styles et les modèles 361

this.PolicePoids = FontWeights.Normal;
}
}//end class
Le bouton x_btn3 (figure 12.5 repère n°3) utilise le style dico_btn_style qui est
stocké dans le dictionnaire MesStyles.xaml (dans le dossier contenu).
Le fichier MesStyles.xaml est un fichier de type ResourceDictionary, qui déclare le
style dico_btn_style.
<ResourceDictionary
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x= «http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:system= «clr-namespace:System;assembly=mscorlib»>
<!-- mes styles -->
<FontFamily x:Key= «dico_police»>Arial</FontFamily>
<system:Double x:Key= «dico_police_taille»>20</system:Double>
<FontWeight x:Key= «dico_police_poids»>Normal</FontWeight>
<Style x:Key= «dico_btn_style» TargetType= «Button»>
<Setter Property= «Padding» Value= «5» />
<Setter Property= «Margin» Value= «5» />
<Setter Property= «FontFamily» Value= «{StaticResource dico_police}» />
<Setter Property= «FontSize» Value= «{StaticResource dico_police_taille}»
/>
<Setter Property= «FontWeight» Value= «{StaticResource dico_police_poids}»
/>
</Style>
</ResourceDictionary>
Dans les ressources de l’UserControl, on fusionne les dictionnaires de ressources
en ajoutant une référence au dictionnaire MesStyles.xaml (principe déjà vu au
chapitre 2).
<UserControl.Resources>
<!-- utiliser le dictionnaire -->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source= «contenu/MesStyles.xaml»>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
A partir de là, le style dico_btn_style du dictionnaire est alors accessible sous forme
d’une ressource statique (Style = «{StaticResource dico_btn_style}»).
<Canvas x:Name= «x_cnv_root»>
<Button Canvas.Left= «12» Canvas.Top= «140» Content= «bouton avec le style
dico_btn_style (arial 20 normal)» Height= «44» Name= «x_btn3»
Style= «{StaticResource dico_btn_style}» Width= «576» /> ...
</Canvas>
362 Développez des applications Internet avec Silverlight 5

2 - Les modèles (template)

Les contrôles disposent d’une caractéristique importante, ils ne gèrent pas


eux-mêmes leur représentation visuelle. Celle-ci est confiée à un modèle (un
template), objet de type ControlTemplate (figure 12.6), qui contient une hiérarchie
d’éléments visuels. En changeant de template, au moyen de la propriété Template,
il est possible de personnaliser l’apparence visuelle d’un contrôle.
Cette notion fondamentale de template fait apparaître la distinction entre l’arbre
logique et l’arbre visuel des éléments Silverlight affichés. L’arbre logique est défini
par le code XAML alors que l’arbre visuel prend en compte la composition des
template des éléments affichés.
FIGURE 12.6

2.1 - Visualiser un template

L’UserControl VisualiserTemplate.xaml (dans la solution DemoTemplate.sln dans le


Copyright 2012 Patrice REY

dossier chapitre12) visualise un bouton classique et un bouton avec un template


personnalisé (figure 12.7).
Le bouton x_btn1 représente un bouton classique par défaut tel qu’il est réalisé
par Silverlight (figure 12.7 repère n°1).
<Button Canvas.Left= «28» Canvas.Top= «12» Content= «un bouton classique»
Height= «35» Name= «x_btn1» Width= «228»
CHAPITRE 12 □ Les styles et les modèles 363

FontFamily= «Verdana» FontSize= «14» Cursor= «Hand» />


FIGURE 12.7

1 2

Le bouton x_btn2 représente un bouton dont on a modifié le template (figure 12.7


repère n°2). En XAML, pour modifier le template d’un bouton (et des contrôles
en général), il faut affecter un objet ControlTemplate à la propriété Template du
contrôle (ici <Button.Template>).
Par exemple on utilise un objet Border, qui matérialise une bordure au moyen de
ses propriétés BorderBrush (couleur de la bordure), BorderThickness (épaisseur
du trait) autour d’un élément enfant unique, et Background (couleur du fond). Ses
angles peuvent être arrondis au moyen de sa propriété CornerRadius. Il dispose
d’une propriété Padding qui définit les marges autour de l’élément enfant. L’élément
enfant est unique, il peut être un TextBlock comme ici, ou bien un conteneur non
visuel comme un StackPanel, un Canvas, un Grid pour un assemblage d’éléments.
Ici nous avons une bordure de couleur noire, avec une épaisseur de 5, un fond de
couleur LightGray, un arrondi des coins à 10, qui héberge un TextBlock dont le
contenu de la propriété Text est écrit en noir.
<Button Canvas.Left= «310» Canvas.Top= «12» Height= «35» Width= «278»
Name= «x_btn2» FontFamily= «Verdana» FontSize= «12»>
<Button.Template>
<ControlTemplate TargetType= «Button» >
<Border BorderBrush= «Black» BorderThickness= «5» CornerRadius= «10»
Background= «LightGray» Cursor= «Hand»>
<TextBlock Foreground= «Black» Text= «un bouton avec un template
personnalisé» VerticalAlignment= «Center»
HorizontalAlignment= «Center»></TextBlock>
</Border>
</ControlTemplate>
</Button.Template>
</Button>

2.2 - Le ContentPresenter

L’objet ContentPresenter a pour rôle d’afficher le contenu défini dans la propriété


364 Développez des applications Internet avec Silverlight 5
Content d’un contrôle. Il représente le visuel de l’entité courante.
En général, l’utilisation directe de ContentPresenter s’effectue dans le
ControlTemplate d’un ContentControl pour marquer l’emplacement où le
contenu doit être ajouté. Le ContentPresenter doit utiliser une liaison de modèle
pour associer la propriété ContentControl.Content à la propriété ContentPresenter.
Content.
L’extension de balisage TemplateBinding lie la valeur d’une propriété dans un
modèle de contrôle à la valeur d’une autre propriété exposée sur le contrôle
basé sur un modèle. TemplateBinding peut uniquement être utilisé dans une
définition ControlTemplate en XAML. Lors de l’implémentation d’un processeur
XAML Silverlight, il n’existe aucune représentation de classe de stockage pour
TemplateBinding. TemplateBinding est exclusivement utilisé en balisage XAML, à
l’aide de la syntaxe {} qui indique à un processeur XAML que le contenu doit être
traité par une extension de balisage.
L’UserControl LeContentPresenter.xaml (dans la solution DemoTemplate.sln dans le
dossier chapitre12) visualise l’utilisation et la signification d’un ContentPresenter
(figure 12.8).
FIGURE 12.8

Le bouton x_btn1 (figure 12.8 repère n°1) utilise comme modèle k_btn_modele1.
Le contenu du modèle contient un TextBlock avec un texte «modèle personnalisé».
Quand on applique au bouton ce modèle, on s’aperçoit que le texte du bouton
(propriété Content) n’est pas prise en compte.
Copyright 2012 Patrice REY

<UserControl.Resources>
<ControlTemplate x:Key= «k_btn_modele1» TargetType= «Button» >
<Border BorderBrush= «Black» BorderThickness= «5» CornerRadius= «10»
Background= «LightGray» Cursor= «Hand»>
<TextBlock Foreground= «Black» VerticalAlignment= «Center»
HorizontalAlignment= «Center» Text= «modéle personnalisé»></TextBlock>
</Border>
</ControlTemplate> ...
</UserControl.Resources>
CHAPITRE 12 □ Les styles et les modèles 365

<Button Canvas.Left= «141» Canvas.Top= «12» Height= «34» Name= «x_btn1»


Width= «330» Content= «un autre bouton avec son template en ressource»
Template= «{StaticResource k_btn_modele1}»></Button>
Pour remédier à ce problème, il faut utiliser l’extension de balisage markup
TemplateBinding qui permet de lier la propriété concernée à la propriété exposée
dans le contrôle. Par exemple le bouton x_btn2 (figure 12.8 repère n°2) utilise le
modèle k_btn_modele2. Ce modèle utilise un ContentPresenter pour afficher le
contenu du contrôle. En spécifiant que la propriété Content du ContentPresenter
est liée à la propriété Content exposée, par l’extension de balisage TemplateBinding,
cela permet de prendre en compte la propriété Content exposée par le contrôle
au niveau du template. Donc la chaîne «un autre bouton avec son template en
ressource» sera affichée cette fois sur le bouton.
<UserControl.Resources>
...
<ControlTemplate x:Key= «k_btn_modele2» TargetType= «Button» >
<Border BorderBrush= «Black» BorderThickness= «5» CornerRadius= «10»
Background= «LightGray» Cursor= «Hand»>
<ContentPresenter Content= «{TemplateBinding Content}»
HorizontalAlignment= «{TemplateBinding HorizontalAlignment}»
VerticalAlignment= «{TemplateBinding VerticalAlignment}»>
</ContentPresenter>
</Border>
</ControlTemplate>
</UserControl.Resources>
<Button Canvas.Left= «141» Canvas.Top= «63» Content= «un autre bouton avec son
template en ressource» Height= «34»
Name= «x_btn2» Template= «{StaticResource k_btn_modele2}» Width= «330»
HorizontalAlignment= «Center» VerticalAlignment= «Center» />

2.3 - Personnaliser un template

L’UserControl PersonnaliserControlTemplate.xaml (dans la solution DemoTemplate.


sln dans le dossier chapitre12) visualise la personnalisation d’un ControlTemplate
d’un bouton pour obtenir un effet de verre (figure 12.9).
FIGURE 12.9
366 Développez des applications Internet avec Silverlight 5
La figure 12.10 illustre les étapes qui vont être détaillées pour obtenir un bouton
avec un effet de verre (glass button).
FIGURE 12.10

1 2

3 4

On positionne sur le Canvas x_cnv_root un bouton x_btn1 dont sa propriété Style


est obtenue de la ressource statique style_bouton, et avec un contenu texte dans
sa propriété Content («Bouton avec un effet de verre»).
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»
HorizontalAlignment= «Center» VerticalAlignment= «Center»
Width= «600» Height= «630»>
<Button Canvas.Left= «26» Canvas.Top= «32» Content= «Bouton avec un effet
verre» Height= «46» Name= «x_btn1» Width= «283»
Style= «{StaticResource style_bouton}»/>
</Canvas>
Dans les ressources de l’UserControl, on commence par ajouter les différents
pinceaux de couleurs et les différents styles qui seront nécessaires à la composition
du modèle du bouton.
<UserControl.Resources>
<SolidColorBrush x:Key= «pinceau_fond» Color= «DarkGray» />
<Color x:Key= «couleur_fond_survol» >LightGray</Color>
<Color x:Key= «couleur_fond_pressee» >LightSlateGray</Color>
<SolidColorBrush x:Key= «pinceau_ecriture» Color= «Black» />
<SolidColorBrush x:Key= «pinceau_focus» Color= «DimGray» />
Copyright 2012 Patrice REY

<LinearGradientBrush x:Key= «pinceau_degrade_refraction» StartPoint= «0.5,0»


EndPoint= «0.5,1»>
<GradientStop Offset= «0.6» Color= «Transparent» />
<GradientStop Offset= «0.9» Color= «White» />
</LinearGradientBrush>
<LinearGradientBrush x:Key= «pinceau_degrade_reflexion» StartPoint= «0.5,0»
EndPoint= «0.5,1»>
<GradientStop Offset= «0» Color= «White» />
<GradientStop Offset= «0.2» Color= «White»/>
CHAPITRE 12 □ Les styles et les modèles 367

<GradientStop Offset= «1» Color= «#51F5F5F5» />


</LinearGradientBrush>
<Style x:Key= «style_rect_coin_arrondi» TargetType= «Rectangle»>
<Setter Property= «RadiusX» Value= «8» />
<Setter Property= «RadiusY» Value= «8» />
</Style>
...
</UserControl.Resources>
Le style style_bouton est un style qui cible les contrôles Button (TargetType). Sa
configuration minimale est composée d’objets Setter qui fixent les propriétés
classiques Margin, Padding, Foreground, Background, FontFamily, FontSize et
Cursor.
<UserControl.Resources>
...
<Style x:Key= «style_bouton» TargetType= «Button»>
<Setter Property= «Margin» Value= «2» />
<Setter Property= «Padding» Value= «10» />
<Setter Property= «Foreground» Value= «{StaticResource pinceau_ecriture}»
/>
<Setter Property= «Background» Value= «{StaticResource pinceau_fond}» />
<Setter Property= «FontFamily» Value= «Verdana»></Setter>
<Setter Property= «FontSize» Value= «18»></Setter>
<Setter Property= «Cursor» Value= «Hand»></Setter>
...
</Style>
</UserControl.Resources>
La modification du modèle du bouton passe par l’ajout d’un objet Setter dont
la propriété Property cible la propriété Template, et dont la propriété Value est
un objet ControlTemplate (qui cible les objets Button). Nous aurons donc la
configuration suivante:
<UserControl.Resources>
...
<Style x:Key= «style_bouton» TargetType= «Button»>
...
<Setter Property= «Template»>
<Setter.Value>
<ControlTemplate TargetType= «Button»>
<Grid>
<!-- contenu des boutons -->
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
368 Développez des applications Internet avec Silverlight 5
</UserControl.Resources>
Tout le contenu du bouton va être positionné sur un Grid. Les éléments apportés
vont être superposés les uns sur les autres. On commence par ajouter le fond
du bouton qui est représenté par un rectangle, nommé fond_rectangle, avec des
coins arrondis (figure 12.10 repère n°1).
<UserControl.Resources>
...
<Style x:Key= «style_bouton» TargetType= «Button»>
...
<Setter Property= «Template»>
<Setter.Value>
<ControlTemplate TargetType= «Button»>
<Grid>
<!-- contenu des boutons -->
<Rectangle Name= «fond_rectangle»
Style= «{StaticResource style_rect_coin_arrondi}»
Fill= «{TemplateBinding Background}» />
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
On superpose un rectangle, doté d’un masque d’opacité, pour simuler l’effet de
réfraction de la lumière sur un bouton à l’aspect de verre (figure 12.10 repère n°2).
<UserControl.Resources>
...
<Style x:Key= «style_bouton» TargetType= «Button»>
...
<Setter Property= «Template»>
<Setter.Value>
<ControlTemplate TargetType= «Button»>
<Grid>
<!-- contenu des boutons -->
...
<Rectangle Style= «{StaticResource style_rect_coin_arrondi}»
Copyright 2012 Patrice REY

Stroke= «{TemplateBinding BorderBrush}»


StrokeThickness= «{TemplateBinding BorderThickness}»
Fill= «White»
OpacityMask= «{StaticResource pinceau_degrade_refraction}» />
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
CHAPITRE 12 □ Les styles et les modèles 369

</Style>
</UserControl.Resources>
On superpose un rectangle, doté d’un masque d’opacité, pour simuler l’effet de
réflexion de la lumière sur un bouton à l’aspect de verre (figure 12.10 repère n°3).
<UserControl.Resources>
...
<Style x:Key= «style_bouton» TargetType= «Button»>
...
<Setter Property= «Template»>
<Setter.Value>
<ControlTemplate TargetType= «Button»>
<Grid>
<!-- contenu des boutons -->
...
<Rectangle Fill= «White»
OpacityMask= «{StaticResource pinceau_degrade_reflexion}»
Height= «8» RadiusX= «5» RadiusY= «5» Margin= «2»
VerticalAlignment= «Top» />
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
On positionne un ContentPresenter avec des propriétés qui référencent les
propriétés exposées, par l’utilisation de l’extension de balisage TemplateBinding
(figure 12.10 repère n°4).
<UserControl.Resources>
...
<Style x:Key= «style_bouton» TargetType= «Button»>
...
<Setter Property= «Template»>
<Setter.Value>
<ControlTemplate TargetType= «Button»>
<Grid>
<!-- contenu des boutons -->
...
<ContentPresenter Content= «{TemplateBinding Content}»
ContentTemplate= «{TemplateBinding ContentTemplate}»
VerticalAlignment= «{TemplateBinding VerticalContentAlignment}»
HorizontalAlignment=
«{TemplateBinding HorizontalContentAlignment}»
Margin= «{TemplateBinding Padding}» />
...
</Grid>
370 Développez des applications Internet avec Silverlight 5
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
On termine en positionnant un rectangle nommé focus_rectangle, caché dans un
premier temps, qui représentera le bouton quand ce dernier aura le focus (figure
12.10 repère n°5).
<UserControl.Resources>
...
<Style x:Key= «style_bouton» TargetType= «Button»>
...
<Setter Property= «Template»>
<Setter.Value>
<ControlTemplate TargetType= «Button»>
<Grid>
<!-- contenu des boutons -->
...
<Rectangle Name= «focus_rectangle»
Style= «{StaticResource style_rect_coin_arrondi}»
Visibility= «Collapsed»
Stroke= «{StaticResource pinceau_focus}» />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
Jusqu’ici, le survol du bouton par la souris ne fait que montrer le curseur main
(la propriété Cursor = «hand»), sans aucune animation. Le paragraphe suivant
indique comment il faut procéder pour ajouter des états visuels au bouton dans la
modification du modèle.

2.4 - La gestion des états visuels

La classe VisualStateManager gère les états et la logique de transition entre


Copyright 2012 Patrice REY

les états des contrôles (figure 12.11). Le modèle objet du VisualStateManager


représente les catégories sous la forme d’objets de classe VisualStateGroup,
chacun contenant des objets VisualState pour représenter les états (définis par
leur propriété Name).
Les noms des groupes et des états d’un contrôle sont documentés dans la
déclaration de type sous la forme d’attributs TemplateVisualState. La figure 12.12
CHAPITRE 12 □ Les styles et les modèles 371

montre ces états mentionnés dans la déclaration de type du contrôle Button.


FIGURE 12.11

FIGURE 12.12

les états d’un


Button
372 Développez des applications Internet avec Silverlight 5
Les groupes d’états sont les suivants:
• le groupe CommonStates contient les états Pressed, MouseOver, Normal et
Disabled.
• le groupe FocusStates contient les états Unfocused et Focused.
Les groupes d’états applicables à un template doivent être assignés au niveau
du conteneur racine de la structure visuelle, au moyen de la propriété attachée
VisualStateManager.VisualStateGroups. Chaque VisualStateGroup contient dans
sa propriété States une collection d’objets VisualState. Cette propiété est implicite
en XAML. Le visuel associé à un objet VisualState est décrit au moyen d’une
animation dans sa propriété Storyboard. Le type d’animation doit correspondre à
celui de la propriété ciblée. Si on ne souhaite pas animer le visuel de l’état, la durée
de l’animation doit être initialisée à zéro.
Pour accéder à l’objet VisualStateManager en XAML, il faut ajouter une référence
à l’espace de noms System.Windows, que l’on intitule vsm (xmlns:vsm = «clr-
namespace:System.Windows;assembly=System.Windows»). La démarche consiste
à visualiser les groupes d’états (VisualStateGroup) et les états qu’ils contiennent
(VisualState). Pour notre bouton, nous avons les groupes d’états CommonStates
(avec les états Normal, MouseOver, Pressed et Disabled), et FocusStates (avec les
états Focused et Unfocused).
<Style x:Key= «style_bouton» TargetType= «Button»>
...
<Setter Property= «Template»>
<Setter.Value>
<ControlTemplate TargetType= «Button»>
<Grid>
<!-- gérer les états des boutons -->
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name= «CommonStates»>
...
<vsm:VisualState x:Name= «Normal»>
...
</vsm:VisualState>
<vsm:VisualState x:Name= «MouseOver»>
...
Copyright 2012 Patrice REY

</vsm:VisualState>
<vsm:VisualState x:Name= «Pressed»>
...
</vsm:VisualState>
<vsm:VisualState x:Name= «Disabled»>
...
</vsm:VisualState>
</vsm:VisualStateGroup>
CHAPITRE 12 □ Les styles et les modèles 373

<vsm:VisualStateGroup x:Name= «FocusStates»>


<vsm:VisualState x:Name= «Focused»>
...
</vsm:VisualState>
<vsm:VisualState x:Name= «Unfocused»>
...
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<!-- contenu des boutons -->
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Dans le groupe d’état CommonStates, l’état Normal ne présente aucune animation
(on garde l’état du bouton par défaut) donc on aura en XAML:
<vsm:VisualStateGroup x:Name= «CommonStates»>
...
<vsm:VisualState x:Name= «Normal»></vsm:VisualState>
...
</vsm:VisualStateGroup>
Dans le groupe d’état CommonStates, l’état MouseOver consiste à animer la couleur
de remplissage du rectangle fond_rectangle. Pour cela on utilise une animation de
type ColorAnimation dont la cible est TargetName = «fond_rectangle» et dont
la propriété ciblée est TargetProperty = «(Shape.Fill).(SolidColorBrush.Color)».
La couleur à atteindre est celle représentée par la couleur en ressource couleur_
fond_survol. La figure 12.13 illustre le résultat obtenu.
FIGURE 12.13

le bouton avec son état le bouton avec son état


Normal MouseOver

<vsm:VisualStateGroup x:Name= «CommonStates»>


...
<vsm:VisualState x:Name= «MouseOver»>
<Storyboard>
<ColorAnimation Storyboard.TargetName= «fond_rectangle»
374 Développez des applications Internet avec Silverlight 5
Storyboard.TargetProperty= «(Shape.Fill).(SolidColorBrush.Color)»
Duration= «0» To= «{StaticResource couleur_fond_survol}» />
</Storyboard>
</vsm:VisualState>
...
</vsm:VisualStateGroup>
Dans le groupe d’état CommonStates, l’état Pressed consiste à animer la couleur
de remplissage du rectangle fond_rectangle. Pour cela on utilise une animation
de type ColorAnimation dont la cible est TargetName = «fond_rectangle» et dont
la propriété ciblée est TargetProperty = «(Shape.Fill).(SolidColorBrush.Color)». La
couleur à atteindre est celle représentée par la couleur en ressource couleur_
fond_pressee. La figure 12.14 illustre le résultat obtenu.
<vsm:VisualStateGroup x:Name= «CommonStates»>
...
<vsm:VisualState x:Name= «Pressed»>
<Storyboard>
<ColorAnimation Storyboard.TargetName= «fond_rectangle»
Storyboard.TargetProperty= «(Shape.Fill).(SolidColorBrush.Color)»
Duration= «0» To= «{StaticResource couleur_fond_pressee}» />
</Storyboard>
</vsm:VisualState>
...
</vsm:VisualStateGroup>

FIGURE 12.14

le bouton avec son état le bouton avec son état


Normal Pressed

Dans le groupe d’état CommonStates, l’état Disabled ne présente aucune animation


(on garde l’état du bouton par défaut) donc on aura en XAML:
<vsm:VisualStateGroup x:Name= «CommonStates»>
...
Copyright 2012 Patrice REY

<vsm:VisualState x:Name= «Disabled»></vsm:VisualState>


...
</vsm:VisualStateGroup>
Dans le groupe d’état FocusStates, l’état Focused consiste à changer la propriété
Visibility du rectangle focus_rectangle qui sert à visualiser le bouton quand il a
le focus (passage de l’état Collapsed d’origine à l’état Visible). Pour cela on utilise
une animation du type ObjectAnimationUsingKeyFrame pour animer la propriété
CHAPITRE 12 □ Les styles et les modèles 375

Visibility. Cette animation cible le rectangle (TargetName = «focus_rectangle»)


et la propriété Visibility (TargetProperty = «Visibility»). La figure 12.15 illustre le
résultat obtenu.
<vsm:VisualStateGroup x:Name= « FocusStates «>
...
<vsm:VisualState x:Name= « Focused «>
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName= «focus_rectangle»
Storyboard.TargetProperty= «Visibility» Duration= «0»>
<DiscreteObjectKeyFrame KeyTime= «0»>
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
...
</vsm:VisualStateGroup>
FIGURE 12.15

le bouton avec son état le bouton avec son état


Normal Focused

Dans le groupe d’état FocusStates, l’état Unfocused ne présente aucune animation


(on garde l’état du bouton par défaut) donc on aura en XAML:
<vsm:VisualStateGroup x:Name= « FocusStates «>
...
<vsm:VisualState x:Name= « Unfocused «></vsm:VisualState>
...
</vsm:VisualStateGroup>
La classe VisualTransition permet de définir une transition entre un état désigné
par la propriété From et un autre état désigné par la propriété To. La durée de la
transition est définie par la propriété GeneratedDuration. La propriété Storyboard
permet de définir une animation pendant la transition. La transition et son
animation s’exécutent avant que l’état de destination soit atteint. Une transition se
définit au sein de la collection Transitions d’un groupe d’états VisualStateGroup. Ici
on décide d’animer la transition pour passer à l’état MouseOver et à l’état Pressed
pour une durée de 0.2 seconde (GeneratedDuration).
376 Développez des applications Internet avec Silverlight 5
<Style x:Key= «style_bouton» TargetType= «Button»>
...
<Setter Property= «Template»>
<Setter.Value>
<ControlTemplate TargetType= «Button»>
<Grid>
<!-- gérer les états des boutons -->
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name= «CommonStates»>
<vsm:VisualStateGroup.Transitions>
<vsm:VisualTransition GeneratedDuration= «0:0:0.2»
To= «MouseOver» />
<vsm:VisualTransition GeneratedDuration= «0:0:0.2»
To= «Pressed» />
</vsm:VisualStateGroup.Transitions>
<vsm:VisualState x:Name= «Normal»>...</vsm:VisualState>
<vsm:VisualState x:Name= «MouseOver»>...</vsm:VisualState>
<vsm:VisualState x:Name= «Pressed»>...</vsm:VisualState>
<vsm:VisualState x:Name= «Disabled»>...</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name= «FocusStates»>
<vsm:VisualState x:Name= «Focused»>...</vsm:VisualState>
<vsm:VisualState x:Name= «Unfocused»>...</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<!-- contenu des boutons -->
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Copyright 2012 Patrice REY


C H A P I T R E 13 DANS CE CHAPITRE
• Le contrôle Label
• Le contrôle
La gestion DescriptionViewer
• Les attributs
des données DisplayAttribute,
RequiredAttribute,
StringLengthAttribute
• L’interface
INotifyPropertyChanged
• ValidationContext,
ValidationAttribute, Validator,
ValidationResult
Le mécanisme de la liaison de données
• Le contrôle
ValidationSummary
(databinding) permet d’associer des données • Le contrôle DataGrid
aux éléments visuels de l’interface utilisateur • DataGridRow et
DataGridColumn
pour automatiser l’affichage et la saisie. Les • DataGridTextColumn,
éléments visuels pouvant eux-mêmes être source DataGridCheckBoxColumn,
de données, le databinding permet également DataGridTemplateColumn
• DataTemplate et
d’associer des éléments visuels pour augmenter la IValueConverter
richesse des interfaces utilisateur et la productivité • PagedCollectionView
de développement. • Le contrôle DataPager
• Les contrôles TreeView et
Les contrôles, qui gèrent et visualisent des données, TreeViewItem
sont plus ou moins complexes. Dans ce chapitre,
nous allons voir comment gérer et visualiser des
données par l’intermédiaire de contrôles plus ou
moins spécialisés.
Les contrôles plus évolués comme le DataGrid et le
TreeView seront vus explicitement pour l’affichage,
la visualisation et l’édition des données.
Il sera aussi abordé la façon d’alimenter les contrôles
par un jeu de données dans un contexte précis. Le
format des données peut différer entre les valeurs
stockées et leur vue affichée. La liaison de données,
qui gère également un système de conversion, sera
vue en détail au travers de divers exemples mettant
en scène différents contrôles spécialisés.
378 Développez des applications Internet avec Silverlight 5
Le mécanisme de la liaison de données (databinding) permet d’associer des
données aux éléments visuels de l’interface utilisateur pour automatiser l’affichage
et la saisie. Les éléments visuels pouvant eux-mêmes être source de données, le
databinding permet également d’associer des éléments visuels pour augmenter la
richesse des interfaces utilisateur et la productivité de développement.

1 - Visualiser des données

L’UserControl VisualiserDonnees.xaml (dans la solution ControleDesDonnees.sln


dans le dossier chapitre13) illustre la façon de relier les données à des éléments
visuels de l’interface utilisateur (figure 13.1).
FIGURE 13.1

1.1 - La propriété DataContext

La classe FrameworkElement expose une propriété DataContext qui définit le


contexte de données pour un objet FrameworkElement lorsqu’il participe à la
Copyright 2012 Patrice REY

liaison de données.
Il est possible de définir un objet source implicite pour tout le contexte d’un élément
conteneur donné, via la propriété DataContext de cet élément. Cette propriété
est héritée automatiquement par un élément cible situé dans l’arbre visuel, et
constitue ce qu’on appelle le contexte de binding. Elle permet de spécifier en une
seule instruction l’objet source des différents objets Binding dans la descendance
CHAPITRE 13 □ La gestion des données 379

du conteneur qui en bénéficient implicitement. Dans les expressions de binding


utilisant ce contexte, il n’est alors plus nécessaire de spécifier la source devenue
implicite. Seules les autres propriétés du binding, telles que Path, doivent être
spécifiées.
Nous instancions ici un objet un_produit, de type Produit, et nous l’affectons à
la propriété DataContext du conteneur Canvas x_cnv_root (pour l’utiliser dans
l’affichage des données contenues dans un_produit).
//evenement loaded
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
string description = «un vélo tout terrain, avec des roues de taille 26
pouces, un dérailleur 25 vitesses»;
Produit un_produit = new Produit(«velo100121», «vélo tout terrain V2625»,
542.36, description);
x_cnv_root.DataContext = un_produit;
}
Supposons que nous ayons un catalogue qui propose des articles composés par
des vélos, nous déclarons une classe Produit avec les propriétés suivantes:
• ModeleNumero qui définit le numéro de l’article sous forme d’un identifiant
unique (de type string).
• ModeleNom qui définit le nom de l’article (de type string).
• ModelePrix qui définit le prix de l’article (de type double).
• ModeleDescription qui définit la description de l’article (de type string).
Le constructeur par défaut est surchargé de façon à instancier un objet Produit
avec un numéro, un nom, un prix et une description (par l’utilisation des champs
m_modele_numero, m_modele_nom, m_modele_prix et m_modele_description).
public class Produit {
//champs
private string m_modele_numero;
private string m_modele_nom;
private double m_modele_prix;
private string m_modele_description;
//propriete
public string ModeleNumero {
get { return m_modele_numero; }
set { m_modele_numero = value;}
}
//propriete
public string ModeleNom {
get { return m_modele_nom; }
set { m_modele_nom = value;}
}
//propriete
380 Développez des applications Internet avec Silverlight 5
public double ModelePrix {
get { return m_modele_prix; }
set { m_modele_prix = value;}
}
//propriete
public string ModeleDescription {
get { return m_modele_description; }
set { m_modele_description = value;}
}
//constructeur
public Produit(string un_numero, string un_nom, double un_prix,
string une_description) {
m_modele_numero = un_numero;
m_modele_nom = un_nom;
m_modele_prix = un_prix;
m_modele_description = une_description;
}
}

1.2 - Le contrôle Label

Comme l’illustre la figure 13.2, à la conception, on positionne un contrôle Label


x_label_numero qui affiche le texte «Label» (propriété Content par défaut), et en
application, le contrôle Label voit sa propriété Content modifiée, en affichant le
texte «Numéro du modèle :» par une liaison de données.
A la conception, le TextBox x_textbox_numero est vide (propriété Text = «»), et
en application, le contrôle TextBox affiche le texte «velo100121» qui représente
la propriété ModeleNumero de l’objet Produit un_produit (objet fourni par la
propriété DataContext du conteneur x_cnv_root).
FIGURE 13.2

Copyright 2012 Patrice REY

Label TextBox
x_label_numero x_textbox_numero
CHAPITRE 13 □ La gestion des données 381

A noter que pour ajouter un contrôle Label, dans le fichier XAML, il faut ajouter,
dans le dossier Références du projet, une référence à l’assembly System.Windows.
Controls.Data.Input, puis dans le fichier XAML, il faut référencer cet espace de
noms en ajoutant l’attribut suivant aux attributs de l’UserControl : xmlns:data =
«clr-namespace:System.Windows.Controls; assembly=System.Windows.Controls.
Data.Input».
Un contrôle Label pourra être alors ajouté en XAML par <data.Label>. Comme la
propriété DataContext de x_cnv_root possède un objet Produit instancié, on peut
lier par databinding la propriété Text du TextBox x_textbox_numero pour que le
contrôle affiche la propriété ModeleNumero de un_produit. Pour cela on affecte
à la propriété Text une liaison de données Binding pour récupérer la propriété
ModelNumero.
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»
HorizontalAlignment= «Center» VerticalAlignment= «Center»
Width= «600» Height= «630»>
<!-- numero produit -->
...
<TextBox Name= «x_textbox_numero» Width= «207» Canvas.Left= «180»
Canvas.Top= «42» Text= «{Binding ModeleNumero, Mode=TwoWay}»
FontFamily= «Verdana» FontSize= «14»></TextBox>
...
</Canvas>
La classe Label hérite de ContentControl. Un contrôle Label affiche une légende, un
indicateur de champ obligatoire et un indicateur d’erreur de validation à l’attention
de l’utilisateur. Il est généralement utilisé avec un contrôle d’entrée (un contrôle
TextBox par exemple).
Pour associer un Label à un autre contrôle, vous pouvez définir la propriété Target
du Label. La propriété Target est une référence à un UIElement. Vous pouvez
définir la propriété Target dans le code ou via une liaison ElementName en XAML.
Lorsque la propriété Target est définie, un Label inspecte chaque propriété de
dépendance sur le contrôle ciblé par Target à la recherche d’un Binding. Chaque fois
qu’il trouve un Binding, il inspecte la propriété Source de ce Binding à la recherche
de métadonnées. Les métadonnées permettent de définir le texte de légende,
de spécifier si le champ est obligatoire et d’indiquer les règles de validation. Si le
contrôle ciblé par Target possède plusieurs Binding, vous pouvez spécifier celui à
partir duquel récupérer des métadonnées en définissant la propriété PropertyPath.
Le Label x_label_numero a sa propriété Target qui est liée par Binding avec le
contrôle TextBox x_textbox_numero. La figure 13.3 visualise le résultat obtenu. On
voit que la propriété Content du Label contient la chaîne «ModeleNumero».
382 Développez des applications Internet avec Silverlight 5
<Canvas x:Name= «x_cnv_root» Background= «WhiteSmoke»
HorizontalAlignment= «Center» VerticalAlignment= «Center»
Width= «600» Height= «630»>
<!-- numero produit -->
<data:Label Canvas.Left= «12» Canvas.Top= «42» Width= «161»
FontFamily= «Verdana» FontSize= «14»
Target= «{Binding ElementName=x_textbox_numero}» Height= «26»
Name= «x_label_numero»></data:Label>
...
</Canvas>

FIGURE 13.3

Label TextBox
x_label_numero x_textbox_numero

1.3 - Les classes d’attributs

L’espace de noms System.ComponentModel.DataAnnotations fournit des classes


d’attributs utilisées pour définir les métadonnées de classes d’entité. Parmi
ces nombreuses classes d’attributs, nous allons voir l’utilisation des classes
DisplayAttribute, RequiredAttribute et StringLengthAttribute (figure 13.4).
Les attributs peuvent être utilisés pour associer des informations aux entités
métier. Ainsi l’attribut Display, de la classe DisplayAttribute, permet de définir des
spécifications automatiquement prises en compte par les contrôles évolués tels
que le Label, le DataGrid ou le DataForm, au moyen des propriétés suivantes:
• Name qui définit le libellé à afficher à côté du champ d’édition de la donnée.
Copyright 2012 Patrice REY

• Description qui définit l’information d’aide.


• Order qui définit le numéro d’ordre du champ dans le formulaire.
Par exemple, la propriété ModeleNumero de la classe Produit est dotée d’un
attribut Display avec une propriété Name et une propriété Description définies.
public class Produit {
...
CHAPITRE 13 □ La gestion des données 383

//propriete
[Display(Name = «Numéro du modèle :»,
Description = «une chaine alphanumérique, de 10 caractères, utilisée
pour identifier le produit»)]
public string ModeleNumero {
get { return m_modele_numero; }
set { m_modele_numero = value; }
}
...}
FIGURE 13.4

Vous pouvez utiliser la propriété Content pour définir la légende d’un Label. Si
la propriété associée au Label possède un DisplayAttribute et que la propriété
Name n’est pas une référence null ni une chaîne vide, la valeur de la propriété
384 Développez des applications Internet avec Silverlight 5
DisplayAttribute.Name sera alors affectée à Content. Dans notre cas, la propriété
Content du Label x_label_numero sera affectée par la chaîne «Numéro du modèle
:» (figure 13.5).
FIGURE 13.5

Label TextBox
x_label_numero x_textbox_numero

Par défaut, le contrôle Label indique un champ obligatoire en mettant le texte


de légende en gras. Vous pouvez mettre le Label dans l’état visuel Required en
affectant true à la propriété IsRequired ou en plaçant un RequiredAttribute sur la
propriété à laquelle le Label est associé.
La classe RequiredAttribute spécifie qu’une valeur doit être fournie pour une
propriété. Vous appliquez l’attribut RequiredAttribute à une propriété pour spécifier
que celle-ci doit contenir une valeur. Une exception de validation est levée si la
propriété est une référence null, est une chaîne vide («») ou ne contient que des
espaces blancs.
En ajoutant l’attribut Required pour la propriété ModeleNumero de la classe
Produit, le contenu texte de la propriété Content du Label est en gras (figure 13.6).
public class Produit {
...
//propriete
[Display(Name = «Numéro du modèle :»,
Description = «une chaine alphanumérique, de 10 caractères, utilisée
pour identifier le produit»)]
[Required()]
Copyright 2012 Patrice REY

public string ModeleNumero {


get { return m_modele_numero; }
set { m_modele_numero = value; }
}
...}
<!-- numero produit -->
<data:Label Canvas.Left= «12» Canvas.Top= «42» Width= «161»
FontFamily= «Verdana» FontSize= «14»
CHAPITRE 13 □ La gestion des données 385

Target= «{Binding ElementName=x_textbox_numero}» Height= «26»


Name= «x_label_numero»></data:Label>
<TextBox Name= «x_textbox_numero» Width= «207» Canvas.Left= «180»
Canvas.Top= «42» Text= «{Binding ModeleNumero, Mode=TwoWay}»
FontFamily= «Verdana» FontSize= «14»></TextBox>
FIGURE 13.6

Label TextBox
x_label_numero x_textbox_numero

La classe StringLengthAttribute spécifie les nombres minimal et maximal


de caractères autorisés pour un membre d’entité. Vous appliquez l’attribut
StringLengthAttribute à une propriété lorsque vous devez spécifier le nombre de
caractères autorisés pour la propriété. Si la valeur de la propriété est une référence
null, la valeur ne fait pas échouer la validation de l’attribut StringLengthAttribute.
Pour vérifier que la valeur n’est pas une référence null, utilisez l’attribut
RequiredAttribute.
Pour identifier un numéro de modèle sur 10 caractères, on ajoute l’attribut
StringLength avec une valeur de 10.
public class Produit {
...
//propriete
[Display(Name = «Numéro du modèle :»,
Description = «une chaine alphanumérique, de 10 caractères, utilisée
pour identifier le produit»)]
[Required()]
[StringLength(10)]
public string ModeleNumero {
get { return m_modele_numero; }
set { m_modele_numero = value; }
}
...}
On procède de la même manière pour les autres champs de la fiche produit.
La propriété ModeleNom, de type string, aura les attributs Display, Required et
StringLength (avec un nombre de caractères compris entre 5 et 30). La figure 13.7
386 Développez des applications Internet avec Silverlight 5
illustre le résultat obtenu.
//propriete
[Display(Name = «Nom du modèle :»,
Description = «une chaine alphanumérique, de 5 à 30 caractères, représentant
le nom du produit»)]
[Required()]
[StringLength(30, MinimumLength = 5)]
public string ModeleNom {
get { return m_modele_nom; }
set { m_modele_nom = value; }
}
<!-- nom produit -->
<data:Label Canvas.Left= «12» Canvas.Top= «82» Width= «161»
FontFamily= «Verdana» FontSize= «14»
Target= «{Binding ElementName=x_textbox_nom}» Height= «26»
Name= «x_label_nom»></data:Label>
<TextBox Name= «x_textbox_nom» Width= «207» Canvas.Left= «180»
Canvas.Top= «82»
Text= «{Binding ModeleNom, Mode=TwoWay}»
FontFamily= «Verdana» FontSize= «14»></TextBox>
FIGURE 13.7

Label TextBox
x_label_nom x_textbox_nom
La propriété ModelePrix, de type double, aura les attributs Display et Required. La
figure 13.8 illustre le résultat obtenu.
//propriete
[Display(Name = «Prix du modèle :»,
Description = «le prix du modele «)]
[Required()]
public double ModelePrix {
get { return m_modele_prix; }
set { m_modele_prix = value; }
}
Copyright 2012 Patrice REY

<!-- prix produit -->


<data:Label Canvas.Left= «12» Canvas.Top= «122» Width= «161»
FontFamily= «Verdana» FontSize= «14»
Target= «{Binding ElementName=x_textbox_prix}» Height= «26»
Name= «x_label_prix»></data:Label>
<TextBox Name= «x_textbox_prix» Width= «82» Canvas.Left= «180»
Canvas.Top= «122»
Text= «{Binding Mode=TwoWay, Path=ModelePrix}»
FontFamily= «Verdana» FontSize= «14»></TextBox>
CHAPITRE 13 □ La gestion des données 387

FIGURE 13.8

Label TextBox
x_label_prix x_textbox_prix

La propriété ModeleDescription, de type string, aura l’attribut Display. La figure


13.9 illustre le résultat obtenu.
//propriete
[Display(Name = «Description du modèle :»,
Description = «une chaine texte donnant le descriptif du produit dans
le catalogue»)]
public string ModeleDescription {
get { return m_modele_description; }
set { m_modele_description = value; }
}
<!-- descrition produit -->
<data:Label Canvas.Left= «12» Canvas.Top= «160» Width= «250»
FontFamily= «Verdana» FontSize= «14»
Target= «{Binding ElementName=x_textbox_description}» Height= «26»
Name= «x_label_description»></data:Label>
<TextBox Name= «x_textbox_description» Width= «576» Canvas.Left= «12»
Canvas.Top= «192» Text= «{Binding ModeleDescription, Mode=TwoWay}»
FontFamily= «Verdana» FontSize= «14» Height= «92»
TextWrapping= «Wrap»></TextBox>
FIGURE 13.9

Label TextBox
x_label_description x_textbox_description

1.4 - Le contrôle DescriptionViewer

La classe DescriptionViewer affiche une description et effectue le suivi de l’état


d’erreur d’un contrôle associé. Un contrôle DescriptionViewer affiche un glyphe
d’informations, ainsi qu’une description textuelle dans une info-bulle lorsque le
pointeur de la souris se trouve sur le glyphe. Il suit également l’état des erreurs
388 Développez des applications Internet avec Silverlight 5
de validation de sorte que vous puissiez implémenter un affichage des erreurs
personnalisé. Il peut être utilisé pour afficher une description ou être associé à un
autre contrôle.
La figure 13.10 illustre le contrôle DescriptionViewer x_description_numero au
repos et quand la souris le survole. L’info-bulle affiche le contenu de la propriété
Description de l’attribut Display de la propriété ModeleNumero de la classe Produit.
FIGURE 13.10

DescriptionViewer
x_description_numero

<!-- numero produit -->


<data:Label Canvas.Left= «12» Canvas.Top= «42» Width= «161»
FontFamily= «Verdana» FontSize= «14»
Target= «{Binding ElementName=x_textbox_numero}» Height= «26»
Name= «x_label_numero»></data:Label>
<TextBox Name= «x_textbox_numero» Width= «207» Canvas.Left= «180»
Canvas.Top= «42» Text= «{Binding ModeleNumero, Mode=TwoWay}»
FontFamily= «Verdana» FontSize= «14»></TextBox>
<data:DescriptionViewer Canvas.Left= «392» Canvas.Top= «43» Height= «26»
Width= «19» Target= «{Binding ElementName=x_textbox_numero}»
Name= «x_description_numero» Cursor= «Hand»></data:DescriptionViewer>
La figure 13.11 illustre le contrôle DescriptionViewer x_description_nom au
repos et quand la souris le survole. L’info-bulle affiche le contenu de la propriété
Description de l’attribut Display de la propriété ModeleNom de la classe Produit.
FIGURE 13.11

DescriptionViewer
Copyright 2012 Patrice REY

x_description_nom

<!-- nom produit -->


<data:Label Canvas.Left= «12» Canvas.Top= «82» Width= «161»
FontFamily= «Verdana» FontSize= «14»
CHAPITRE 13 □ La gestion des données 389

Target= «{Binding ElementName=x_textbox_nom}» Height= «26»


Name= «x_label_nom»></data:Label>
<TextBox Name= «x_textbox_nom» Width= «207» Canvas.Left= «180»
Canvas.Top= «82»
Text= «{Binding ModeleNom, Mode=TwoWay}»
FontFamily= «Verdana» FontSize= «14»></TextBox>
<data:DescriptionViewer Canvas.Left= «392» Canvas.Top= «83» Height= «26»
Width= «19» Cursor= «Hand»
Target= «{Binding ElementName=x_textbox_nom}»
Name= «x_description_nom»></data:DescriptionViewer>
La figure 13.12 illustre le contrôle DescriptionViewer x_description_prix au
repos et quand la souris le survole. L’info-bulle affiche le contenu de la propriété
Description de l’attribut Display de la propriété ModelePrix de la classe Produit.
FIGURE 13.12

DescriptionViewer
x_description_prix

<!-- prix produit -->


<data:Label Canvas.Left= «12» Canvas.Top= «122» Width= «161»
FontFamily= «Verdana» FontSize= «14»
Target= «{Binding ElementName=x_textbox_prix}» Height= «26»
Name= «x_label_prix»></data:Label>
<TextBox Name= «x_textbox_prix» Width= «82» Canvas.Left= «180»
Canvas.Top= «122»
Text= «{Binding Mode=TwoWay, Path=ModelePrix}»
FontFamily= «Verdana» FontSize= «14»></TextBox>
<data:DescriptionViewer Canvas.Left= «265» Canvas.Top= «123» Height= «26»
Width= «19» Cursor= «Hand»
Target= «{Binding ElementName=x_textbox_prix}»
Name= «x_description_prix»></data:DescriptionViewer>
La figure 13.13 illustre le contrôle DescriptionViewer x_description_description au
repos et quand la souris le survole. L’info-bulle affiche le contenu de la propriété
Description de l’attribut Display de la propriété ModeleDescription de la classe
Produit.
<!-- descrition produit -->
<data:Label Canvas.Left= «12» Canvas.Top= «160» Width= «250»
FontFamily= «Verdana» FontSize= «14»
Target= «{Binding ElementName=x_textbox_description}» Height= «26»
Name= «x_label_description»></data:Label>
<TextBox Name= «x_textbox_description» Width= «576» Canvas.Left= «12»
390 Développez des applications Internet avec Silverlight 5
Canvas.Top= «192» Text= «{Binding ModeleDescription, Mode=TwoWay}»
FontFamily= «Verdana» FontSize= «14» Height= «92»
TextWrapping= «Wrap»></TextBox>
<data:DescriptionViewer Canvas.Left= «567» Canvas.Top= «160» Height= «26»
Width= «19» Cursor= «Hand»
Target= «{Binding ElementName=x_textbox_description}»
Name= «x_description_description»></data:DescriptionViewer>
FIGURE 13.13

DescriptionViewer
x_description_des
cription

La propriété GlyphTemplate permet de fixer l’icone à afficher pour visualiser un


DescriptionViewer. Par exemple la figure 13.14 affiche une image personnalisée
(intitulée infos.jpg dans le dossier contenu) pour visualiser l’objet DescriptionViewer
de la propriété ModeleDescription de l’objet Produit.
<data:DescriptionViewer Canvas.Left= «543» Canvas.Top= «210» Height= «41»
Width= «40» Cursor= «Hand»
Target= «{Binding ElementName=x_textbox_description}»
Name= «x_description_description»>
<data:DescriptionViewer.GlyphTemplate>
<ControlTemplate>
<Image Source= «contenu/infos.jpg» Stretch= «Fill»></Image>
</ControlTemplate>
</data:DescriptionViewer.GlyphTemplate>
</data:DescriptionViewer>
FIGURE 13.14

Copyright 2012 Patrice REY

propriété
GlyphTemplate de
DescriptionViewer
CHAPITRE 13 □ La gestion des données 391

Le DescriptionViewer possède des groupes d’états avec leurs états (figure 13.15).
Il est donc possible de modifier les modèles comme cela a été déjà vu au chapitre
précédent. Les groupes d’états sont:
• CommonStates avec les états Normal et Disabled.
• DescriptionStates avec les états NoDescription et HasDescription.
• ValidationStates avec les états InvalidFocused, InvalidUnfocused, ValidFocused
et ValidUnfocused.
FIGURE 13.15

les groupes d’états et leurs états

2 - Notification et validation

L’UserControl ValiderDonnees.xaml (dans la solution ControleDesDonnees.sln


dans le dossier chapitre13) illustre la façon de notifier les erreurs dans la saisie des
champs de données, et la façon de valider une fiche correctement remplie.
L’objet Binding expose une propriété Mode qui détermine en fonction d’une valeur
énumérée le sens de parcours de la donnée liée:
• OneWay qui représente le databinding en lecture seule; l’élément cible reflète
la valeur de la donnée source; ce mode est utilisé dans les scénarios d’affichage
sans saisie.
• TwoWay qui représente le databinding en lecture écriture; l’élément cible
reflète la valeur de la donnée source mais peut la mettre à jour; c’est le mode
utilisé dans les scénarios avec saisie.
• OneTime qui représente un mode proche de celui de OneWay; l’élément cible
reflète la valeur initiale de la donnée source, mais les variations éventuelles de
celle-ci ne sont pas répercutées.
Pour supporter les modes OneWay et TwoWay, un objet source doit intégrer un
mécanisme lui permettant de notifier à l’élément cible un changement de valeur
392 Développez des applications Internet avec Silverlight 5
de la donnée source. Le mécanisme préconisé consiste à implémenter l’interface
INotifyPropertyChanged. La définition de l’interface INotifyPropertyChanged est:
namespace System.ComponentModel {
//Notifie les clients qu’une valeur de propriété a été modifiée.
public interface INotifyPropertyChanged {
//Se produit lorsqu’une valeur de propriété est modifiée.
event PropertyChangedEventHandler PropertyChanged;
}
}
L’interface INotifyPropertyChanged expose l’événement PropertyChanged qui
propage un argument de type PropertyChangedEventArgs. Celui-ci indique dans
PropertyName le nom de la propriété qui vient de changer de valeur. L’objet source
doit émettre l’événement PropertyChanged pour chaque modification de valeur
d’une propriété publique susceptible d’être utilisée pour le databinding.
Comme le montre la figure 13.16, le fait d’implémenter l’interface
INotifyPropertyChanged pour la classe Produit, déclenche un sous-menu de
l’intellisense de Visual Studio pour l’implémentation.
FIGURE 13.16

On déclare une méthode publique OnPropertyChanged pour la prise en charge


d’une modification de propriété par l’intermédiaire du délégué PropertyChanged.
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e) {
if (PropertyChanged != null)
PropertyChanged(this, e);
}
Dans chaque propriété, dans la partie de l’accesseur set, on appelle la méthode
OnPropertyChanged en lui passant en argument le nom de la propriété modifiée.
Copyright 2012 Patrice REY

En cas de modification d’une propriété, l’événement de la modification est diffusé.


//propriete numero
public string ModeleNumero {
...
set {
m_modele_numero = value;
OnPropertyChanged(new PropertyChangedEventArgs(«ModeleNumero»));
}
CHAPITRE 13 □ La gestion des données 393

}
public string ModeleNom {
...
set {
m_modele_nom = value;
OnPropertyChanged(new PropertyChangedEventArgs(«ModeleNom»));
}
}
public double ModelePrix {
...
set {
m_modele_prix = value;
OnPropertyChanged(new PropertyChangedEventArgs(«ModelePrix»));
}
}
public string ModeleDescription {
...
set {
m_modele_description = value;
OnPropertyChanged(new PropertyChangedEventArgs(«ModeleDescription»));
}
}
Dans le cas de la validation du stockage d’une donnée sur un binding en
mode TwoWay, le mécanisme de validation consiste à émettre l’événement
BindingValidationError sur l’élément cible quand une exception est déclenchée.
L’objet Binding doit avoir sa propriété ValidatesOnExceptions explicitement fixée à
true, et sa propriété NotifyOnValidationError explicitement fixée à true.
<!-- numero produit ****************************** -->
<TextBox Name= «x_textbox_numero»
Text= «{Binding ModeleNumero, Mode=TwoWay,
ValidatesOnExceptions=True, NotifyOnValidationError=True}»
...</TextBox>
<!-- nom produit ****************************** -->
<TextBox Name= «x_textbox_nom»
Text= «{Binding ModeleNom, Mode=TwoWay,
ValidatesOnExceptions=True, NotifyOnValidationError=True}»
...</TextBox>
<!-- prix produit ****************************** -->
<TextBox Name= «x_textbox_prix»
Text= «{Binding ModelePrix, Mode=TwoWay,
ValidatesOnExceptions=True, NotifyOnValidationError=True}»
...</TextBox>
La classe ValidationContext (dans l’espace de noms System.ComponentModel.
DataAnnotations) fournit des informations sur un type ou un membre à valider.
Elle décrit le type ou le membre sur lequel la validation est exécutée. Elle permet
394 Développez des applications Internet avec Silverlight 5
également aux routines de validation de faire référence à des services disponibles
via la méthode GetService de l’interface IServiceProvider.
Le constructeur ValidationContext initialise une nouvelle instance de la classe
ValidationContext avec l’objet spécifié à valider, un fournisseur de services qui
active des méthodes de validation pour accéder aux services externes et une
collection de valeurs en rapport avec la validation. La propriété MemberName
définit le nom de programmation du membre à valider.
Par exemple, pour la propriété ModeleNumero, on instancie un objet context de
type ValidationContext, qui reçoit en argument this (objet à valider). On fixe sa
propriété MemberName par la chaîne «ModeleNumero» (nom de la propriété à
valider).
//propriete numero du modele
[Display(Name = «Numéro du modèle :»,
Description = «une chaine alphanumérique, de 10 caractères, utilisée pour
identifier le produit»)]
[Required()]
[StringLength(10,MinimumLength=10)]
public string ModeleNumero {
get { return m_modele_numero; }
set {
ValidationContext context = new ValidationContext(this, null, null);
context.MemberName = «ModeleNumero»;
...
m_modele_numero = value;
OnPropertyChanged(new PropertyChangedEventArgs(«ModeleNumero»));
}
}
La classe Validator (dans l’espace de noms System.ComponentModel.
DataAnnotations) fournit des membres qui permettent de valider des objets et des
membres à l’aide de valeurs de l’attribut ValidationAttribute associé. La méthode
statique Validator.ValidateProperty détermine si la valeur de propriété spécifiée
est valide, et lève une exception de type ValidationException si la propriété n’est
pas valide. Ici on utilise cette méthode statique pour valider la valeur value pour
l’objet context.
Copyright 2012 Patrice REY

//propriete numero du modele


[Display(Name = «Numéro du modèle :»,
Description = «une chaine alphanumérique, de 10 caractères, utilisée pour
identifier le produit»)]
[Required()]
[StringLength(10,MinimumLength=10)]
public string ModeleNumero {
get { return m_modele_numero; }
CHAPITRE 13 □ La gestion des données 395

set {
ValidationContext context = new ValidationContext(this, null, null);
context.MemberName = «ModeleNumero»;
Validator.ValidateProperty(value, context);
m_modele_numero = value;
OnPropertyChanged(new PropertyChangedEventArgs(«ModeleNumero»));
}
}
La propriété ModeleNumero de Produit doit être une chaîne contenant 10
caractères ([StringLength(10,MinimumLength=10)]). Si on inscrit une chaîne
qui contient plus de 10 caractères ou moins de 10 caractères, la validation lève
une exception en affichant une erreur. La figure 13.17 visualise ce qui se passe
(le champ qui n’est pas correct est entouré d’une bordure rouge avec un triangle
rouge dans le coin haut droit, qui affiche une info-bulle quand la souris le survole).
FIGURE 13.17

si le numéro du produit contient


plus de 10 caractères

si le numéro du produit contient


moins de 10 caractères

On effectue la même démarche pour la propriété ModeleNom de Produit.


//propriete nom du modele
[Display(Name = «Nom du modèle :»,
396 Développez des applications Internet avec Silverlight 5
Description = «une chaine alphanumérique, de 5 à 30 caractères, représentant
le nom du produit»)]
[Required()]
[StringLength(30, MinimumLength = 5)]
public string ModeleNom {
get { return m_modele_nom; }
set {
ValidationContext context = new ValidationContext(this, null, null);
context.MemberName = «ModeleNom»;
Validator.ValidateProperty(value, context);
m_modele_nom = value;
OnPropertyChanged(new PropertyChangedEventArgs(«ModeleNom»));
}
}
En ce qui concerne le prix du produit (propriété ModelePrix de Produit), il faut
pouvoir lever une exception si le prix indiqué est négatif. La propriété ModelePrix
possède l’attribut Required (qui indique qu’un prix est obligatoire), mais il n’y a pas
de contrainte par l’attribut StringLength. La figure 13.18 illustre la visualisation de
la levée d’une exception. Pour effectuer une levée d’exception, il suffit d’indiquer
qu’une valeur value négative, lors de la modification de la propriété, lève une
exception de type ArgumentException, recevant en paramètre une chaîne
indiquant l’avertissement. La levée d’une exception s’effectue par l’instruction
throw.
FIGURE 13.18

si le prix du produit est négatif, on


lève une exception

//propriete prix du modele


Copyright 2012 Patrice REY

[Display(Name = «Prix du modèle :»,


Description = «le prix du modele «)]
[Required()]
public double ModelePrix {
get { return m_modele_prix; }
set {
if (value < 0) throw new ArgumentException(«le prix ne peut pas être
négatif»);
CHAPITRE 13 □ La gestion des données 397

ValidationContext context = new ValidationContext(this, null, null);


context.MemberName = «ModelePrix»;
m_modele_prix = value;
OnPropertyChanged(new PropertyChangedEventArgs(«ModelePrix»));
}
}
Le fait d’indiquer que l’on a inscrit un prix de vente négatif, nous permet de modifier
le champ mais ne permet pas de valider la fiche. Il faut pouvoir valider la fiche par
un système qui vérifie les champs et qui valide si tout est correct.
La classe RequiredAttribute, qui hérite de ValidationAttribute, possède une
propriété héritée CustomValidation. La propriété CustomValidation délègue la
validation à une méthode statique d’une classe donnée. Cet attribut est applicable
à une propriété ou à la globalité d’une entité. La méthode appelée par l’attribut
CustomValidation doit renvoyer:
• soit l’objet ValidationResult.Success en cas de réussite de la validation.
• soit un objet ValidationResult spécifiant dans sa propriété ErrorMessage le
message d’erreur (mettre null pour utiliser le message défini dans l’attribut
CustomValidation).
On déclare une classe ValidationProduit et on ajoute une méthode statique
ValiderLePrix, qui reçoit en paramètre une variable v_prix, de type double, et
une variable v_context, de type ValidationContext, et qui retourne un objet
ValidationResult. Si le prix est strictement supérieur à 0, la méthode retourne
le membre statique ValidationResult.Success, sinon elle retourne une nouvelle
instance de ValidationResult (qui reçoit en paramètre une chaîne indiquant
l’erreur).
public class ValidationProduit {
public static ValidationResult ValiderLePrix(double v_prix,
ValidationContext v_context) {
if (v_prix <= 0) {
return new ValidationResult(«le prix du produit doit être strictement
supérieur à 0»);
} else {
return ValidationResult.Success;
}
}
...
}
Pour la propriété ModelePrix, on ajoute un attribut CustomValidation, qui est du
type ValidationProduit et qui fait appel à la méthode statique ValiderLePrix.
//propriete prix du modele
398 Développez des applications Internet avec Silverlight 5
[Display(Name = «Prix du modèle :»,
Description = «le prix du modele «)]
[Required()]
[CustomValidation(typeof(ValidationProduit), «ValiderLePrix»)]
public double ModelePrix {
...
}
De façon à afficher visuellement toutes les erreurs de validation, il faut ajouter
un contrôle ValidationSummary (qui hérité de System.Windows.Controls.Control).
La classe ValidationSummary affiche un résumé des erreurs de validation sur un
formulaire.
Le contrôle ValidationSummary affiche une liste consolidée d’erreurs de
validation pour un conteneur donné. Par défaut, il affichera les erreurs au
niveau à la fois objet et propriété. La collection Errors contient toutes les erreurs
ajoutées au ValidationSummary. Une erreur individuelle est représentée
par un ValidationSummaryItem. Vous pouvez spécifier le type d’erreurs
affichées en affectant à la propriété Filter l’une des valeurs d’énumération
ValidationSummaryFilters. Par exemple, si les erreurs au niveau propriété sont
déjà affichées par des contrôles individuels sur le formulaire, vous pouvez définir
le ValidationSummary de manière à n’afficher que les erreurs au niveau objet. La
collection DisplayedErrors contient toutes les erreurs qui correspondent à l’affichage
Filter. Le ValidationSummary reçoit les événements BindingValidationError de son
conteneur parent. Cela lui permet d’afficher les erreurs de validation au niveau
propriété d’un formulaire.
Sur le Canvas x_cnv_root, nous ajoutons un contrôle x_validation_summary de
type ValidationSummary (<data:ValidationSummary>). La figure 13.19 montre
son affichage à la conception en XAML.
<!-- validation -->
<data:ValidationSummary Width=»576» Height=»90» Canvas.Top=»303» Canvas.
Left=»12» Name=»x_validation_summary»></data:ValidationSummary>

FIGURE 13.19
Copyright 2012 Patrice REY

ValidationSummary
x_validation_summary

La figure 13.20 illustre le résultat obtenu. Quand on inscrit un prix négatif, le


ValidationSummary centralise l’affichage de l’erreur.
CHAPITRE 13 □ La gestion des données 399

FIGURE 13.20

ValidationSummary
x_validation_summary

Il est possible de valider l’objet entier en ajoutant un attribut CustomValidation


à la classe, pour vérifier par exemple que la valeur de la propriété ModeleNom
est différente de la valeur de la propriété ModeleNumero. Pour effectuer
cette vérification, on ajoute la méthode statique ValiderLeProduit à la classe
ValidationProduit.
public class ValidationProduit {
...
public static ValidationResult ValiderLeProduit(Produit un_produit,
ValidationContext v_context) {
if (un_produit.ModeleNom == un_produit.ModeleNumero) {
return new ValidationResult(«le nom du produit doit être différent du
numéro du produit»);
} else {
return ValidationResult.Success;
}
}
}//end class
[CustomValidation(typeof(ValidationProduit), «ValiderLeProduit»)]
public class Produit : INotifyPropertyChanged {
...
}
400 Développez des applications Internet avec Silverlight 5

3 - Le contrôle DataGrid

La classe DataGrid, qui hérite de la classe Control (figure 13.21), affiche les
données dans une grille personnalisable. Le contrôle DataGrid offre un moyen
souple d’afficher une collection de données dans des lignes et des colonnes. Les
types de colonnes intégrés incluent une colonne de zone de texte, une colonne de
case à cocher et une colonne de modèle pour héberger le contenu personnalisé. Le
type de ligne intégré inclut une section de détails déroulante qui permet d’afficher
du contenu supplémentaire sous les valeurs de cellules.
Pour lier le contrôle DataGrid aux données, vous définissez la propriété ItemsSource
à une implémentation IEnumerable. Chaque ligne de la grille de données est liée à
un objet dans la source de données et chaque colonne de la grille de données est
liée à une propriété de l’objet de données.
FIGURE 13.21

3.1 - Afficher des données


Copyright 2012 Patrice REY

L’UserControl AfficherDataGrid.xaml (dans la solution ControleDesDonnees.sln


dans le dossier chapitre13) illustre la façon de visualiser un ensemble de données
de façon basique (figure 13.22). Les données visualisées sont des objets de la
classe ProduitVelo.
Pour ajouter un contrôle DataGrid, il faut ajouter une référence à l’assembly System.
Windows.Controls.Data dans le dossier Références dans l’explorateur des fichiers.
CHAPITRE 13 □ La gestion des données 401

Pour ajouter en XAML un DataGrid, il faut ajouter une référence xmlns à l’espace de
noms xmlns:data = «clr-namespace:System.Windows.Controls;assembly=System.
Windows.Controls.Data», que l’on nomme data.
FIGURE 13.22

3
402 Développez des applications Internet avec Silverlight 5
Un contrôle DataGrid sera instancié en XAML par <data:DataGrid>, et on le
nommera x_datagrid. La propriété AutoGenerateColumns définit une valeur
qui indique si les colonnes sont créées automatiquement lorsque la propriété
ItemsSource est définie.
<Grid x:Name= «x_grid_root» Background= «WhiteSmoke»
HorizontalAlignment= «Center» VerticalAlignment= «Top»>
<Grid.RowDefinitions>
<RowDefinition Height= «50» />
<RowDefinition Height= «*» MinHeight= «200» />
</Grid.RowDefinitions>
<!-- titre -->
<Rectangle Width= «600» Height= «36» RadiusX= «5» RadiusY= «5»
StrokeThickness= «2» Stroke= «Black» Grid.Row= «0»></Rectangle>
<TextBlock Width= «600» TextAlignment= «Center» FontFamily= «Comic Sans MS»
FontSize= «18» Grid.Row= «0» VerticalAlignment= «Center»>
Listing des vélos du catalogue</TextBlock>
<!-- contenu des donnees -->
<data:DataGrid Name= «x_datagrid» Grid.Row= «1»
AutoGenerateColumns= «True»></data:DataGrid>
</Grid>
La propriété ItemsSource définit une collection utilisée pour générer le contenu du
contrôle. Par exemple on instancie une liste d’objets ProduitVelo, et on l’affecte à
ItemsSource du DataGrid.
//evenement loaded
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
List<ProduitVelo> liste_velo = new List<ProduitVelo>();
ProduitVelo velo = null;
velo = new ProduitVelo(ProduitVelo.Categorie.de_ville,
«velo123256», «ligne classique AB02», 125.56,
«velo de ville, classique avec ligne épurée»,»contenu/velo_ville.jpg»,2);
liste_velo.Add(velo);
velo = new ProduitVelo(ProduitVelo.Categorie.de_ville,
«velo236458», «ligne classique VER022», 331.24,
«velo de ville, classique avec ligne épurée», «contenu/velo_ville.jpg»,3);
liste_velo.Add(velo);
...
x_datagrid.ItemsSource = liste_velo;
Copyright 2012 Patrice REY

}
Le repère n°1 de la figure 13.22 montre le DataGrid basique avec ses données
de type ProduitVelo. Les en-têtes des colonnes sont générés automatiquement
par défaut, et leurs intitulés correspondent au nom des propriétés des données à
visualiser. Le repère n°2 de la figure 13.22 montre que l’on peut élargir ou rétrécir
les colonnes (curseur avec une double flèche horizontale). Le repère n°3 de la
figure 13.22 montre qu’un clic sur un en-tête de colonne permet de trier par ordre
CHAPITRE 13 □ La gestion des données 403

décroissant puis par ordre croissant les données contenues dans la colonne. La
modification des données est possible. Plusieurs lignes peuvent être sélectionnées.
Le tri multicolonne est possible par appui simultanée sur la touche Shift.

3.2 - Les fonctionnalités

Les lignes et les colonnes d’un contrôle DataGrid (figure 13.23) sont représentées
respectivement au moyen d’objets DataGridRow et DataGridColumn.
FIGURE 13.23

La classe abstraite DataGridColumn (figure 13.23) hérite de DependencyObject


et décrit les propriétés d’une colonne. Trois types de colonnes héritent de
DataGridColumn:
• DataGridTextColumn qui permet l’affichage ou la saisie de données textuelles,
respectivement au moyen d’objet Textblock ou d’objet TextBox.
404 Développez des applications Internet avec Silverlight 5
• DataGridCheckBoxColumn qui permet l’affichage et la saisie de données
booléennes.
• DataGridTemplateColumn qui représente une colonne personnalisable par
template.
La classe DataGridRow (figure 13.23) hérite de Control. Dans le cas d’une utilisation
basique du DataGrid, cette classe est rarement manipulée. Mais dans le cas d’une
utilisation personnalisée et approfondie, cette classe permet de programmer des
actions spécifiques pour une ligne donnée.
Un objet DataGridRow peut être obtenu au moyen de la méthode statique
DataGridRow.GetRowContainingElement, ou au moyen des événements relatifs
aux lignes du DataGrid tels que LoadingRow ou UnloadingRow. Seules les lignes
visibles sont effectivement chargées.
La méthode GetIndex d’un objet DataGridRow renvoie son numéro de ligne.
Le contenu d’une cellule donnée peut être retrouvé au moyen de la méthode
GetCellContent de la colonne. Cette méthode attend comme argument un
objet DataGridRow ou l’objet de la collection source des données. L’événement
CurrentCellChanged est émis au changement de cellule. La colonne courante peut
être lue dans la propriété CurrentColumn. L’élément courant peut être lu dans la
propriété CurrentItem.
Par défaut la saisie est possible, et permet de modifier effectivement les données
sous-jacentes. Le passage en mode édition se fait par un double clic ou par appui sur
la touche F2. L’événement BeginingEdit est émis lors du passage en mode édition,
et permet d’annuler ce passage par programmation. La propriété IsReadOnly
permet de basculer en mode lecture seule. La propriété RowBackground permet
de définir la couleur de fond des lignes au moyen d’un pinceau. La propriété
AlternatingRowBackground permet de définir une alternance de couleur. Les
traits peuvent être cachés au moyen de la propriété GridLinesVisibility. La
propriété HeadersVisibility conditionne la visibilité des en-têtes de colonne
et de ligne au moyen d’une valeur énumérée. Un en-tête peut être défini pour
une ligne spécifique au moyen des propriétés Header et HeaderStyle d’un objet
Copyright 2012 Patrice REY

DataGridRow. Une même taille peut être donnée aux colonnes au moyen de la
propriété ColumnWidth. Une même hauteur peut être donnée aux lignes au moyen
de la propriété RowHeight. Le dimensionnement des cellules est automatique par
défaut, et fonctionne selon le même principe que les cellules d’un conteneur Grid
comme déjà vu (valeurs Auto et *).
La propriété booléenne CanUserResizeColumns indique si l’utilisateur peut
redimensionner les colonnes par glisser-déposer. La propriété booléenne
CHAPITRE 13 □ La gestion des données 405

CanUserReorderColumns indique si l’utilisateur peut déplacer les colonnes.


En cas de déplacement de colonne, les événements ColumnReordering,
ColumnDisplayIndexChanged et ColumnReordered sont successivement émis.
ColumnReordering permet d’annuler l’opération par programme. La propriété
FrozenColumnCount permet de geler des colonnes à gauche.
Par défaut, l’utilisateur peut trier les données affichées si la collection sous-jacente
implémente IList, ce qui est le cas pour la collection ObservableCollection<T>. Le
tri se fait par un clic sur un en-tête de colonne. Le tri multicolonne est possible par
appui simultané sur la touche Shift. La propriété CanUserSortColumns permet de
désactiver le tri.
Si le DataGrid supporte par défaut la multisélection, elle peut être inhibée par
la propriété SelectionMode. Le visuel des différentes parties du DataGrid est
configurable au moyen de styles et de templates. Cependant la personnalisation
du DataGrid passe par la définition manuelle des différentes colonnes.

3.3 - La personnalisation des colonnes

Pour pouvoir personnaliser des colonnes, il faut au préalable assigner false à la


propriété AutoGenerateColumns. Les définitions des colonnes doivent ensuite
être ajoutées manuellement à la propriété Columns. Chaque colonne dispose des
propriétés suivantes:
• les propriétés Header et HeaderStyle déterminent le contenu et l’apparence
de l’en-tête.
• la propriété IsReadOnly bascule en mode lecture seule.
• les propriétés ElementStyle et EditingElementStyle définissent le style du
contrôle utilisé respectivement pour l’affichage et la saisie.
• la propriété Visibility permet de masquer la colonne.
• la propriété Width détermine la taille de la colonne de façon prioritaire par
rapport aux dimensions définies au niveau du DataGrid.
• la propriété MinWidth spécifie une taille minimale en cas de
redimensionnement.
• la propriété CanUserResize indique si l’utilisateur peut redimensionner la
colonne.
• la propriété CanUserSort indique si l’utilisateur peut trier la colonne.
• la propriété CanUserReorder indique si l’utilisateur peut déplacer la colonne.
• la propriété DisplayIndex permet de modifier le numéro d’ordre de la colonne;
l’événement ColumnDisplayIndexChanged du DataGrid est alors émis.
406 Développez des applications Internet avec Silverlight 5
• les propriétés FontFamily, FontSize et les autres relatives à la fonte configurent
la police des colonnes DataGridTextColumn.
• la propriété IsThreeState permet de gérer une valeur nulle dans les colonnes
DataGridCheckBoxColumn.
• la propriété Binding spécifie la liaison de données sur l’objet source des colonnes
DataGridTextColumn et DataGridCheckBoxColumn; le mode TwoWay est
utilisé par défaut.
Les colonnes DataGridTemplateColumn peuvent être personnalisées au moyen
d’un DataTemplate pour l’affichage, défini dans la propriété CellTemplate, et d’un
DataTemplate pour la saisie, défini dans la propriété CellEditingTemplate.
L’événement PreparingCellForEdit du DataGrid est émis lors du passage en mode
édition d’une colonne DataGridTemplateColumn.
L’UserControl ColonnePersonnalisee.xaml (dans la solution ControleDesDonnees.
sln dans le dossier chapitre13) illustre la façon de visualiser un ensemble de
données avec des colonnes personnalisées (figure 13.24). Les données visualisées
sont des objets de la classe ProduitVelo.
Sur le Grid x_grid_root, on positionne un DataGrid x_datagrid en lecture seule
(valeur true affectée à IsReadOnly), en inhibant explicitement la génération
automatique des colonnes (valeur false à AutoGenerateColumns), et en ajoutant
un gestionnaire pour l’événement LoadingRow. Les colonnes sont du type
DataGridTextColumn pour visualiser les données des propriétés ModeleNumero,
ModeleNom, ModeleCategorie, ModeleQuantite, ModelePrix, ModeleImage et
ModeleDescription d’un objet ProduitVelo. La colonne, pour la propriété booléenne
ModeleCouleur, est de type DataGridCheckBoxColumn. Chaque colonne est reliée
à une propriété par l’attribut Binding. La figure 13.25 illustre le résultat basique
attendu.
<data:DataGrid Name= «x_datagrid» Grid.Row= «1» AutoGenerateColumns= «False»
LoadingRow= «x_datagrid_LoadingRow» IsReadOnly= «True»>
<data:DataGrid.Columns>
<!-- colonne numero *********************** -->
<data:DataGridTextColumn Header= «Numéro» Width= «75»
Copyright 2012 Patrice REY

Binding= «{Binding ModeleNumero}» ></data:DataGridTextColumn>


<!-- colonne nom *********************** -->
<data:DataGridTextColumn Header= «Nom» Width= «100»
Binding= «{Binding ModeleNom}»></data:DataGridTextColumn>
<!-- colonne categorie *********************** -->
<data:DataGridTextColumn Header= «Catégorie» Width= «75»
Binding= «{Binding ModeleCategorie}»></data:DataGridTextColumn>
<!-- colonne quantite *********************** -->
<data:DataGridTextColumn Header= «Quantité» Width= «75»
CHAPITRE 13 □ La gestion des données 407
FIGURE 13.24

FIGURE 13.25
propriété propriété propriété propriété
ModeleNumero ModeleCategorie ModelePrix ModeleCheminImgVelo
propriété propriété propriété propriété
ModeleNom ModeleQuantite ModeleCouleur ModeleDescription
408 Développez des applications Internet avec Silverlight 5
Binding= «{Binding ModeleQuantite}»></data:DataGridTextColumn>
<!-- colonne prix -->
<data:DataGridTextColumn Header= «Prix» Width= «75»
Binding= «{Binding ModelePrix}»></data:DataGridTextColumn>
<!-- colonne couleur personnalisee ************ -->
<data:DataGridCheckBoxColumn Header= «Couleur»
Binding= «{Binding ModeleCouleur}» Width= «70»>
</data:DataGridCheckBoxColumn>
<!-- colonne image ************************** -->
<data:DataGridTextColumn Header= «Image» Width= «150»
Binding= «{Binding ModeleCheminImgVelo}»></data:DataGridTextColumn>
<!-- colonne description *********************** -->
<data:DataGridTextColumn Header= «Description» Width= «150»
Binding= «{Binding ModeleDescription}»>
</data:DataGridTextColumn>
</data:DataGrid.Columns>
</data:DataGrid>
La colonne Prix affiche les données de la propriété ModelePrix de type double.
Ces données représentent une valeur monétaire. Dans l’extension de balisage
Binding, la propriété StringFormat spécifie le format de type string à utiliser pour
l’affichage. Le caractère «C» représente l’affichage monétaire dans la langue par
défaut (culture anglo-saxonne donc symbole monétaire $). Pour utiliser le symbole
monétaire € dans la culture française, il faut spécifier la culture que doit utiliser le
convertisseur.
L’attribut ConverterCulture représente le convertisseur de culture qui peut être
défini comme un identificateur basé sur des normes. En lui passant la valeur «fr-
FR», on utilise le convertisseur français. La figure 13.26 illustre le résultat attendu
en fonction du convertisseur choisi.
Les options les plus couramment employées pour la propriété StringFormat sont:
• «C» qui représente le symbole monétaire dans la culture choisie.
• «E» qui représente la notation scientifique (par exemple 1.2454E+004).
• «P» qui représente le pourcentage (par exemple 52.6%).
• «F?» qui représente un nombre de chiffres après la virgule (F2 pour 2.52 et F0
pour 2).
Copyright 2012 Patrice REY

• «d» qui représente une date au format court, et «D» qui représente une date
au format long.
• «f» qui représente une date au format long avec l’heure au format court, et
«F» qui représente une date au format long avec l’heure au format long.
• «M» qui représente le jour et le mois.
• «G» qui représente le format général de date et heure dans la culture choisie.
CHAPITRE 13 □ La gestion des données 409

<!-- colonne prix -->


<data:DataGridTextColumn Header= «Prix» Width= «75»
Binding= «{Binding ModelePrix,StringFormat=’C’}»>
</data:DataGridTextColumn>
<!-- colonne prix -->
<data:DataGridTextColumn Header= «Prix» Width= «75»
Binding= «{Binding ModelePrix,StringFormat=’C’,
ConverterCulture=fr-FR}»>
</data:DataGridTextColumn>
FIGURE 13.26

ConverterCulture
par défaut
(symbole
monétaire $)

ConverterCulture
= «fr-FR»

La colonne Couleur est de type DataGridCheckBoxColumn c’est-à-dire qu’elle


est composée de case à cocher. Sa liaison de données repose sur des valeurs
booléennes. Si la propriété relevée ModeleCouleur est égale à true, la case à cocher
est cochée, sinon c’est l’inverse.
La colonne Description représente la propriété ModeleDescription d’un objet
ProduitVelo. Il s’agit souvent d’un texte d’une certaine longueur pour lequel
il serait judicieux d’appliquer un style comme un retour à la ligne (propriété
TextWrapping d’un TextBlock). La propriété ElementStyle définit le style utilisé
lors du rendu de l’élément que la colonne affiche pour une cellule qui n’est pas
en mode édition. Pour ajouter un objet Style à la propriété ElementStyle, on
ouvre la propriété par <data:DataGridTextColumn.ElementStyle> et on ajoute un
élément Style. Son TargetType est TextBlock car il cible un texte dans une colonne
DataGridTextColumn. Et on définit le style à appliquer comme on l’a déjà vu dans
un précédent chapitre, avec des objets Setter.
410 Développez des applications Internet avec Silverlight 5
Ici on fixe la valeur Wrap à la propriété TextWrapping, la valeur Verdana à la
propriété FontFamily, et la valeur 14 à la propriété FontSize. La figure 13.27 illustre
le résultat attendu.
<!-- colonne description *********************** -->
<data:DataGridTextColumn Header= «Description» Width= «150»
Binding= «{Binding ModeleDescription}»>
<data:DataGridTextColumn.ElementStyle>
<Style TargetType= «TextBlock»>
<Setter Property= «TextWrapping» Value= «Wrap»></Setter>
<Setter Property= «FontFamily» Value= «Verdana»></Setter>
<Setter Property= «FontSize» Value= «14»></Setter>
</Style>
</data:DataGridTextColumn.ElementStyle>
</data:DataGridTextColumn>
FIGURE 13.27

<data:DataGridTextColumn Header= "Description" Width= "150"


Binding= "{Binding ModeleDescription}">
</data:DataGridTextColumn>

<data:DataGridTextColumn Header= "Description" Width= "150"


Binding= "{Binding ModeleDescription}">
<data:DataGridTextColumn.ElementStyle>
<Style TargetType= "TextBlock">
<Setter Property= "TextWrapping" Value= "Wrap"></Setter>
<Setter Property= "FontFamily" Value= "Verdana"></Setter>
<Setter Property= "FontSize" Value= "14"></Setter>
</Style>
</data:DataGridTextColumn.ElementStyle>
</data:DataGridTextColumn>

Il est possible aussi de mettre le style en ressources dans le but de pouvoir le


réutiliser. Par exemple la colonne Prix a ses données écrites en gras. On définit
le style k_prix_gras dans les ressources de l’UserControl. Pour appliquer le style,
la propriété ElementStyle fait appel à la ressource statique k_prix_gras. La figure
13.28 illustre le résultat attendu.
Copyright 2012 Patrice REY

<UserControl.Resources>
<Style x:Key= «k_prix_gras» TargetType= «TextBlock»>
<Setter Property= «FontWeight» Value= «Bold»></Setter>
</Style>
</UserControl.Resources>
<data:DataGrid Name= «x_datagrid» Grid.Row= «1» AutoGenerateColumns= «False»
LoadingRow= «x_datagrid_LoadingRow» IsReadOnly= «True»>
<data:DataGrid.Columns>
CHAPITRE 13 □ La gestion des données 411

...
<data:DataGridTextColumn Header= «Prix» Width= «75»
Binding= «{Binding ModelePrix,StringFormat=’C’,ConverterCulture=fr-FR}»
ElementStyle= «{StaticResource k_prix_gras}»></data:DataGridTextColumn>
...
</data:DataGrid.Columns>
</data:DataGrid>
FIGURE 13.28

<data:DataGridTextColumn Header= "Prix" Width= "75"


Binding= "{Binding ModelePrix,StringFormat='C',ConverterCulture=fr-FR}">
</data:DataGridTextColumn>

<data:DataGridTextColumn Header= "Prix" Width= "75"


Binding= "{Binding ModelePrix,StringFormat='C',ConverterCulture=fr-FR}"
ElementStyle= "{StaticResource k_prix_gras}">
</data:DataGridTextColumn>

La colonne Image contient des données qui représentent un chemin, qui pointe
vers une image au format JPG, qui est stockée dans le dossier contenu. Ce qu’il
faudrait ce serait de remplacer le texte du chemin par l’image elle-même qui est
pointée par ce chemin.
Pour positionner un contrôle Image dans une colonne, il faut appliquer à cette
colonne un type DataGridTemplateColumn. Un objet DataGridTemplateColumn
représente une colonne dans un DataGrid qui héberge du contenu spécifié par
modèle dans ses cellules. Sa propriété CellTemplate définit le modèle utilisé pour
afficher le contenu d’une cellule qui n’est pas en mode édition. On affecte à cette
propriété un élément objet DataTemplate qui définit l’affichage pour un mode
qui n’est pas en mode édition. Ici ce DataTemplate est composé d’un contrôle
Image. Sa propriété Source est liée par un objet Binding qui référence la propriété
ModeleCheminImgVelo d’un objet ProduitVelo. La donnée liée n’est qu’un chemin
d’accès vers une ressource mais pas un contrôle Image. Il faut donc utiliser un
convertisseur par la propriété Converter de Binding.
La propriété Converter spécifie l’objet de convertisseur appelé par le moteur de
liaison. Le convertisseur peut être défini en XAML, mais uniquement si vous faites
référence à un convertisseur qui est défini de manière à pouvoir être instancié et
placé dans un ResourceDictionary en XAML. La référence XAML exige ensuite une
référence StaticResource à cet objet dans le dictionnaire de ressources.
412 Développez des applications Internet avec Silverlight 5
Ici on utilise une ressource statique avec la clé k_convertir_chemin_img,
placée dans les ressources de l’UserControl, qui permet d’instancier un objet
ConvertirCheminImage.
<!-- colonne image ************************** -->
<data:DataGridTemplateColumn Header= «Image» Width= «80»>
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Stretch= «Fill»
Source= «{Binding ModeleCheminImgVelo,
Converter={StaticResource k_convertir_chemin_img}}»
Width= «75» Height= «50»></Image>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
<UserControl.Resources>
<local:ConvertirCheminImage x:Key= «k_convertir_chemin_img»>
</local:ConvertirCheminImage>
</UserControl.Resources>
Ensuite on déclare une classe ConvertirCheminImage dont le but sera de renvoyer
un objet BitmapImage. En effet, on a déjà vu dans un chapitre précédent que l’on
pouvait affecter un objet BitmapImage avec une propriété UriSource définie, à un
contrôle Image.
On déclare la classe ConvertirCheminImage qui implémente l’interface
IValueConverter. L’interface IValueConverter expose des méthodes qui permettent
la modification des données à mesure qu’elles passent par le moteur de liaison. La
définition de cette interface est:
namespace System.Windows.Data {
public interface IValueConverter {
//Modifie les données sources avant de les passer à la cible en vue de leur
//affichage dans l’interface utilisateur.
//Retourne :
//La valeur à passer à la propriété de dépendance cible.
object Convert(object value, Type targetType, object parameter,
CultureInfo culture);
//Modifie les données cibles avant de les passer à l’objet source. Cette
Copyright 2012 Patrice REY

// méthode est appelée uniquement dans les liaisons BindingMode.TwoWay.


//retourne :
//La valeur à passer à l’objet source.
object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture);
}
}
On définit la propriété BaseUri qui stocke le chemin de l’emplacement de la
CHAPITRE 13 □ La gestion des données 413

ressource image. Comme les images ici sont embarquées dans l’application, la
propriété BaseUri aura comme valeur «/ControleDesDonnees;component/» (dans
le constructeur). La méthode Convert reçoit la donnée value (de type object donc
avec un cast en string ici) et retourne un objet BitmapImage. On instancie un
BitmapImage et on affecte à sa propriété UriSource un Uri composé de m_base_
uri et de la donnée de type string.
FIGURE 13.29

<data:DataGridTextColumn Header="Image" Width="150"


Binding="{Binding ModeleCheminImgVelo}">
</data:DataGridTextColumn>

<data:DataGridTemplateColumn Header="Image" Width="80">


<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Stretch="Fill"
Source="{Binding ModeleCheminImgVelo,
Converter={StaticResource k_convertir_chemin_img}}"
Width="75" Height="50"></Image>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

public class ConvertirCheminImage : IValueConverter {


//champs et propriete
private string m_base_uri;
public string BaseUri {
get { return m_base_uri; }
set { m_base_uri = value; }
}
//constructeur
public ConvertirCheminImage() {
m_base_uri = «/ControleDesDonnees;component/»;
}
//convertisseur
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture) {
string chemin_img = (string)value;
BitmapImage bim = new BitmapImage();
bim.UriSource = new Uri(m_base_uri + chemin_img, UriKind.Relative);
return bim;
}
//
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
414 Développez des applications Internet avec Silverlight 5
}
}//end class
Plutôt que d’avoir un fond de couleur en alternance sur chaque ligne, on
souhaiterait avoir une ligne de couleur grise quand la quantité en stock est nulle,
et une ligne blanche dans les autres cas. Pour cela il faut implémenter l’événement
LoadingRow du DataGrid.
Pour améliorer les performances, le contrôle DataGrid n’instancie pas d’objet
DataGridRow pour chaque élément de données dans la source de données liée.
Au lieu de cela, la grille de données crée des objets DataGridRow uniquement
lorsqu’ils sont nécessaires et les réutilise autant que possible. Par exemple, la grille
de données crée un objet DataGridRow pour chaque élément de données qui est
actuellement affiché et recycle la ligne lorsque l’élément de données défile hors de
l’écran. Cet événement vous permet d’apporter toute modification nécessaire à une
nouvelle ligne avant qu’elle puisse être utilisée. Pour annuler ces personnalisations
avant qu’une ligne soit réutilisée, gérez l’événement UnloadingRow.
L’implémentation consiste à récupérer l’objet ProduitVelo d’une ligne DataGridRow
par l’intermédiaire de la propriété DataContext d’un objet Row. Si la quantité est
nulle (propriété ModeleQuantite égale à 0), alors l’objet Row voit sa propriété
Background fixée à un SolidColorBrush dont la couleur est LightGray. Dans le cas
contraire c’est une couleur White qui est passée. La figure 13.30 illustre le résultat
attendu.
FIGURE 13.30

un surlignage gris
pour un stock nul
Copyright 2012 Patrice REY

//evenement loadingrow sur x_datagrid


private void x_datagrid_LoadingRow(object sender, DataGridRowEventArgs e) {
//on recupere l’objet ProduitVelo
ProduitVelo produit_velo = (ProduitVelo)e.Row.DataContext;
//on applique le formatage
if (produit_velo.ModeleQuantite == 0) {
e.Row.Background = new SolidColorBrush(Colors.LightGray);
} else {
CHAPITRE 13 □ La gestion des données 415

e.Row.Background = new SolidColorBrush(Colors.White);


}
}

3.4 - Le détail d’une ligne

La propriété RowDetailsTemplate du DataGrid permet de définir une zone


de détail pour chaque ligne, dont l’affichage est conditionné par la propriété
RowDetailsVisibilityMode. Cette propriété peut prendre les valeurs:
• Visible quand la zone de détail est visible pour toutes les lignes.
• VisibleWhenSelected quand la zone de détail est visible sur la ligne courante
(valeur par défaut).
• Collapsed quand la zone de détail est cachée pour toutes les lignes.
L’événement RowDetailsVisibilityChanged est émis lors du changement de
valeur de cette propriété. Une zone de détail peut être définie pour une ligne
spécifique au moyen des propriétés DetailsTemplate et DetailsVisibility d’un objet
DataGridRow. Les événements LoadingRowDetails et UnloadingRowDetails sont
émis respectivement quand la zone de détail est chargée ou déchargée pour une
ligne. En cas de défilement, les zones cachées sont déchargées.
Nous allons ici permettre l’affichage d’une zone de détail dans laquelle un TextBlock
contiendra le contenu de la propriété ModeleDescription d’un objet ProduitVelo. On
ajoute un objet DataTemplate à la propriété RowDetailsTemplate (<data:DataGrid.
RowDetailsTemplate>). Ce DataTemplate est composé d’un Grid, avec une ligne et
une colonne, sur lequel on positionne un Border qui contient un TextBlock. Le
contenu du TextBlock est lié par databinding à la propriété ModeleDescription. La
figure 13.31 illustre le résultat attendu. Quand on sélectionne une ligne, une zone
de détail s’affiche en-dessous. Cette zone est composée d’une bordure épaisse
dans laquelle le contenu de la propriété ModeleDescription est affiché.
<data:DataGrid Name=»x_datagrid» Grid.Row=»1» AutoGenerateColumns=»False»
LoadingRow=»x_datagrid_LoadingRow» IsReadOnly=»True»>
<data:DataGrid.RowDetailsTemplate>
<DataTemplate>
<Grid>
<Border Margin=»5» Padding=»10» BorderBrush=»Black» BorderThickness=»3»
CornerRadius=»5» Background=»White»>
<TextBlock Text=»{Binding ModeleDescription}» TextWrapping=»Wrap»
FontSize=»14» FontFamily=»Trebuchet MS»></TextBlock>
</Border>
</Grid>
</DataTemplate>
416 Développez des applications Internet avec Silverlight 5
</data:DataGrid.RowDetailsTemplate>
<data:DataGrid.Columns> ... </data:DataGrid.Columns>
</data:DataGrid>
FIGURE 13.31

3.5 - Regrouper des lignes

Une vue de collection permet de naviguer parmi les éléments dans une
collection en gérant une notion d’élément courant, et permet le tri, le filtrage
et le regroupement de données. Ce comportement est défini par l’interface
ICollectionView, implémentée par la classe PagedCollectionView.
La classe PagedCollectionView représente une vue permettant des opérations de
regroupement, de tri, de filtrage et de navigation dans une collection de données
paginée.
Copyright 2012 Patrice REY

L’UserControl RegroupementLigne.xaml (dans la solution ControleDesDonnees.sln


dans le dossier chapitre13) illustre la façon de visualiser un ensemble de données
avec des colonnes personnalisées et des lignes regroupées en fonction de critères
(figure 13.32). Les données visualisées sont des objets de la classe ProduitVelo.
Quand l’UserControl est chargé, on instancie une nouvelle vue vue_donnee de
type PagedCollectionView, qui reçoit en paramètre une collection (liste_velo de
type List<ProduitVelo>). La propriété GroupDescriptions représente une collection
CHAPITRE 13 □ La gestion des données 417

d’objets GroupDescription qui décrivent comment les éléments de la collection


sont regroupés dans la vue. La classe GroupDescription fournit une classe de base
pour définir comment diviser les éléments d’une collection en groupes. La classe
PropertyGroupDescription, qui hérite de la classe GroupDescription, décrit le
regroupement d’éléments en utilisant pour critère le nom d’une propriété.
Pour regrouper les éléments ProduitVelo en fonction de la propriété
ModeleCategorie, on instancie un objet PropertyGroupDescription en lui passant
le nom de la propriété de regroupement (ici ModeleCategorie). Cet objet est
ajouté à la collection GroupDescriptions de vue_donnee. La propriété ItemsSource
de DataGrid reçoit la collection utilisée (vue_donnee) pour générer le contenu du
DataGrid. La figure 13.32 illustre le résultat attendu. Les lignes sont regroupées en
fonction de leur catégorie (repère n°1 à n°5).
FIGURE 13.32

5
418 Développez des applications Internet avec Silverlight 5
//evenement loaded
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
List<ProduitVelo> liste_velo = new List<ProduitVelo>();
ProduitVelo velo = null;
velo = new ProduitVelo(ProduitVelo.Categorie.de_ville, «velo123256»,
«ligne classique AB02», 125.56, «velo de ville, classique avec ligne
épurée», «contenu/velo_ville.jpg», 2, true);
liste_velo.Add(velo);
...
PagedCollectionView vue_donnee = new PagedCollectionView(liste_velo);
vue_donnee.GroupDescriptions.Add(
new PropertyGroupDescription(«ModeleCategorie»));
x_datagrid.ItemsSource = vue_donnee;
}
Le regroupement de ligne peut être fait avec un tri dans chacun des groupes
composés. L’UserControl RegroupementLigneTrier.xaml (dans la solution
ControleDesDonnees.sln dans le dossier chapitre13) illustre la façon de visualiser un
ensemble de données avec des colonnes personnalisées, et des lignes regroupées
et triées en fonction de critères (figure 13.33).
Dans cet exemple, le regroupement est fait en fonction de la propriété booléenne
ModeleCouleur de ProduitVelo. Il y a donc que 2 regroupements possibles: les
lignes qui ont la valeur true, et celles qui ont la valeur false.
Dans chacun de ces regroupements, on effectue un tri en fonction de la
propriété ModelePrix par ordre décroissant. La propriété SortDescriptions de
PagedCollectionView représente une collection d’objets SortDescription qui
décrivent comment les éléments de la collection sont triés dans la vue. Le tri se
fait en ajoutant à cette collection des objets SortDescription.
La structure SortDescription définit le sens et le nom de propriété qui seront
utilisés comme critères de tri d’une collection. La propriété PropertyName de
SortDescription définit le nom de propriété qui est utilisé comme critère de tri,
et la propriété Direction définit une valeur qui indique s’il faut trier en ordre
croissant ou décroissant. Pour trier les lignes en fonction de ModelePrix par ordre
décroissant, il faudra instancier une structure SortDescription avec la propriété
PropertyName fixée à ModelePrix et la propriété Direction fixée à la valeur
Copyright 2012 Patrice REY

énumérée ListSortDirection.Descending (pour un ordre croissant, on utilisera la


valeur ListSortDirection.Ascending). La figure 13.33 montre le résultat attendu
(repère n°1 et n°2).
//evenement loaded
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
List<ProduitVelo> liste_velo = new List<ProduitVelo>();
ProduitVelo velo = null;
CHAPITRE 13 □ La gestion des données 419

velo = new ProduitVelo(ProduitVelo.Categorie.de_ville, «velo123256»,


«ligne classique AB02», 125.56, «velo de ville, classique avec ligne
épurée», «contenu/velo_ville.jpg», 2, true);
liste_velo.Add(velo);
...
PagedCollectionView vue_donnee = new PagedCollectionView(liste_velo);
vue_donnee.GroupDescriptions.Add(
new PropertyGroupDescription(«ModeleCategorie»));
x_datagrid.ItemsSource = vue_donnee;
}
FIGURE 13.33

Pour appliquer un style particulier à la ligne générée qui représente un


regroupement, il faut affecter un objet Style à la propriété RowGroupHeaderStyles.
Cet objet Style doit cibler la propriété DataGridRowGroupHeader.
Par exemple ici, on décide que la zone de regroupement ait un fond noir et une
écriture blanche. Pour cela le style appliqué contient des objets Setter qui modifie
la propriété Background et Foreground. La figure 13.34 illustre le résultat attendu
pour l’application d’un style à la zone de regroupement.
420 Développez des applications Internet avec Silverlight 5
<!-- contenu des donnees -->
<data:DataGrid Name= «x_datagrid» Grid.Row= «1» AutoGenerateColumns= «False»
LoadingRow= «x_datagrid_LoadingRow» IsReadOnly= «True»>
<data:DataGrid.RowGroupHeaderStyles>
<Style TargetType= «data:DataGridRowGroupHeader»>
<Setter Property= «Background» Value= «Black» />
<Setter Property= «Foreground» Value= «White» />
</Style>
</data:DataGrid.RowGroupHeaderStyles>
<data:DataGrid.Columns>
...
</data:DataGrid.Columns>
</data:DataGrid>

FIGURE 13.34

3.6 - Utiliser la pagination

Silverlight intègre un mécanisme extensible de pagination locale des données,


basé sur l’interface IPagedCollectionView et sur le contrôle DataPager.
L’interface IPagedCollectionView définit une vue paginée sur une collection. Elle
dispose de membres relatifs à la gestion des pages avec notamment:
• des méthodes préfixées par MoveTo qui définissent les méthodes de
changement de page (MoveToFirstPage, MoveToLastPage, MoveToNextPage,
MoveToPage et MoveToPreviousPage).
• la propriété PageSize qui définit le nombre d’éléments de la collection contenus
dans une page.
Copyright 2012 Patrice REY

• la propriété PageIndex qui définit le nombre de page courant.


• l’événement PageChanging qui est émis avant le changement de page; la
propriété Cancel de l’argument PageChangingEventArgs permet d’empêcher
le changement.
• l’événement PageChanged qui est émis après le changement de page.
CHAPITRE 13 □ La gestion des données 421

La classe DataPager (figure 13.35), qui hérite de Control, fournit une interface
utilisateur qui permet la pagination au sein d’une collection de données. Le
contrôle DataPager fournit une interface utilisateur configurable pour effectuer
une pagination via une collecte de données. Vous pouvez généralement utiliser une
collection qui implémente IPagedCollectionView en tant que source de données
et l’interface IPagedCollectionView fournit la fonctionnalité de pagination.
Vous pouvez lier le DataPager à toute collection IEnumerable. Le DataPager se
comportera toutefois comme si toutes les données se trouvaient dans une même
page. Pour fournir la fonctionnalité de pagination d’une collection IEnumerable,
vous pouvez l’inclure dans un wrapper dans la classe PagedCollectionView.
FIGURE 13.35
422 Développez des applications Internet avec Silverlight 5
Les données paginées s’affichent généralement dans un contrôle qui représente
une collection (DataGrid ou ListBox, par exemple). Pour utiliser un DataPager
en vue de paginer des données dans un autre contrôle, vous devez assigner la
propriété ItemsSource du contrôle et la propriété DataPager.Source à la même
collecte de données.
Vous pouvez modifier l’apparence du DataPager en définissant la propriété
DisplayMode. Si vous utilisez un DisplayMode qui affiche des boutons numériques,
vous pouvez modifier le nombre de boutons affichés en définissant la propriété
NumericButtonCount. Vous pouvez affecter true à la propriété AutoEllipsis pour
que le DataPager affiche des points de suspension au lieu du dernier bouton
numérique, à moins que les points de suspension ne correspondent à la dernière
page. Vous pouvez également modifier l’apparence des boutons numériques en
définissant un NumericButtonStyle personnalisé. La figure 13.36 montre le contrôle
DataPager configuré pour utiliser différents modes d’affichage.
FIGURE 13.36
DisplayMode = FirstLastNumeric

DisplayMode = FirstLastPreviousNext

DisplayMode = FirstLastPreviousNextNumeric

DisplayMode = Numeric

DisplayMode = PreviousNext

DisplayMode = PreviousNextNumeric

L’UserControl RegroupementDataPager.xaml (dans la solution ControleDesDonnees.


sln dans le dossier chapitre13) illustre la façon de visualiser un ensemble de données
Copyright 2012 Patrice REY

avec des colonnes personnalisées et avec une pagination par l’intermédiaire du


contrôle DataPager (figure 13.37). Les données visualisées sont des objets de la
classe ProduitVelo.
En XAML on ajoute un contrôle DataPager x_datapager, en fixant sa
propriété PageSize à 4 (pour 4 lignes affichées), un DisplayMode comme
FirstLastPreviousNextNumeric, un NumericButtonCount à 3 et IsTotalItemCountFixed
CHAPITRE 13 □ La gestion des données 423

à true. Le repère n°1 de la figure 13.37 visualise le résultat obtenu.


FIGURE 13.37

<!-- contenu -->


<Grid x:Name= «x_grid_root» HorizontalAlignment= «Center»
VerticalAlignment= «Top»>
<!-- contenu des donnees -->
<data:DataGrid Name= «x_datagrid» Grid.Row= «1» AutoGenerateColumns= «False»
LoadingRow= «x_datagrid_LoadingRow» IsReadOnly= «True»>
...
</data:DataGrid>
424 Développez des applications Internet avec Silverlight 5
<!-- datapager ********************************** -->
<data:DataPager Name= «x_datapager» Grid.Row= «2»
PageSize= «4» DisplayMode= «FirstLastPreviousNextNumeric»
NumericButtonCount= «3» IsTotalItemCountFixed= «True»></data:DataPager>
</Grid>
La propriété ItemsSource du DataGrid reçoit un objet vue_donnee de type
PagedCollectionView. La propriété Source de x_datapager reçoit elle aussi la
même collection de données vue_donnee.
//evenement loaded
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
List<ProduitVelo> liste_velo = new List<ProduitVelo>();
ProduitVelo velo = null;
velo = new ProduitVelo(ProduitVelo.Categorie.de_ville, «velo123256»,
«ligne classique AB02», 125.56, «velo de ville, classique avec ligne
épurée», «contenu/velo_ville.jpg», 2, true);
liste_velo.Add(velo);
...
PagedCollectionView vue_donnee = new PagedCollectionView(liste_velo);
x_datagrid.ItemsSource = vue_donnee;
x_datapager.Source = vue_donnee;
}

4 - Le contrôle TreeView

La classe TreeView représente un contrôle qui affiche des données hiérarchiques


dans une arborescence dont les éléments peuvent être développés ou réduits.
La classe TreeViewItem fournit un élément sélectionnable hiérarchique pour
le contrôle TreeView. La figure 13.38 montre le graphe d’héritage des classes
TreeView et TreeViewItem.
Le contrôle TreeView présente les données sous une forme hiérarchisée. Comme
le contrôle TreeView est un ItemsControl, vous devez remplir ses propriétés Items
ou ItemsSource pour le remplir avec du contenu.
En général, un contrôle TreeView contient des objets TreeViewItem. Les objets
TreeViewItem peuvent contenir un en-tête et un élément de contenu (comme un
Copyright 2012 Patrice REY

autre TreeViewItem qui peut contenir un en-tête et un autre TreeViewItem). Ces


contrôles TreeViewItem imbriqués constituent la hiérarchie du contrôle TreeView.
Le contrôle TreeView permet à l’utilisateur de développer ou de réduire des nœuds
dans la hiérarchie à l’aide de la souris ou du clavier.
Vous pouvez obtenir l’élément sélectionné à l’aide de la propriété SelectedItem.
L’événement SelectedItemChanged se produit lorsque l’élément sélectionné est
modifié.
CHAPITRE 13 □ La gestion des données 425

Vous pouvez lier le TreeView aux données et utiliser un HierarchicalDataTemplate


pour afficher les données sous une forme hiérarchisée. Vous pouvez utiliser les
propriétés ItemsSource et ItemTemplate du modèle de données pour spécifier la
source et le format du niveau de données suivant contenues dans le TreeView.
Lorsque le TreeView est lié à des données, vous pouvez récupérer des éléments
via leur index ou leur conteneur à l’aide du ItemContainerGenerator associé au
TreeView.
Vous pouvez définir un style personnalisé pour les objets TreeViewItem contenus
dans le TreeView en définissant sa propriété ItemContainerStyle. Pour appliquer
les mêmes paramètres de propriété à plusieurs contrôles TreeView, utilisez
la propriété Style. Pour modifier la structure et le comportement visuels d’un
TreeView, copiez et modifiez son modèle et son style par défaut. Les propriétés
de dépendance de ce contrôle peuvent être définies par le style par défaut du
contrôle. Si une propriété de dépendance pour un TreeView est définie par son
style par défaut, la propriété peut prendre une valeur autre que celle par défaut
lorsque le TreeView s’affiche dans l’application.
La classe TreeViewItem fournit un conteneur pour les éléments de contenu affichés
dans un contrôle TreeView. Vous pouvez remplir ce contrôle à l’aide de la propriété
Items ou ItemsSource. TreeViewItem est un contrôle HeaderedItemsControl par
héritage. Vous pouvez définir un en-tête pour son contenu à l’aide de la propriété
Header.
FIGURE 13.38
426 Développez des applications Internet avec Silverlight 5
Lorsqu’un TreeViewItem contient des objets TreeViewItem supplémentaires, il
peut être développé et réduit en cliquant sur l’icône de signe insertion située à
côté du contrôle. Vous pouvez développer par programmation le TreeViewItem en
affectant true à IsExpanded ou réduire l’élément en affectant false à IsExpanded.
Pour désélectionner un élément, affectez true à la propriété SelectedItem. La
personnalisation du TreeViewItem par un style s’effectue de la même façon que
pour le TreeView.

4.1 - Une arborescence basique

L’UserControl TreeViewBasique.xaml (dans la solution ControleDesDonnees.sln


dans le dossier chapitre13) illustre la façon de visualiser une arborescence de
données (figure 13.39).
FIGURE 13.39

Copyright 2012 Patrice REY


CHAPITRE 13 □ La gestion des données 427

Pour ajouter un contrôle TreeView en XAML, il faut ajouter dans le dossier


Références l’assembly System.Windows.Controls. Ensuite il faut ajouter une
référence xmlns, nommée controls, à l’espace de noms System.Windows.Controls
(xmlns:controls = «clr-namespace:System.Windows.Controls; assembly=System.
Windows.Controls»).
Dans l’exemple (figure 13.39), on place des contrôles TreeViewItem pour réaliser
une arborescence de données. La propriété Header d’un TreeViewItem représente
l’en-tête de la donnée. Dans une arborescence, on peut ajouter d’autres contrôles
comme un Button x_btn1. Un contrôle TreeViewItem peut contenir d’autres
contrôles comme le Button x_btn2 et le TextBlock x_textblock.
<controls:TreeView Background= «WhiteSmoke» BorderBrush= «Transparent»
Width= «300» Height= «400» HorizontalAlignment= «Left»
Grid.ColumnSpan= «2» Name= «x_treeview»
ItemContainerStyle= «{StaticResource k_style_treeview_perso}»
SelectedItemChanged= «x_treeview_SelectedItemChanged»
VerticalAlignment= «Top»>
<controls:TreeViewItem Header= «1 - les repas»></controls:TreeViewItem>
<controls:TreeViewItem Header= «2 - les fruits»>
<controls:TreeViewItem Header= «2.1 - l’ananas»></controls:TreeViewItem>
<controls:TreeViewItem Header= «2.2 - l’orange»></controls:TreeViewItem>
<controls:TreeViewItem Header= «2.3 - le raisin»></controls:TreeViewItem>
<controls:TreeViewItem Header= «2.4 - la pomme»></controls:TreeViewItem>
</controls:TreeViewItem>
<controls:TreeViewItem Header= «3 - les légumes»>
<controls:TreeViewItem Header= «3.1 - les épinards»></controls:TreeViewItem>
<controls:TreeViewItem Header= «3.2 - les choux»></controls:TreeViewItem>
<controls:TreeViewItem Header= «3.3 - les petits pois»>
</controls:TreeViewItem>
</controls:TreeViewItem>
<Button Content= «bouton n°1» FontFamily= «Verdana» FontSize= «14»
Name= «x_btn1» Width= «150» Cursor= «Hand»></Button>
<controls:TreeViewItem Header= «4 - des contrôles»>
<Button Content= «bouton n°2» FontFamily= «Verdana» FontSize= «14»
Name= «x_btn2» Width= «150» Cursor= «Hand»></Button>
<TextBlock Name= «x_textblock» Text= «un TextBlock avec son contenu»
FontFamily= «Verdana» FontSize= «14» FontStyle= «Italic»></TextBlock>
</controls:TreeViewItem>
</controls:TreeView>
L’événement SelectedItemChanged est émis lorsqu’un élément est sélectionné
dans le TreeView. La méthode GetType permet de connaître le type de l’objet
sélectionné, et par conséquent d’agir sur l’action à réaliser. Dans l’exemple, le type
de l’élément sélectionné est ajouté au format texte dans le TextBlock x_infos.
//evenement selected item changed
private void x_treeview_SelectedItemChanged(object sender,
428 Développez des applications Internet avec Silverlight 5
RoutedPropertyChangedEventArgs<object> e) {
x_infos.Text += x_treeview.SelectedItem.GetType().ToString() + RC;
}

4.2 - Une arborescence liée à des données

L’UserControl TreeViewAvecDonnees.xaml (dans la solution ControleDesDonnees.


sln dans le dossier chapitre13) illustre la façon de visualiser une arborescence liée
par un ensemble de données (figure 13.40). Les données utilisées sont de la classe
Artiste, Album et Chanson. L’ouverture d’un nœud concernant un type Artiste
dévoile les nœuds de type Album (repère n°1). L’ouverture d’un nœud concernant
un type Album dévoile les nœuds de type Chanson (repère n°2).
FIGURE 13.40

3
1
2

Copyright 2012 Patrice REY


CHAPITRE 13 □ La gestion des données 429

La classe Artiste représente un chanteur. Sa propriété ArtisteNom définit le


nom du chanteur, sa propriété ArtisteGenre définit son genre de musique, et sa
propriété ArtisteCollectionAlbums définit une collection d’objets de type Album.
Une propriété statique ImagetteArtiste permet de récupérer un objet Image dont
la source pointe sur une image embarquée, intitulée img_artiste.png, dans le
dossier contenu.
A cette classe, on ajoute un attribut de type ContentPropertyAttribute qui spécifie
la propriété d’une classe pouvant être interprétée comme la propriété de contenu
lorsque la classe est analysée par un processeur XAML. Cet attribut reçoit le nom
de la propriété ArtisteCollectionAlbums puisqu’il faudra remplir la collection par
des objets de type Album.
La méthode statique TousLesArtistes retourne une collection d’objets de type
Artiste. Le détail de cette collection est écrit dans les ressources de l’application et
porte la clé k_catalogue_musique.
//represente un artiste
[ContentProperty(«ArtisteCollectionAlbums»)]
public class Artiste {
private string RC = Environment.NewLine;
//nom de l’artiste
public string ArtisteNom { get; set; }
//genre de l’artiste
public string ArtisteGenre { get; set; }
//obtenir la collection des albums
public Collection<Album> ArtisteCollectionAlbums { get; private set; }
//constructeur
public Artiste() {
ArtisteCollectionAlbums = new Collection<Album>();
}
//obtenir imagette artiste
public static Image ImagetteArtiste {
get {
Image img = Utilitaire.ObtenirImagette(«img_artiste.png»);
img.Width = 20;
img.Height = 20;
img.Stretch = Stretch.Fill;
return img;
}
}
//obtenir la collection d’artistes
public static IEnumerable<Artiste> TousLesArtistes {
get {
IEnumerable<object> data =
Application.Current.Resources[«k_catalogue_musique»]
as IEnumerable<object>;
430 Développez des applications Internet avec Silverlight 5
if (data != null) {
return data.OfType<Artiste>();
} else {
return Enumerable.Empty<Artiste>();
}
}
}
//
public override string ToString() {
string aff = «»;
aff += «artiste -> « + this.ArtisteNom + RC;
aff += «genre -> « + this.ArtisteGenre + RC;
aff += «nombre d’albums -> « +
this.ArtisteCollectionAlbums.Count.ToString() + RC;
return aff;
}
}//end class
La classe Album représente un album de chansons. Sa propriété AlbumTitre
définit le nom de l’album, sa propriété AlbumAnnee définit l’année de sortie, et
sa propriété AlbumCollectionChansons définit une collection d’objets de type
Chanson. Une propriété statique ImagetteAlbum permet de récupérer un objet
Image dont la source pointe sur une image embarquée, intitulée img_album.png,
dans le dossier contenu.
A cette classe, on ajoute un attribut de type ContentPropertyAttribute qui spécifie
la propriété d’une classe pouvant être interprétée comme la propriété de contenu
lorsque la classe est analysée par un processeur XAML. Cet attribut reçoit le nom
de la propriété AlbumCollectionChansons puisqu’il faudra remplir la collection par
des objets de type Chanson.
//represente un album
[ContentProperty(«AlbumCollectionChansons»)]
public class Album {
private string RC = Environment.NewLine;
//titre de l’album
public string AlbumTitre { get; set; }
//annee de sortie de l’album
public string AlbumAnnee { get; set; }
Copyright 2012 Patrice REY

//imagette album
public static Image ImagetteAlbum {
get {
Image img = Utilitaire.ObtenirImagette(«img_album.png»);
img.Width = 20;
img.Height = 20;
img.Stretch = Stretch.Fill;
return img;
}
CHAPITRE 13 □ La gestion des données 431

}
//collection de chansons contenue dans un album
public Collection<Chanson> AlbumCollectionChansons { get; private set; }
//constructeur
public Album() {
AlbumCollectionChansons = new Collection<Chanson>();
}
//
public override string ToString() {
string aff = «»;
aff += «titre -> « + this.AlbumTitre + RC;
aff += «année -> « + this.AlbumAnnee + RC;
aff += «nombre de chansons -> « +
this.AlbumCollectionChansons.Count.ToString() + RC;
return aff;
}
}//end class
La classe Chanson représente une chanson. Sa propriété ChansonTitre définit le
nom de la chanson et sa propriété ChansonDuree définit la durée de la chanson.
Une propriété statique ImagetteChanson permet de récupérer un objet Image
dont la source pointe sur une image embarquée, intitulée img_chanson.png, dans
le dossier contenu.
public class Chanson {
private string RC = Environment.NewLine;
//propriete titre de la chanson
public string ChansonTitre { get; set; }
//propriete duree de la chanson
public string ChansonDuree { get; set; }
//obtenir imagette pour une chanson
public static Image ImagetteChanson {
get {
Image img = Utilitaire.ObtenirImagette(«img_chanson.png»);
img.Width = 20;
img.Height = 20;
img.Stretch = Stretch.Fill;
return img;
}
}
//constructeur
public Chanson() { }
//
public override string ToString() {
string aff = «»;
aff += «titre -> « + this.ChansonTitre + RC;
aff += «durée -> « + this.ChansonDuree + RC;
return aff;
}
432 Développez des applications Internet avec Silverlight 5
}//end class
Dans les ressources de l’application (fichier App.xaml), on positionne le contenu
qui sera analysé par le processeur XAML. Pour ajouter une collection d’objets, il
faut ajouter une référence xmlns, nommée toolkit, qui est xmlns:toolkit = «clr-
namespace:System.Windows.Controls;assembly=System.Windows.Controls.
Toolkit». Pour accéder aux objets des classes Artiste, Album et Chanson, il
faut ajouter une référence xmlns, nommée local, qui est xmlns:local = «clr-
namespace:ControleDesDonnees». En premier on commence par ajouter les
nœuds concernant les artistes.
<Application.Resources>
<!-- catalogue de musiques pour le treeview -->
<toolkit:ObjectCollection x:Key= «k_catalogue_musique»
xmlns= «http://schemas.microsoft.com/winfx/2006/xaml/presentation»>
<local:Artiste ArtisteNom= «Frank Sinatra» ArtisteGenre= «Pop»>
...
</local:Artiste>
<local:Artiste ArtisteNom= «African Wind» ArtisteGenre= «Blues»>
...
</local:Artiste>
<local:Artiste ArtisteNom= «Johnny Cash» ArtisteGenre= «Country»>
...
</local:Artiste>
</toolkit:ObjectCollection>
</Application.Resources>
Ensuite, pour un artiste, on ajoute les nœuds qui contiennent les albums avec les
propriétés AlbumTitre et AlbumAnnee.
<local:Artiste ArtisteNom= «Frank Sinatra» ArtisteGenre= «Pop»>
<local:Album AlbumTitre= «In The Wee Small Hours» AlbumAnnee= «1954»>
...
</local:Album>
<local:Album AlbumTitre= «You Do Something To Me» AlbumAnnee= «2008»>
...
</local:Album>
</local:Artiste>
<local:Artiste ArtisteNom= «African Wind» ArtisteGenre= «Blues»>
Copyright 2012 Patrice REY

<local:Album AlbumTitre= «Mercy» AlbumAnnee= «2006»>


...
</local:Album>
</local:Artiste>
Enfin, pour un album, on ajoute les noeuds qui contiennent les chansons avec les
propriétés ChansonTitre et ChansonDuree.
<local:Artiste ArtisteNom= «Frank Sinatra» ArtisteGenre= «Pop»>
<local:Album AlbumTitre= «In The Wee Small Hours» AlbumAnnee= «1954»>
CHAPITRE 13 □ La gestion des données 433

<local:Chanson ChansonTitre= «In The Wee Small Hours Of The Morning»


ChansonDuree = «3:00» />
<local:Chanson ChansonTitre= «Mood Indigo» ChansonDuree= «3:30» />
<local:Chanson ChansonTitre= «Glad To Be Unhappy» ChansonDuree= «2:35» />
<local:Chanson ChansonTitre= «I Get Along Without You Very Well»
ChansonDuree= «3:42» />
<local:Chanson ChansonTitre= «Deep In A Dream» ChansonDuree= «2:49» />
</local:Album>
<local:Album AlbumTitre= «You Do Something To Me» AlbumAnnee= «2008»>
<local:Chanson ChansonTitre= «As Time Goes By» ChansonDuree= «2:13» />
<local:Chanson ChansonTitre= «Close To You» ChansonDuree= «3:06» />
<local:Chanson ChansonTitre= «Come Fly With Me» ChansonDuree= «2:54» />
<local:Chanson ChansonTitre= «Guess I’ll Hang My Tears Out To Dry»
ChansonDuree= «3:05» />
<local:Chanson ChansonTitre= «I’ve Got My Love To Keep Me Warm»
ChansonDuree= «1:56» />
</local:Album>
</local:Artiste>
Le contrôle TreeView x_treeview est alimenté par une collection d’objets de type
Artiste par sa propriété ItemsSource (collection obtenue par la méthode statique
Artiste.TousLesArtistes).
//evenement loaded de usercontrol
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
x_treeview.ItemsSource = Artiste.TousLesArtistes;
}
Le visuel de chaque élément est défini au moyen d’un DataTemplate spécifié
dans la propriété ItemTemplate. La propriété ItemTemplate est particulièrement
utile lorsque vous liez le ItemsSource aux données. Vous utilisez un DataTemplate
pour définir l’apparence de vos objets de données. Le contenu de DataTemplate
devient la structure visuelle de vos objets de données. Si vous ne définissez pas le
ItemTemplate, le ItemsControl affiche la représentation sous forme de chaîne des
objets dans une collection.
La ressource statique k_entree_artiste est affectée à la propriété ItemTemplate du
TreeView.
<controls:TreeView Grid.Column= «0» Grid.Row= «0» Name= «x_treeview»
ItemTemplate= «{StaticResource k_entree_artiste}»
SelectedItemChanged= «x_treeview_SelectedItemChanged»>
</controls:TreeView>
La classe HierarchicalDataTemplate représente un DataTemplate qui
prend en charge les objets HeaderedItemsControl, tels que TreeViewItem.
HierarchicalDataTemplate vous permet de définir un modèle de données pour le
niveau actuel des données hiérarchiques, ainsi que de définir le modèle pour le sous-
434 Développez des applications Internet avec Silverlight 5
niveau suivant avec la propriété ItemTemplate. Utilisez HierarchicalDataTemplate
pour afficher des données hiérarchiques dans un contrôle, tel qu’un objet TreeView.
L’objet HierarchicalDataTemplate, de clé k_entree_artiste, a sa propriété
ItemsSource qui reçoit la collection de données ArtisteCollectionAlbums, et son
ItemTemplate est la ressource statique k_entree_album. Ce nœud est composé
d’un conteneur StackPanel, à orientation horizontale, dans lequel on ajoute un
ContentPresenter dont sa propriété Content est liée par databinding à la propriété
statique ImagetteArtiste, et un TextBlock dont sa propriété Text est liée par
databinding à la propriété ArtisteNom.
<UserControl.Resources>
...
<common:HierarchicalDataTemplate x:Key= «k_entree_artiste»
ItemsSource= «{Binding Path=ArtisteCollectionAlbums}»
ItemTemplate= «{StaticResource k_entree_album}»>
<StackPanel Orientation= «Horizontal»>
<ContentPresenter Margin= «0 0 4 0» Content= «{Binding ImagetteArtiste}» />
<TextBlock Text= «{Binding Path=ArtisteNom}»
Style= «{StaticResource k_ecrit_artiste}» />
</StackPanel>
</common:HierarchicalDataTemplate>
</UserControl.Resources>
L’objet HierarchicalDataTemplate, de clé k_entree_album, a sa propriété
ItemsSource qui reçoit la collection de données AlbumCollectionChansons, et son
ItemTemplate est la ressource statique k_entree_chanson. Ce nœud est composé
d’un conteneur StackPanel, à orientation horizontale, dans lequel on ajoute un
ContentPresenter dont sa propriété Content est liée par databinding à la propriété
statique ImagetteAlbum, et un TextBlock dont sa propriété Text est liée par
databinding à la propriété AlbumTitre.
<UserControl.Resources>
...
<common:HierarchicalDataTemplate x:Key= «k_entree_album»
ItemsSource= «{Binding Path=AlbumCollectionChansons}»
ItemTemplate= «{StaticResource k_entree_chanson}»>
<StackPanel Orientation= «Horizontal»>
Copyright 2012 Patrice REY

<ContentPresenter Margin= «0 0 4 0» Content= «{Binding ImagetteAlbum}» />


<TextBlock Text= «{Binding Path=AlbumTitre}»
Style= «{StaticResource k_ecrit_album}» />
</StackPanel>
</common:HierarchicalDataTemplate>
...
</UserControl.Resources>
L’objet HierarchicalDataTemplate, de clé k_entree_chanson, est composé d’un
CHAPITRE 13 □ La gestion des données 435

conteneur StackPanel, à orientation horizontale, dans lequel on ajoute un


ContentPresenter dont sa propriété Content est liée par databinding à la propriété
statique ImagetteChanson, et un TextBlock dont sa propriété Text est liée par
databinding à la propriété ChansonTitre.
<UserControl.Resources>
<common:HierarchicalDataTemplate x:Key= «k_entree_chanson»>
<StackPanel Orientation= «Horizontal»>
<ContentPresenter Margin= «0 0 4 0»
Content= «{Binding ImagetteChanson}» />
<TextBlock Text= «{Binding Path=ChansonTitre}»
Style= «{StaticResource k_ecrit_chanson}» />
</StackPanel>
</common:HierarchicalDataTemplate>
...
</UserControl.Resources>
L’événement SelectedItemChanged est émis quand un nœud est sélectionné.
La propriété SelectedItem retourne l’objet sélectionné sous le type object. Le
TextBlock x_infos affiche le type du nœud par la méthode GetType, ainsi que le
détail au format texte de l’objet correspondant. Le repère n°1 de la figure 13.41
affiche le détail d’un nœud de type Artiste, le repère n°2 affiche le détail d’un
nœud de type Album, et le repère n°3 affiche le détail d’un nœud de type Chanson.
FIGURE 13.41

1 2 3
436 Développez des applications Internet avec Silverlight 5
//evenement selection donnee dans treeview
private void x_treeview_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e) {
object recup = x_treeview.SelectedItem;
x_infos.Text = «type = « + x_treeview.SelectedItem.GetType().ToString()
+ RC;
x_infos.Text += recup.ToString();
}

Copyright 2012 Patrice REY


C H A P I T R E 14 DANS CE CHAPITRE
• ObjectCollection et

La représentation
IEnumerable<T>
• AreaSeries
• BarSeries
graphique • ColumnSeries
• LineSeries
• BubbleSeries
des données • PieSeries

Silverlight possède un ensemble de classes qui


permettent de réaliser facilement des graphiques
à partir d’un ensemble de données.
Nous allons voir dans ce chapitre comment
implémenter une collection de données, et
comment l’intégrer en XAML pour effectuer une
représentation graphique rapide et soignée.
L’utilisation des classes génériques comme la
classe ObjectCollection, ainsi que l’utilisation
des interfaces génériques comme l’interface
IEnumerable<T>, seront abordés au travers de
plusieurs exemples.
Silverlight implémente des classes pour la
représentation graphique des données. Nous
verrons l’utilisation de ces classes pour visualiser
des données dans des graphiques comme les aires
graphiques, les graphiques à barres horizontales,
les graphiques à barres verticales, les graphiques
à lignes, les graphiques à bulles et les graphiques
à secteurs.
Chaque type de graphique fait l’objet d’un exemple
dans sa configuration par défaut, et dans une
configuration personnalisée (pour montrer les
particularités).
438 Développez des applications Internet avec Silverlight 5
Silverlight possède un ensemble de classes qui permettent de réaliser facilement
des graphiques à partir d’un ensemble de données. Avant de voir ces classes en
détail, nous allons voir comment réaliser une collection de données grâce à des
classes génériques spécialisées.

1 - Constituer une collection de données

Il existe différentes classes et différentes manières de constituer une collection de


données.
L’UserControl CollectionDonnees.xaml (dans la solution GraphiqueEtDiagramme.
sln dans le dossier chapitre14) illustre l’utilisation et la manipulation des collections
de données.

1.1 - La classe ObjectCollection

La classe ObjectCollection (figure 14.1) hérite de la classe Collection<T>. La classe


Collection<T> fournit la classe de base pour une collection générique. Elle peut
être utilisée immédiatement en créant une instance de l’un de ses types construits.
Vous devez simplement spécifier le type d’objet que la collection doit contenir. En
outre, vous pouvez dériver votre propre type de collection de tout type construit
ou dériver un type de collection générique de la classe Collection<T> elle-même.
La classe Collection<T> fournit des méthodes protégées qui peuvent être utilisées
pour personnaliser son comportement lorsqu’il s’agit d’ajouter et de supprimer des
éléments, d’effacer la collection ou de définir la valeur d’un élément existant. La
plupart des objets de la collection peuvent être modifiés. Il est possible d’accéder
aux éléments de cette collection en utilisant un index d’entiers. Les index de cette
collection sont des index de base zéro.
Une directive using doit être ajoutée pour l’utilisation de la classe ObjectCollection
(using System.Windows.Controls.DataVisualization).
Pour illustrer l’utilisation de la classe ObjectCollection, nous déclarons une classe
RatioFemmeParDivision qui contient une propriété Division de type int, et une
Copyright 2012 Patrice REY

propriété RatioDeFemme de type double. La propriété Division définit le numéro


d’un batiment dans une entreprise, et la propriété RatioFemmeParDivision définit
le nombre moyen de femmes employées dans une division.
La propriété statique DonneesDivisionRatioFemme retourne une collection, de
type ObjectCollection, qui est composée d’objet RatioFemmeParDivision. La
méthode Add permet d’ajouter un objet à la collection générique.
CHAPITRE 14 □ La représentation graphique des données 439
FIGURE 14.1

public class RatioFemmeParDivision {


//numero batiment
public int Division { get; set; }
//proportion de femme
public double RatioDeFemme { get; set; }
//constructeur
public RatioFemmeParDivision() {
}
//propriete: collection des donnees
public static ObjectCollection DonneesDivisionRatioFemme {
get {
ObjectCollection collection = new ObjectCollection();
collection.Add(new RatioFemmeParDivision { Division = 100,
RatioDeFemme = 4.1 });
collection.Add(new RatioFemmeParDivision { Division = 101,
RatioDeFemme = 4.3 });
collection.Add(new RatioFemmeParDivision { Division = 102,
RatioDeFemme = 5.7 });
collection.Add(new RatioFemmeParDivision { Division = 103,
RatioDeFemme = 5.4 });
collection.Add(new RatioFemmeParDivision { Division = 104,
RatioDeFemme = 5.9 });
collection.Add(new RatioFemmeParDivision { Division = 105,
440 Développez des applications Internet avec Silverlight 5
RatioDeFemme = 5.0 });
collection.Add(new RatioFemmeParDivision { Division = 106,
RatioDeFemme = 3.6 });
collection.Add(new RatioFemmeParDivision { Division = 107,
RatioDeFemme = 1.9 });
collection.Add(new RatioFemmeParDivision { Division = 108,
RatioDeFemme = 7.3 });
return collection;
}
}
}
Pour visualiser les données contenues dans la collection, on instancie la variable
collection qui reçoit la collection par la propriété statique RatioFemmeParDivision.
DonneesDivisionRatioFemme. La propriété Count de la collection donne le nombre
d’objets qu’elle contient. Une boucle for permet de parcourir les objets et de
récupérer leurs propriétés pour écrire dans x_infos_1 leurs valeurs. La figure 14.2
visualise le résultat attendu.
string message = «»;
ObjectCollection collection = RatioFemmeParDivision.DonneesDivisionRatioFemme;
for (int xx = 0; xx < collection.Count; xx++) {
RatioFemmeParDivision donnee = (RatioFemmeParDivision) collection[xx];
message += «division = « + donnee.Division.ToString() + « «;
message += «ratio = « + donnee.RatioDeFemme.ToString() + RC;
}
x_infos_1.Text = message;
FIGURE 14.2

Copyright 2012 Patrice REY

1.2 - L’interface IEnumerable<T>

Une autre façon rapide de constituer une collection de données consiste à déclarer
CHAPITRE 14 □ La représentation graphique des données 441

une classe qui implémente l’interface IEnumerable<T>. L’interface IEnumerable<T>


expose l’énumérateur, qui prend en charge une itération simple sur une collection
d’un type spécifié. La définition de cette interface est:
namespace System.Collections.Generic {
public interface IEnumerable<out T> : IEnumerable {
IEnumerator<T> GetEnumerator();
}
}
La méthode GetEnumerator retourne un énumérateur qui itère au sein d’une
collection d’un type générique (<T>). Déclarons par exemple une classe
ChiffreAffaire avec une propriété JourDuMois qui définit la date d’un jour dans
un mois (de type int), et la propriété Montant qui définit le montant d’un chiffre
d’affaire (de type double).
public class ChiffreAffaire {
//propriete: la date
public int JourDuMois { get; set; }
//propriete: le montant
public double Montant { get; set; }
}
Maintenant on déclare une classe ChiffreAffaireCollectionF, qui implémente
l’interface IEnumerable<ChiffreAffaire>, pour constituer une collection de
données de type ChiffreAffaire.
public class ChiffreAffaireCollectionF : IEnumerable<ChiffreAffaire> {
//retourne le flux de la collection
public IEnumerator<ChiffreAffaire> GetEnumerator() {
yield return new ChiffreAffaire {
JourDuMois = 1,
Montant = 45.36
};
yield return new ChiffreAffaire {
JourDuMois = 5,
Montant = 60.25
};
yield return new ChiffreAffaire {
JourDuMois = 7,
Montant = 52
};
...
}
//retourne la collection
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() {
return ((IEnumerable<ChiffreAffaire>)this).GetEnumerator();
}}
442 Développez des applications Internet avec Silverlight 5
L’interface IEnumerable<T> indique que la classe prend en charge la méthode
GetEnumerator qui retourne une interface IEnumerator. A son tour, l’interface
IEnumerator fournit un mécanisme simple pour la recherche vers l’avant. La
propriété Current récupère l’élément de la collection actuellement énumérée.
La méthode MoveNext effectue un déplacement vers l’élément suivant de la
collection. La méthode Reset positionne l’énumérateur avant le premier élément
de la collection pour permettre à la méthode MoveNext d’être appelée et de
récupérer le premier élément de la collection. Le repère n°2 de la figure 14.3
illustre le résultat obtenu.
//
string message3 = «»;
IEnumerator<ChiffreAffaire> enumerateur = ca_femme.GetEnumerator();
while (enumerateur.MoveNext()) {
message3 += enumerateur.Current.ToString() + RC;
}
x_infos_3.Text = message3;
Une autre façon de parcourir une collection est d’utiliser l’instruction foreach.
Avec cette instruction, vous énumérez la collection et vous créez un objet pour
chaque élément de la collection. Ce modèle d’itération présente l’avantage de
pouvoir spécifier le type d’objet de la collection quand il est connu. Le repère n°1
de la figure 14.3 illustre le résultat obtenu.
string message2 = «»;
ChiffreAffaireCollectionF ca_femme=new ChiffreAffaireCollectionF ();
foreach (ChiffreAffaire element in ca_femme) {
message2 += element.ToString() + RC;
}
x_infos_2.Text = message2;

FIGURE 14.3

Copyright 2012 Patrice REY

1 2
CHAPITRE 14 □ La représentation graphique des données 443

2 - Les aires graphiques

La classe AreaSeries définit un type de représentation graphique des données. Il


s’agit d’une aire graphique qui est complètement personnalisable. La figure 14.4
visualise l’arbre d’héritage.
FIGURE 14.4

L’UserControl ControleAreaSeries.xaml (dans la solution GraphiqueEtDiagramme.


sln dans le dossier chapitre14) illustre la visualisation de données sous forme
d’aire graphique (figure 14.5). Le repère n°1 visualise l’affichage des données de la
collection RatioFemmeParDivision. Le repère n°2 visualise l’affichage des données
de deux collections ChiffreAffaireCollectionF et ChiffreAffaireCollectionH.
444 Développez des applications Internet avec Silverlight 5
FIGURE 14.5

Pour utiliser la classe AreaSeries, il faut ajouter une référence à l’assembly System.
Windows.Controls.DataVisualization.Toolkit dans le dossier Références. En XAML,
il faut ajouter une référence xmlns, nommée graphiqueToolkit, qui référence
l’espace de noms System.Windows.Controls.DataVisualization.Charting (xmlns:
graphiqueToolkit = « clr-namespace:System.Windows.Controls.DataVisualization.
Charting; assembly = System.Windows.Controls.DataVisualization.Toolkit »).
Pour accéder aux collections de données, il faut ajouter une référence xmlns,
nommée local, qui référence l’espace de noms du projet (xmlns:local = «clr-names
pace:GraphiqueEtDiagramme»).
Copyright 2012 Patrice REY

Pour accéder à la collection de données DonneesDivisionRatioFemme de la classe


RatioFemmeParDivision, on ajoute dans les ressources du Canvas une instance de
la classe repérée par la clé k_donnees_ratio_femme. On positionne sur le Canvas
un contrôle Chart dont sa propriété Title représente le titre du graphique. Sa
propriété Series représente les séries de données qui sont ajoutées au graphique
(<graphiqueToolkit:Chart.Series>).
CHAPITRE 14 □ La représentation graphique des données 445

Une aire graphique est représentée par le contrôle AreaSeries. Sa propriété Title
définit le nom de la série à afficher. Sa propriété ItemsSource représente une
collection de données. Ici on effectue une liaison par databinding à la collection
(retournée par la propriété statique DonneesDivisionRatioFemme). La propriété
IndependentValueBinding définit les valeurs à inscrire sur l’axe horizontal (qui sont
liées à la propriété Division par databinding). La propriété DependentValueBinding
définit les valeurs à inscrire sur l’axe vertical (qui sont liées à la propriété
RatioDeFemme par databinding). Le repère n°1 de la figure 14.5 visualise le résultat
obtenu.
<Canvas x:Name= «x_cnv_root» HorizontalAlignment= «Center»
VerticalAlignment= «Top» Height= «500» Width= «600»>
<Canvas.Resources>
<local:RatioFemmeParDivision x:Key= «k_donnees_ratio_femme»>
</local:RatioFemmeParDivision>
...
</Canvas.Resources>
<graphiqueToolkit:Chart Title= «Ratio des femmes par batiment» Width= «600»
Height= «233»>
<graphiqueToolkit:Chart.Series>
<graphiqueToolkit:AreaSeries
Title= «Ratio»
ItemsSource= «{Binding DonneesDivisionRatioFemme,
Source={StaticResource k_donnees_ratio_femme}}»
IndependentValueBinding= «{Binding Division}»
DependentValueBinding= «{Binding RatioDeFemme}»>
</graphiqueToolkit:AreaSeries>
</graphiqueToolkit:Chart.Series>
</graphiqueToolkit:Chart>
...
</Canvas>
Le deuxième graphique consiste à visualiser deux séries de données, par aire
graphique, sur le même graphique.
Pour accéder à la collection de données ChiffreAffaireCollectionH de la classe
ChiffreAffaireCollectionH, on ajoute dans les ressources du Canvas une instance
de la classe repérée par la clé k_donnees_ca_h. Pour accéder à la collection de
données ChiffreAffaireCollectionF de la classe ChiffreAffaireCollectionF, on ajoute
dans les ressources du Canvas une instance de la classe repérée par la clé k_
donnees_ca_f.
On positionne sur le Canvas un contrôle Chart dont sa propriété Title représente
le titre du graphique. Sa propriété Series représente les séries de données qui
sont ajoutées au graphique (<graphiqueToolkit:Chart.Series>). Comme nous avons
deux séries de données à représenter, nous ajouterons deux aires graphiques
446 Développez des applications Internet avec Silverlight 5
AreaSeries. La propriété ItemsSource pointe directement sur la ressource
statique k_donnees_ca_h pour l’un, et k_donnees_ca_f pour l’autre. La propriété
IndependentValueBinding permet l’affichage horizontalement des valeurs de la
propriété JourDuMois. La propriété DependentValueBinding permet l’affichage
verticalement des valeurs de la propriété Montant. Le repère n°2 de la figure 14.5
visualise le résultat obtenu.
<Canvas x:Name= «x_cnv_root» HorizontalAlignment= «Center»
VerticalAlignment= «Top» Height= «500» Width= «600»>
<Canvas.Resources>
...
<local:ChiffreAffaireCollectionH x:Key= «k_donnees_ca_h»>
</local:ChiffreAffaireCollectionH>
<local:ChiffreAffaireCollectionF x:Key= «k_donnees_ca_f»>
</local:ChiffreAffaireCollectionF>
</Canvas.Resources>
...
<graphiqueToolkit:Chart Title= «Chiffre affaire Homme et Femme» Width= «600»
Height= «233» Canvas.Top= «240» Canvas.Left= «0»>
<graphiqueToolkit:Chart.Series>
<graphiqueToolkit:AreaSeries
Title= «CA des H»
ItemsSource= «{StaticResource k_donnees_ca_h}»
IndependentValueBinding= «{Binding JourDuMois}»
DependentValueBinding= «{Binding Montant}» Name= «x_aire_2_h»>
</graphiqueToolkit:AreaSeries>
<graphiqueToolkit:AreaSeries
Title= «CA des F»
ItemsSource= «{StaticResource k_donnees_ca_f}»
IndependentValueBinding= «{Binding JourDuMois}»
DependentValueBinding= «{Binding Montant}» Name= «x_aire_2_f»>
</graphiqueToolkit:AreaSeries>
</graphiqueToolkit:Chart.Series>
</graphiqueToolkit:Chart>
</Canvas>

3 - Les graphiques à barres horizontales Copyright 2012 Patrice REY

La classe BarSeries permet de représenter des graphiques à barres horizontales. La


figure 14.6 montre l’arbre d’héritage de la classe.
L’UserControl ControleBarSeries.xaml (dans la solution GraphiqueEtDiagramme.
sln dans le dossier chapitre14) illustre la visualisation de données sous forme de
barres horizontales (figure 14.7). Le repère n°1 visualise l’affichage des données de
la collection CollectionDonnees de la classe PopulationVille. Le repère n°2 visualise
l’affichage de ces mêmes données mais avec une mise en forme plus poussée du
CHAPITRE 14 □ La représentation graphique des données 447

graphique.
FIGURE 14.6

FIGURE 14.7

2
448 Développez des applications Internet avec Silverlight 5
La classe PopulationVille représente le nombre d’habitants par ville. Sa propriété
NomVille définit le nom de la ville (de type string), et sa propriété NombreHabitant
définit le nombre des habitants dans une ville donnée (de type int). Sa propriété
statique CollectionDonnees retourne une collection d’objets PopulationVille.
public class PopulationVille {
//propriete: nom de la ville
public string NomVille { get; set; }
//propriete: quantite habitants
public int NombreHabitant { get; set; }
//constructeur
public PopulationVille() { }
//override affichage
public override string ToString() {
string aff = «»;
aff += «ville = « + NomVille + « «;
aff += «nombre habitants = « + NombreHabitant.ToString();
return aff;
}
//collection de donnees
public static ObjectCollection CollectionDonnees {
get {
ObjectCollection element = new ObjectCollection();
element.Add(new PopulationVille { NomVille = «Paris (75)»,
NombreHabitant = 2125851 });
element.Add(new PopulationVille { NomVille = «Marseille (13)»,
NombreHabitant = 797491 });
element.Add(new PopulationVille { NomVille = «Lyon (69)»,
NombreHabitant = 445274 });
element.Add(new PopulationVille { NomVille = «Toulouse (31)»,
NombreHabitant = 390401 });
return element;
}
}
}//end class
Le premier graphique x_graphique1, de type BarSeries, visualise les données de
la collection CollectionDonnees de la classe PopulationVille. La collection des
données est instanciée dans les ressources du Canvas et repérée par la clé k_
donnees_population_ville.
Copyright 2012 Patrice REY

Sur l’axe vertical, on affiche les villes (la propriété IndependentValueBinding liée à
la propriété NomVille) et sur l’axe horizontal, on affiche le nombre d’habitants (la
propriété DependentValueBinding liée à la propriété NombreHabitant). Le repère
n°1 de la figure 14.7 visualise le graphique obtenu dans sa version minimale.
<Canvas x:Name= «x_cnv_root» HorizontalAlignment= «Center»
VerticalAlignment= «Top» Height= «500» Width= «600»>
CHAPITRE 14 □ La représentation graphique des données 449

<Canvas.Resources>
<local:PopulationVille x:Key= «k_donnees_population_ville»>
</local:PopulationVille>
</Canvas.Resources>
<graphiqueToolkit:Chart Title= «Nombre d’habitants par villes» Width= «600»
Height= «233» Name= «x_graphique1»>
<graphiqueToolkit:Chart.Series>
<graphiqueToolkit:BarSeries
Title= «nombre habitants»
ItemsSource= «{Binding CollectionDonnees,
Source={StaticResource k_donnees_population_ville}}»
IndependentValueBinding= «{Binding NomVille}»
DependentValueBinding= «{Binding NombreHabitant}»>
</graphiqueToolkit:BarSeries>
</graphiqueToolkit:Chart.Series>
</graphiqueToolkit:Chart>
...
</Canvas>
Pour personnaliser les axes du graphique, il faut se référer à la propriété Axes de
Chart. L’objet CategorieAxis représente l’axe vertical. Sa propriété Title permet
de donner un nom à afficher, et sa propriété Orientation permet de définir
l’orientation d’écriture (valeur énumérée X ou Y). Sa propriété Location permet
de définir l’emplacement de l’étiquette (valeur énumérée Left, Top, Right, Bottom
ou Auto). La propriété booléenne ShowGridLines permet d’afficher ou de masquer
les lignes de repérage. L’objet LinearAxis représente l’axe horizontal. Les bornes
qui englobent les valeurs à visualiser sont contrôlées par les propriétés Minimum
et Maximum, la propriété Interval permettant de positionner des graduations de
ces valeurs sur l’axe. Les propriétés Title, Orientation, Location et ShowGridLines
sont identiques à celles de CategoryAxis. Le repère n°2 de la figure 14.7 visualise
le graphique obtenu dans sa version personnalisée.
<Canvas x:Name= «x_cnv_root» HorizontalAlignment= «Center»
VerticalAlignment= «Top» Height= «500» Width= «600»>
<Canvas.Resources>
<local:PopulationVille x:Key= «k_donnees_population_ville»>
</local:PopulationVille>
</Canvas.Resources>
...
<graphiqueToolkit:Chart Title= «Nombre d’habitants par villes» Width= «600»
Height= «233» Name= «x_graphique2» Canvas.Top= «236»>
<graphiqueToolkit:Chart.Series>
<graphiqueToolkit:BarSeries
Title= «nombre habitants»
ItemsSource= «{Binding CollectionDonnees,
Source={StaticResource k_donnees_population_ville}}»
450 Développez des applications Internet avec Silverlight 5
IndependentValueBinding= «{Binding NomVille}»
DependentValueBinding= «{Binding NombreHabitant}»>
</graphiqueToolkit:BarSeries>
</graphiqueToolkit:Chart.Series>
<graphiqueToolkit:Chart.Axes>
<graphiqueToolkit:CategoryAxis Title= «Villes» Orientation= «Y»
Location= «Left» FontFamily= «Verdana» FontSize= «14»
Background= «WhiteSmoke» FontStyle= «Italic» ShowGridLines= «True»>
</graphiqueToolkit:CategoryAxis>
<graphiqueToolkit:LinearAxis Minimum= «0» Maximum= «3000000»
Interval= «1000000» Orientation= «X» Location= «Bottom»
ShowGridLines= «True» FontFamily= «Verdana» FontSize= «12»
FontStyle= «Italic»></graphiqueToolkit:LinearAxis>
</graphiqueToolkit:Chart.Axes>
</graphiqueToolkit:Chart>
</Canvas>

4 - Les graphiques à barres verticales

La classe ColumnSeries permet de représenter des graphiques à barres verticales.


La figure 14.8 montre l’arbre d’héritage de la classe.
FIGURE 14.8

Copyright 2012 Patrice REY

L’UserControl ControleColumnSeries.xaml (dans la solution GraphiqueEtDiagramme.


sln dans le dossier chapitre14) illustre la visualisation de données sous forme de
barres verticales (figure 14.9).
Le repère n°1 visualise l’affichage des données de la collection CollectionDonnees
de la classe PopulationVille. Le repère n°2 visualise l’affichage de ces mêmes
données mais avec une mise en forme plus poussée du graphique. Le repère
CHAPITRE 14 □ La représentation graphique des données 451

n°3 visualise l’affichage des données de la collection CollectionDonnees de


la classe PopulationVille et de la collection CollectionDonnees de la classe
PopulationVilleAvant.
FIGURE 14.9

La classe PopulationVille représente le nombre d’habitants par ville. Sa propriété


NomVille définit le nom de la ville (de type string), et sa propriété NombreHabitant
452 Développez des applications Internet avec Silverlight 5
définit le nombre des habitants (de type int) dans une ville donnée. Sa propriété
statique CollectionDonnees retourne une collection d’objets PopulationVille. La
classe PopulationVilleAvant est identique à PopulationVille mais avec le nombre
d’habitants dix ans avant.
Le premier graphique x_graphique1, de type ColumnSeries, visualise les données
de la collection CollectionDonnees de la classe PopulationVille. La collection des
données est instanciée dans les ressources du Canvas et repérée par la clé k_
donnees_population_ville. Sur l’axe horizontal, on affiche les villes (la propriété
IndependentValueBinding liée à la propriété NomVille) et sur l’axe vertical, on
affiche le nombre d’habitants (la propriété DependentValueBinding liée à la
propriété NombreHabitant). Le repère n°1 de la figure 14.9 visualise le graphique
obtenu dans sa version minimale.
<Canvas.Resources>
<local:PopulationVille x:Key= «k_donnees_population_ville»>
</local:PopulationVille>
<local:PopulationVilleAvant x:Key= «k_donnees_population_ville_avant»>
</local:PopulationVilleAvant>
</Canvas.Resources>
<graphiqueToolkit:Chart Title= «Nombre d’habitants par villes» Width= «600»
Height= «233» Name= «x_graphique1»>
<graphiqueToolkit:Chart.Series>
<graphiqueToolkit:ColumnSeries
Title= «nombre habitants»
ItemsSource= «{Binding CollectionDonnees,
Source={StaticResource k_donnees_population_ville}}»
IndependentValueBinding= «{Binding NomVille}»
DependentValueBinding= «{Binding NombreHabitant}»>
</graphiqueToolkit:ColumnSeries>
</graphiqueToolkit:Chart.Series>
</graphiqueToolkit:Chart>
Le deuxième graphique x_graphique2, de type ColumnSeries, visualise les données
de la collection CollectionDonnees de la classe PopulationVille, avec une mise en
page personnalisée pour les axes (de façon identique au paragraphe précédent
avec BarSeries). Le repère n°2 de la figure 14.9 visualise le graphique obtenu dans
sa version personnalisée.
Copyright 2012 Patrice REY

<Canvas.Resources>
<local:PopulationVille x:Key= «k_donnees_population_ville»>
</local:PopulationVille>
<local:PopulationVilleAvant x:Key= «k_donnees_population_ville_avant»>
</local:PopulationVilleAvant>
</Canvas.Resources>
<graphiqueToolkit:Chart Title= «Nombre d’habitants par villes» Width= «600»
Height= «265» Name= «x_graphique2» Canvas.Top= «236»>
CHAPITRE 14 □ La représentation graphique des données 453

<graphiqueToolkit:Chart.Series>
<graphiqueToolkit:ColumnSeries
Title= «nombre habitants»
ItemsSource= «{Binding CollectionDonnees,
Source={StaticResource k_donnees_population_ville}}»
IndependentValueBinding= «{Binding NomVille}»
DependentValueBinding= «{Binding NombreHabitant}»>
</graphiqueToolkit:ColumnSeries>
</graphiqueToolkit:Chart.Series>
<graphiqueToolkit:Chart.Axes>
<graphiqueToolkit:CategoryAxis Title= «Villes» Orientation= «X»
Location= «Bottom» FontFamily= «Verdana» FontSize= «14»
Background= «WhiteSmoke» FontStyle= «Italic» ShowGridLines= «True»>
</graphiqueToolkit:CategoryAxis>
<graphiqueToolkit:LinearAxis Minimum= «0» Maximum= «3000000»
Interval= «1000000» Orientation= «Y» Location= «Left»
ShowGridLines= «True» FontFamily= «Verdana» FontSize= «12»
FontStyle= «Italic»></graphiqueToolkit:LinearAxis>
</graphiqueToolkit:Chart.Axes>
</graphiqueToolkit:Chart>
Le troisième graphique x_graphique3, de type ColumnSeries, visualise les données
de la collection CollectionDonnees de la classe PopulationVille et de la collection
CollectionDonnees de la classe PopulationVilleAvant, avec une mise en page
personnalisée pour les axes. Le repère n°3 de la figure 14.9 visualise le graphique
obtenu dans sa version personnalisée avec les deux séries de données.
<Canvas.Resources>
<local:PopulationVille x:Key= «k_donnees_population_ville»>
</local:PopulationVille>
<local:PopulationVilleAvant x:Key= «k_donnees_population_ville_avant»>
</local:PopulationVilleAvant>
</Canvas.Resources>
<graphiqueToolkit:Chart Title= «Nombre d’habitants par villes» Width= «600»
Height= «265» Name= «x_graphique2» Canvas.Top= «236»>
<graphiqueToolkit:Chart.Series>
<graphiqueToolkit:ColumnSeries
Title= «nombre habitants»
ItemsSource= «{Binding CollectionDonnees,
Source={StaticResource k_donnees_population_ville}}»
IndependentValueBinding= «{Binding NomVille}»
DependentValueBinding= «{Binding NombreHabitant}»>
</graphiqueToolkit:ColumnSeries>
<graphiqueToolkit:ColumnSeries
Title= «nombre habitants»
ItemsSource= «{Binding CollectionDonnees,
Source={StaticResource k_donnees_population_ville_avant}}»
IndependentValueBinding= «{Binding NomVille}»
DependentValueBinding= «{Binding NombreHabitant}»>
454 Développez des applications Internet avec Silverlight 5
</graphiqueToolkit:ColumnSeries>
</graphiqueToolkit:Chart.Series>
<graphiqueToolkit:Chart.Axes>
<graphiqueToolkit:CategoryAxis Title= «Villes» Orientation= «X»
Location= «Bottom» FontFamily= «Verdana» FontSize= «14»
Background= «WhiteSmoke» FontStyle= «Italic» ShowGridLines= «True»>
</graphiqueToolkit:CategoryAxis>
<graphiqueToolkit:LinearAxis Minimum= «0» Maximum= «3000000»
Interval= «1000000» Orientation= «Y» Location= «Left»
ShowGridLines= «True» FontFamily= «Verdana» FontSize= «12»
FontStyle= «Italic»></graphiqueToolkit:LinearAxis>
</graphiqueToolkit:Chart.Axes>
</graphiqueToolkit:Chart>

5 - Les graphiques à lignes

La classe LineSeries représente des graphiques dont les données sont reliées par
des segments. La figure 14.10 visualise l’arbre d’héritage de la classe.
FIGURE 14.10

L’UserControl ControleLineSeries.xaml (dans la solution GraphiqueEtDiagramme.


Copyright 2012 Patrice REY

sln dans le dossier chapitre14) illustre la visualisation de données sous forme


de segments connectés (figure 14.11). Le repère n°1 visualise l’affichage
des données de la collection DonneesDivisionRatioFemme de la classe
RatioFemmeParDivision. Le repère n°2 visualise l’affichage de deux séries de
données, DonneesDivisionRatioFemme et DonneesRatioAvant, avec une mise en
forme plus poussée du graphique.
CHAPITRE 14 □ La représentation graphique des données 455
FIGURE 14.11

Le principe de fonctionnement pour l’affichage des données est identique aux


autres formes de graphiques (BarSeries, ColumnSeries).
<!-- graphique 1 -->
<graphiqueToolkit:Chart Title= «Ratio des femmes par division» Width= «600»
Height= «233» Name= «x_graphique1»>
<graphiqueToolkit:Chart.Series>
<graphiqueToolkit:LineSeries
Title= «ratio femme»
ItemsSource= «{Binding DonneesDivisionRatioFemme,
Source={StaticResource k_donnees_ratio}}»
IndependentValueBinding= «{Binding Division}»
DependentValueBinding= «{Binding RatioDeFemme}»></
graphiqueToolkit:LineSeries>
</graphiqueToolkit:Chart.Series>
</graphiqueToolkit:Chart>
<!-- graphique 2 -->
<graphiqueToolkit:Chart Title= «Ratio des femmes par division» Width= «600»
Height= «265» Name= «x_graphique2» Canvas.Top= «236»>
<graphiqueToolkit:Chart.Series>
456 Développez des applications Internet avec Silverlight 5
<graphiqueToolkit:LineSeries
Title= «ratio femme»
ItemsSource= «{Binding DonneesDivisionRatioFemme,
Source={StaticResource k_donnees_ratio}}»
IndependentValueBinding= «{Binding Division}»
DependentValueBinding= «{Binding RatioDeFemme}»>
</graphiqueToolkit:LineSeries>
<graphiqueToolkit:LineSeries
Title= «ratio femme»
ItemsSource= «{Binding DonneesRatioAvant,
Source={StaticResource k_donnees_ratio}}»
IndependentValueBinding= «{Binding Division}»
DependentValueBinding= «{Binding RatioDeFemme}»>
</graphiqueToolkit:LineSeries>
</graphiqueToolkit:Chart.Series>
<graphiqueToolkit:Chart.Axes>
<graphiqueToolkit:CategoryAxis Title= «Division» Orientation= «X»
Location= «Bottom» FontFamily= «Verdana» FontSize= «14»
Background= «WhiteSmoke» FontStyle= «Italic»
ShowGridLines= «True»></graphiqueToolkit:CategoryAxis>
<graphiqueToolkit:LinearAxis Minimum= «0» Maximum= «10» Interval= «2»
Orientation= «Y» Location= «Left» ShowGridLines= «True»
FontFamily= «Verdana» FontSize= «12» FontStyle= «Italic»>
</graphiqueToolkit:LinearAxis>
</graphiqueToolkit:Chart.Axes>
</graphiqueToolkit:Chart>

6 - Les graphiques à bulles

La classe BubbleSeries représente le graphique à bulles. Les données sont


représentées par des bulles de tailles variables. La figure 14.12 visualise l’arbre
d’héritage.
L’UserControl ControleBubbleSeries.xaml (dans la solution GraphiqueEtDiagramme.
sln dans le dossier chapitre14) illustre la visualisation de données sous forme de
bulles (figure 14.13). Les données de la collection DonneesDivisionRatioFemme de
la classe RatioFemmeParDivision sont positionnées et représentées par des bulles
dont le diamètre est conditionné par une variable.
Copyright 2012 Patrice REY

Le principe de fonctionnement pour l’affichage des données est identique aux


autres formes de graphiques (BarSeries, ColumnSeries et LineSeries).
Le diamètre des bulles (propriété SizeValueBinding de BubbleSeries) peut
être calculé par un paramètre. Ici on choisit de lier cette taille à la propriété
RatioDeFemme.
CHAPITRE 14 □ La représentation graphique des données 457

FIGURE 14.12

FIGURE 14.13

<Canvas x:Name= «x_cnv_root» HorizontalAlignment= «Center»


VerticalAlignment= «Top» Height= «800» Width= «600»>
<Canvas.Resources>
<local:RatioFemmeParDivision x:Key= «k_donnees_ratio»>
</local:RatioFemmeParDivision>
</Canvas.Resources>
<!-- graphique 2 -->
<graphiqueToolkit:Chart Title= «Ratio des femmes par division» Width= «600»
Height= «265» Name= «x_graphique2» Canvas.Top= «0»>
458 Développez des applications Internet avec Silverlight 5
<graphiqueToolkit:Chart.Series>
<graphiqueToolkit:BubbleSeries
Title= «ratio femme»
ItemsSource= «{Binding DonneesDivisionRatioFemme,
Source={StaticResource k_donnees_ratio}}»
IndependentValueBinding= «{Binding Division}»
DependentValueBinding= «{Binding RatioDeFemme}»
SizeValueBinding= «{Binding RatioDeFemme}»>
</graphiqueToolkit:BubbleSeries>
</graphiqueToolkit:Chart.Series>
<graphiqueToolkit:Chart.Axes>
<graphiqueToolkit:CategoryAxis Title= «Division» Orientation= «X»
Location= «Bottom» FontFamily= «Verdana» FontSize= «14»
Background= «WhiteSmoke» FontStyle= «Italic» ShowGridLines= «True»>
</graphiqueToolkit:CategoryAxis>
<graphiqueToolkit:LinearAxis Minimum= «0» Maximum= «10» Interval= «2»
Orientation= «Y» Location= «Left» ShowGridLines= «True»
FontFamily= «Verdana» FontSize= «12» FontStyle= «Italic»>
</graphiqueToolkit:LinearAxis>
</graphiqueToolkit:Chart.Axes>
</graphiqueToolkit:Chart>
</Canvas>

7 - Les graphiques à secteurs

La classe PieSeries représente le graphique à secteur. Les données sont représentées


par des secteurs dont la taille est fonction de la donnée. La figure 14.14 visualise
l’arbre d’héritage.
L’UserControl ControlePieSeries.xaml (dans la solution GraphiqueEtDiagramme.
sln dans le dossier chapitre14) illustre la visualisation de données sous forme de
secteurs (figure 14.15). Les données de la collection CollectionDonnees de la classe
PopulationVille sont positionnées et représentées par des secteurs. Le repère
n°1 visualise le graphique dans son état par défaut, et le repère n°2 visualise le
graphique dans son état personnalisé pour le graphisme des secteurs.
L’affichage du graphique x_graphique1 (repère n°1 de la figure 14.15), de type
PieSeries, s’exécute de façon identique aux autres formes de graphiques (BarSeries,
Copyright 2012 Patrice REY

ColumnSeries, LineSeries).
<!-- graphique 1 -->
<graphiqueToolkit:Chart Title= «La population des villes» Width= «600»
Height= «265» Name= «x_graphique1» Canvas.Top= «0»>
<graphiqueToolkit:Chart.Series>
<graphiqueToolkit:PieSeries
ItemsSource= «{Binding CollectionDonnees,
Source={StaticResource k_donnees_population}}»
CHAPITRE 14 □ La représentation graphique des données 459

IndependentValueBinding= «{Binding NomVille}»


DependentValueBinding= «{Binding NombreHabitant}»>
</graphiqueToolkit:PieSeries>
</graphiqueToolkit:Chart.Series>
</graphiqueToolkit:Chart>
FIGURE 14.14

L’affichage du graphique x_graphique2 (repère n°2 de la figure 14.15), de type


PieSeries, s’exécute de façon identique aux autres formes de graphiques (BarSeries,
ColumnSeries, LineSeries). La seule différence est que l’on personnalise le style
des différents secteurs en le modifiant.
Pour accéder au ResourceDictionaryCollection il faut ajouter une référence
xmlns, nommée visualization, qui référence l’espace de noms System.Windows.
Controls.DataVisualization (xmlns:visualizationToolkit = «clr-namespace:System.
Windows.Controls.DataVisualization; assembly = System.Windows.Controls.
DataVisualization.Toolkit»). La propriété Palette de PieSeries contient une
collection ResourceDictionaryCollection composée de ResourceDictionary.
Chaque objet de cette collection permet de personnaliser le style des secteurs
dans leur ordre d’apparition. Les objets Style portent la clé DataPointStyle et cible
le type Control. Par l’intermédiaire d’objets Setter, le style d’un secteur peut être
460 Développez des applications Internet avec Silverlight 5
modifié par les propriétés Background et BorderBrush.
FIGURE 14.15

<!-- graphique 2 -->


<graphiqueToolkit:Chart Title= «La population des villes» Width= «600»
Height= «265» Name= «x_graphique2» Canvas.Top= «300»>
<graphiqueToolkit:Chart.Series>
<graphiqueToolkit:PieSeries
ItemsSource= «{Binding CollectionDonnees,
Source={StaticResource k_donnees_population}}»
Copyright 2012 Patrice REY

IndependentValueBinding= «{Binding NomVille}»


DependentValueBinding= «{Binding NombreHabitant}»>
<graphiqueToolkit:PieSeries.Palette>
<visualizationToolkit:ResourceDictionaryCollection>
<ResourceDictionary>
<Style x:Key= «DataPointStyle» TargetType= «Control»>
<Setter Property= «Background» Value= «DimGray»></Setter>
<Setter Property= «BorderBrush» Value= «Black»></Setter>
<Setter Property= «BorderThickness» Value= «2»></Setter>
CHAPITRE 14 □ La représentation graphique des données 461

</Style>
</ResourceDictionary>
<ResourceDictionary>
<Style x:Key= «DataPointStyle» TargetType= «Control»>
<Setter Property= «Background» Value= «LightGray»></Setter>
<Setter Property= «BorderBrush» Value= «Black»></Setter>
<Setter Property= «BorderThickness» Value= «2»></Setter>
</Style>
</ResourceDictionary>
<ResourceDictionary>
<Style x:Key= «DataPointStyle» TargetType= «Control»>
<Setter Property= «Background» Value= «GhostWhite»></Setter>
<Setter Property= «BorderBrush» Value= «Black»></Setter>
<Setter Property= «BorderThickness» Value= «2»></Setter>
</Style>
</ResourceDictionary>
<ResourceDictionary>
<Style x:Key= «DataPointStyle» TargetType= «Control»>
<Setter Property= «Background» Value= «WhiteSmoke»></Setter>
<Setter Property= «BorderBrush» Value= «Black»></Setter>
<Setter Property= «BorderThickness» Value= «2»></Setter>
</Style>
</ResourceDictionary>
</visualizationToolkit:ResourceDictionaryCollection>
</graphiqueToolkit:PieSeries.Palette>
</graphiqueToolkit:PieSeries>
</graphiqueToolkit:Chart.Series>
</graphiqueToolkit:Chart>
C H A P I T R E 15 DANS CE CHAPITRE
• Le contrôle

La modélisation 3D
DrawingSurface
• GraphicsDeviceManager
• VertexBuffer,
VertexPositionColor,
VertexPositionTexture
• Color, Vector3, Matrix,
Vector2
• La projection perspective
et la projection parallèle
• La caméra
La modélisation 3D constitue la grande nouveauté • GraphicsDevice et
RasterizerState
de Silverlight dans sa version 5.
• Le rendu solide et filaire
Le modèle 3D de Silverlight est basé sur le • Dupliquer les objets 3D
framework XNA de la plateforme Windows. Le • Les mouvements des
objets 3D
rendu 3D de Silverlight est un rendu avec une
accélération matérielle (hardware) et non pas un
rendu logiciel (software).
Une grande partie de XNA est implémentée pour
Silverlight, ce qui induit un apport 3D puissant et
réaliste.
Nous allons voir dans ce chapitre comment utiliser
la puissance de XNA pour programmer la 3D au
travers d’objets basiques, tout cela fonctionnant
dans le navigateur avec le runtime Silverlight 5
minimum.
Nous aborderons l’utilisation et la personnalisation
du contrôle DrawingSurface qui émule la surface
de rendu d’un contenu 3D. Nous verrons comment
modéliser des objets 3D, composés de triangles,
avec des faces aux couleurs unies et des faces
texturées. Nous verrons comment effectuer un
rendu avec une caméra, une projection, un éclairage
et un positionnement des objets. Nous verrons
également la duplication et les mouvements des
objets 3D.
464 Développez des applications Internet avec Silverlight 5
Le modèle 3D de Silverlight n’est pas basé sur le modèle 3D de WPF. Avant d’aller
plus loin, vous devez être informé des points suivants:
• l’accélération matérielle: le rendu 3D avec Silverlight n’est pas un rendu logiciel
mais un rendu matériel; si l’accélération matérielle n’est pas activée, ou bien si
elle n’est pas disponible (cas des cartes vidéo obsolètes), vous serez incapable
de visualiser une scène 3D.
• une programmation par code: contrairement à WPF 3D qui permet de visualiser
des objets 3D avec du XAML, avec Silverlight, vous devez réaliser vos objets par
code de programmation et vous avez la liberté de créer des passerelles pour
afficher vos objets en XAML.
• le support de XNA: Silverlight 3D se base sur une partie du framework XNA
(qui est utilisé pour créer des jeux sur Xbox et Windows Phone); une grande
partie de XNA est utilisée pour Silverlight mais avec un point important qu’est
le support du shader model 2.0 (et non pas le shader model 3.0 actuel).
• la présence de Windows: XNA est un framework qui est implémenté sur la
plateforme Windows; Silverlight 3D ne fonctionne donc pas avec la plateforme
Mac.
Pour modéliser en 3D avec Silverlight, il faut ajouter un certain nombre de
références à des assemblys (dans le dossier Références) qui sont:
• System.Windows.Xna.dll: cet assembly contient le contrôle DrawingSurface et
le GraphicsDeviceManager.
• Microsoft.Xna.Framework.dll: cet assembly définit la version des structures
Color et Rectangle qui sont fournies par XNA, ainsi que le support de l’audio.
• Microsoft.Xna.Framework.Graphics.dll: cet assembly définit un grand nombre
de type d’objets, dont le GraphicsDevice, qui sont nécessaires au dessin 3D
dans le contrôle DrawingSurface.
• Microsoft.Xna.Framework.Graphics.Extensions.dll: cet assembly définit
l’essentiel du rendu 3D avec notamment la classe BasicEffect, qui sert de
passerelle pour un grand nombre de détails pour le dessin 3D et pour l’affichage
du pixel shader que vous n’aurez pas à écrire.
Copyright 2012 Patrice REY

• Microsoft.Xna.Framework.Graphics.Shaders.dll: cet assembly contient les


classes PixelShader et VertexShader qui vous permettent de charger vos
propres shader, compilés avec le langage HLSL (High Level Shader Language).
• Microsoft.Xna.Framework.Math.dll: cet assembly contient les bases pour le
calcul en 3D avec les points et les matrices.
La figure 15.1 visualise les références aux assemblys à ajouter dans le dossier
Références.
CHAPITRE 15 □ La modélisation 3D 465
FIGURE 15.1

1 - Ma première scène 3D

Nous allons réaliser une première scène 3D complète pour voir la démarche à
effectuer pour visualiser un contenu 3D. L’UserControl MaPremiereScene.xaml
(dans le projet Modelisation3d.sln dans le dossier chapitre15) illustre cet exemple.
Le support de la 3D avec Silverlight nécessite l’accélération matérielle. Sans elle,
aucun affichage 3D ne peut être réalisé. Pour activer l’accélération matérielle, il
faut ajouter un paramètre dans la section du plugin Silverlight dans la page de
test de l’application. Le paramètre enableGPUAcceleration doit être fixé à la valeur
true.
<div id= «silverlightControlHost»>
<object data= «data:application/x-silverlight-2,»
type= «application/x-silverlight-2» width= «100%» height= «100%»>
<param name= «source» value= «ClientBin/Modelisation3d.xap»/>
<param name= «onError» value= «onSilverlightError» />
<param name= «background» value= «white» />
<param name= «minRuntimeVersion» value= «5.0.61118.0» />
<param name= «autoUpgrade» value= «true» />
<param name= «enableGPUAcceleration» value= «true» />
</object>
</div>
L’application Silverlight est bloquée par défaut dès lors qu’il y a une utilisation de la
carte graphique demandée. Cela est fait pour éviter des attaques par modification
de code.
En premier, il faut informer que l’application Silverlight nécessite des privilèges
élevés en cas d’exécution dans le navigateur. Comme le visualise la figure 15.2,
dans les propriétés du projet, il faut cocher la case permettant l’autorisation de ces
privilèges élevés.
466 Développez des applications Internet avec Silverlight 5
FIGURE 15.2

Le GraphicsDeviceManager traite la configuration et la gestion du périphérique


graphique. La méthode statique GraphicsDeviceManager.Current obtient une
instance du GraphicsDeviceManager courant. Sa propriété RenderMode indique
le type de rendu en cours (valeur énumérée RenderMode.Unavailable et
RenderMode.Hardware). Sa propriété RenderModeReason donne une explication
suite au RenderMode par une valeur énumérée:
• RenderModeReason.SecurityBlocked qui indique que l’accélération matérielle
n’est pas encore autorisée.
• RenderModeReason.GPUAccelerationDisabled qui indique que le paramètre
Copyright 2012 Patrice REY

enableGPUAcceleration dans les paramètres du plugin, dans la page de test,


est absent, ou bien qu’il est présent avec une valeur fixée à false.
• RenderModeReason.Not3DCapable qui indique que la carte vidéo ne supporte
pas la 3D.
• RenderModeReason.TemporarilyUnavailable qui indique que la carte vidéo a
un problème de driver.
CHAPITRE 15 □ La modélisation 3D 467

//evenement loaded
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
GraphicsDeviceManager gdm = GraphicsDeviceManager.Current;
if (gdm.RenderMode == RenderMode.Unavailable) {
x_listbox.SelectedIndex = -1;
string code_erreur = string.Empty;
code_erreur += «code erreur -> (« + gdm.RenderMode.ToString() + « - «;
code_erreur += gdm.RenderModeReason.ToString() + «)» + RC + RC;
switch (gdm.RenderModeReason) {
case RenderModeReason.SecurityBlocked:
code_erreur += «L’accélération matérielle n’est pas activée pour des
raisons de « + «sécurité. La procédure à suivre est la suivante:» + RC;
code_erreur += «1 - Faites un clic droit puis choisir Silverlight»
+ RC;
code_erreur += «2 - Dans l’onglet Autorisations, sélectionnez la ligne
correspondante et choisissez Autoriser» + RC;
break;
case RenderModeReason.GPUAccelerationDisabled:
code_erreur = «Erreur de développement! Le paramètre
enableGPUAcceleration n’est pas présent « +
«dans les paramètres du plugin avec la valeur fixée à true» + RC;
break;
case RenderModeReason.Not3DCapable:
code_erreur = «Votre carte vidéo ne supporte pas la 3D»;
break;
case RenderModeReason.TemporarilyUnavailable:
code_erreur = «Votre driver de la carte vidéo est obsolète»;
break;
}
x_infos_3d.Text = code_erreur;
} else {
v_3d_support = true;
x_listbox.SelectedIndex = 0;
}
}
En gérant ces alertes, on peut indiquer à l’utilisateur la démarche éventuelle à faire
pour résoudre le problème. Le plus souvent, c’est le problème de l’autorisation de
l’accélération matérielle qui n’est pas réalisée.
La figure 15.3 illustre le processus de cette autorisation pour pouvoir visualiser un
contenu 3D:
• une alerte affiche le problème de l’autorisation de l’accélération matérielle non
activée (repère n°1).
• un clic droit permet de choisir Silverlight dans le sous-menu (repère n°2).
• l’onglet Autorisations doit être sélectionné (repère n°3).
• il faut sélectionner la ligne correspondant à l’autorisation à donner, puis il faut
cliquer sur le bouton Autoriser (repère n°4) puis sur OK.
468 Développez des applications Internet avec Silverlight 5
• il ne reste plus qu’à actualiser la page par la touche F5 et le contenu 3D peut
alors être visualisé (repère n°5).
FIGURE 15.3
1 2

3 4

5
Copyright 2012 Patrice REY

Dans le fichier MaPremiereScene.xaml, on positionne sur le Canvas x_cnv_root un


contrôle Border qui contient comme unique enfant un contrôle DrawingSurface
x_scene_3d.
<Canvas x:Name= «x_cnv_root» HorizontalAlignment= «Center»
VerticalAlignment= «Top» Height= «500» Width= «600»>
<Border BorderThickness= «2» Width= «600» Height= «430» BorderBrush= «Black»
Canvas.Top= «0»>
CHAPITRE 15 □ La modélisation 3D 469

<DrawingSurface Width= «577» Height= «420» Name= «x_scene_3d»


Draw= «x_scene_3d_Draw»></DrawingSurface>
</Border>
</Canvas>
Le contrôle DrawingSurface définit une surface à l’intérieur de laquelle un contenu
3D peut être composé et être rendu. Un DrawingSurface est un contrôle, il hérite
de FrameworkElement. Sa propriété CompositionMode permet de définir le mode
de composition. L’événement Draw est émis lorsque le contrôle doit être dessiné.
La méthode Invalidate permet d’invalider le contrôle en le forçant à être redessiné
et en appelant un événement Draw. La figure 15.4 illustre l’arbre d’héritage de
DrawingSurface.
FIGURE 15.4

Pour dessiner un contenu 3D à l’intérieur de x_scene_3d, il faut ajouter un


gestionnaire pour l’événement Draw, intitulé x_scene_3d_Draw.
Silverlight adopte les conventions de XNA en utilisant le système de coordonnées
dit de la main droite. Comme l’illustre la figure 15.5, l’axe X est orienté positif en
allant du côté droit, l’axe Y est orienté positif en allant vers le haut, et l’axe Z est
orienté positif en allant vers l’avant.
Les objets 3D sont modelés avec des triangles. En effet, le triangle constitue la
figure la plus simple avec laquelle on peut réaliser des figures complexes qui
contiennent des milliers de triangles et plus. La figure 15.6 illustre la construction
d’une sphère à base de trapèzes constitués de deux triangles.
470 Développez des applications Internet avec Silverlight 5
FIGURE 15.5 Y

FIGURE 15.6

Dans l’espace 3D, un sommet représente un point dans l’espace 3D. Un triangle
possède 3 coins et on dira que ses coins sont des sommets. Un triangle est ainsi
repéré dans l’espace 3D par 3 sommets.
Copyright 2012 Patrice REY

Dans l’espace de noms Microsoft.Xna.Framework.Graphics, la classe VertexBuffer


représente une liste de sommets 3D qui est envoyée comme un flux au dispositif
d’affichage pour un rendu. On déclare une variable v_buffer_sommet, de type
VertexBuffer, qui stockera les sommets à afficher.
//stocke les coins de tous les triangles
private VertexBuffer v_buffer_sommet;
CHAPITRE 15 □ La modélisation 3D 471

La méthode Contenu3D va générer le contenu 3D à afficher. Nous allons commencer


par afficher un triangle qui représente l’élément basique de la modélisation 3D. Un
point dans le système de coordonnées 3D est représenté par une structure de type
Vector3. Une structure Vector3 définit un vecteur avec 3 composantes: le champ
X représente la coordonnée sur l’axe X, le champ Y représente la coordonnée sur
l’axe Y, et le champ Z représente la coordonnée sur l’axe Z. Un certain nombre de
propriétés statiques sont couramment utilisées:
• Vector3.UnitX qui définit le vecteur unitaire (1,0,0).
• Vector3.UnitY qui définit le vecteur unitaire (0,1,0).
• Vector3.UnitZ qui définit le vecteur unitaire (0,0,1).
• Vector3.Zero qui définit le vecteur nul (0,0,0).
• Vector3.Up qui définit le vecteur dirigé vers le haut (0,1,0).
• Vector3.Down qui définit le vecteur dirigé vers le bas (0,-1,0).
• Vector3.Right qui définit le vecteur dirigé vers la droite (1,0,0).
• Vector3.Left qui définit le vecteur dirigé vers la gauche (-1,0,0).
• Vector3.One qui définit le vecteur avec ses composantes fixées à une unité
(1,1,1).
Comme l’illustre la figure 15.7, on choisit de générer un triangle dont les sommets
sont tr_haut(0,1,0), tr_gauche(-1,0,0) et tr_droit(1,0,0).
FIGURE 15.7 Y

(0,1,0)

X
(-1,0,0) (1,0,0)

//creation d’un contenu 3d


private void Contenu3D() {
//coordonnees des sommets du triangle
Vector3 tr_haut = new Vector3(0, 1, 0);
Vector3 tr_gauche = new Vector3(-1, 0, 0);
472 Développez des applications Internet avec Silverlight 5
Vector3 tr_droit = new Vector3(1, 0, 0);
...
}
Le rendu 3D est réalisé par un pixel shader, qui est un petit programme en langage
HLSL qui est exécuté pour définir la couleur et l’opacité de chaque pixel. Il faut
définir une couleur pour chaque sommet. Si la couleur des sommets est unique,
le triangle aura une couleur unie. Si les sommets ont des couleurs différentes, le
pixel shader fera une interpolation de couleur pour les pixels qui composent le
triangle. On définit trois structures Color pour la couleur des 3 sommets (attention,
la structure Color appartient à l’espace de noms Microsoft.Xna.Framework). La
structure Color est initialisée en passant les composantes R (rouge), G (verte), B
(bleu), et accessoirement A (pour la transparence).
//creation d’un contenu 3d
private void Contenu3D() {
...
//couleurs
Color color1 = Color.Black;
Color color2 = new Color(169, 169, 169);
Color color3 = new Color(245, 245, 245);
...
}
La structure VertexPositionColor permet d’associer une couleur à un sommet. On
définit un tableau composé de structures VertexPositionColor, qui associent les
sommets à une couleur.
//creation d’un contenu 3d
private void Contenu3D() {
//coordonnees des sommets du triangle
Vector3 tr_haut = new Vector3(0, 1, 0);
Vector3 tr_gauche = new Vector3(-1, 0, 0);
Vector3 tr_droit = new Vector3(1, 0, 0);
//couleurs
Color color1 = Color.Black;
Color color2 = new Color(169, 169, 169);
Color color3 = new Color(245, 245, 245);
//association d’une couleur a un sommet
Copyright 2012 Patrice REY

VertexPositionColor[] vertices = new VertexPositionColor[3];


vertices[0] = new VertexPositionColor(tr_gauche, color1);
vertices[1] = new VertexPositionColor(tr_haut, color3);
vertices[2] = new VertexPositionColor(tr_droit, color2);
...
}
Le flux v_buffer_sommet est instancié en recevant en paramètre le périphérique
d’affichage (GraphicsDeviceManager.Current.GraphicsDevice), la déclaration du
CHAPITRE 15 □ La modélisation 3D 473

type des sommets, le nombre de sommets et le mode d’écriture. La méthode


SetData écrit les données.
//creation d’un contenu 3d
private void Contenu3D() {
...
//association d’une couleur a un sommet
VertexPositionColor[] vertices = new VertexPositionColor[3];
vertices[0] = new VertexPositionColor(tr_gauche, color1);
vertices[1] = new VertexPositionColor(tr_haut, color3);
vertices[2] = new VertexPositionColor(tr_droit, color2);
//initialisation du buffer contenant les sommets
GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
v_buffer_sommet = new VertexBuffer(device, typeof(VertexPositionColor),
vertices.Length, BufferUsage.WriteOnly);
v_buffer_sommet.SetData(0, vertices, 0, vertices.Length, 0);
...
}
Pour pouvoir obtenir un rendu 2D d’une scène 3D, il faut positionner une caméra
et l’orienter dans la bonne direction. Positionner une caméra est relativement
facile. Cela consiste à suivre une démarche en 3 points. Le premier point consiste
à positionner la caméra dans l’espace 3D. Le deuxième point consiste à orienter
la caméra pour qu’elle fixe une position particulière. Le troisième point consiste à
donner l’orientation de la rotation de la caméra pour permettre de fixer l’angle de
vue. La méthode statique CreateLookAt de la structure Matrix permet de créer une
caméra (figure 15.8) que l’on positionne en (0,0,2), qui est orientée vers l’origine
(0,0,0), et dont la direction du haut est (0,1,0).
FIGURE 15.8
Y Y

(0,1,0)

(0,1,0)
direction
(-1,0,0) (1,0,0) haut
X X

(0,0,0) cible

(0,0,2) position (0,0,2) position

Z Z
474 Développez des applications Internet avec Silverlight 5
//creation d’un contenu 3d
private void Contenu3D() {
...
//configuration de la camera
Matrix view = Matrix.CreateLookAt(new Vector3(0, 0, 2), Vector3.Zero,
new Vector3(0, 1, 0));
...
}
Il existe deux types de projection: la projection perspective (figures 15.9, 15.10
et 15.11) et la projection parallèle (figures 15.12 et 15.13). Une projection est
caractérisée par un volume d’observation qui est la zone de l’espace comprise
entre le plan de projection (plan de près) et le plan de loin. Tous les objets qui se
trouvent dans le volume d’observation feront l’objet d’un rendu.
volume
FIGURE 15.9 d’observation
plan de
projection

haut

Y gauche

Z
bas droit
X
centre de
projection plan de loin
(caméra) plan de
près
volume
FIGURE 15.10 d’observation
plan de
projection

hauteur
Copyright 2012 Patrice REY

Y
largeur
Z
angle de
X vue
centre de plan de loin
projection
(caméra)
CHAPITRE 15 □ La modélisation 3D 475
FIGURE 15.11 volume
d’observation
direction du
haut (up
direction) plan de
projection direction du
regard (look
Y direction)

Z
angle de
vue
centre de
projection plan de loin
(caméra) X

FIGURE 15.12 plan de


projection
haut

gauche
Z

X droit
centre de bas
projection à
l’infini (caméra)
plan de volume
près d’observation plan de
loin
FIGURE 15.13 plan de
projection
hauteur

X largeur
centre de
projection à
l’infini (caméra)
plan de volume
près d’observation plan de
loin
476 Développez des applications Internet avec Silverlight 5
La méthode statique Matrix.CreatePerspectiveFieldOfView permet de créer une
projection de type perspective. Elle reçoit en paramètre l’angle de vue (en radians),
le ratio d’aspect concernant le rapport de la largeur par la hauteur, la distance
entre la caméra et le plan de près, et la distance entre la caméra et le plan de loin.
La classe MathHelper contient des champs statiques et des méthodes statiques
pratiques pour les données demandées. Par exemple un angle de vue de 45 degrés
sera obtenu par le champ statique MathHelper.PiOver4. Pour obtenir la projection
de la vue, il suffit de multiplier la vue par la projection.
//creation d’un contenu 3d
private void Contenu3D() {
...
//configuration de la camera
Matrix view = Matrix.CreateLookAt(new Vector3(0, 0, 2), Vector3.Zero,
new Vector3(0, 1, 0));
Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
1.33f, 1, 10);
Matrix viewProjection = view * projection;
...
}
Pour obtenir un effet de rendu basique, on utilise un objet BasicEffect. Il est
instancié en lui passant en paramètre le périphérique d’affichage. Sa propriété
View reçoit la vue calculée. Sa propriété Projection reçoit la projection de la vue
(qui est fonction de la vue et du type de projection, perspective ou parallèle). La
propriété VertexColorEnabled active la coloration des pixels pour le rendu.
//creation d’un contenu 3d
private void Contenu3D() {
...
//configuration de la camera
Matrix view = Matrix.CreateLookAt(new Vector3(0, 0, 2), Vector3.Zero,
new Vector3(0, 1, 0));
Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
1.33f, 1, 10);
Matrix viewProjection = view * projection;
//initialisation des effets
v_effect = new BasicEffect(device);
Copyright 2012 Patrice REY

v_effect.World = Matrix.Identity;
v_effect.View = view;
v_effect.Projection = viewProjection;
v_effect.VertexColorEnabled = true;
}
Le gestionnaire de l’événement Draw permet de dessiner le contenu 3D dans
le contrôle DrawingSurface. La démarche consiste à récupérer le périphérique
d’affichage courant (GraphicsDeviceManager.Current.GraphicsDevice).
CHAPITRE 15 □ La modélisation 3D 477

La méthode Clear vide la mémoire tampon (buffer) en le remplissant d’une couleur.


La méthode SetVertexBuffer envoie le flux contenant les sommets au périphérique
d’affichage. Une boucle foreach permet d’effectuer les passes de rendu pour
l’effet de rendu basique utilisé ici qui est BasicEffect. La méthode Apply démarre
la passe de rendu. La méthode DrawPrimitives effectue le rendu d’une séquence
de primitives géométriques non indexées selon un type de flux de données. La
méthode InvalidateSurface provoque un nouvel appel de l’événement Draw. Si
on bouge la position de la caméra et que la méthode InvalidateSurface n’est pas
invoquée, on ne verra pas le résultat obtenu. La figure 15.14 illustre le résultat
obtenu pour notre triangle. Nous avons bien un triangle avec un rendu par pixel
de couleur.
FIGURE 15.14

//evenement draw de x_scene_3d


private void x_scene_3d_Draw(object sender, DrawEventArgs e) {
GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
device.Clear(new Color(255,255,255));
device.SetVertexBuffer(v_buffer_sommet);
foreach (EffectPass passe in v_effect.CurrentTechnique.Passes) {
passe.Apply();
device.DrawPrimitives(PrimitiveType.TriangleList, 0,
478 Développez des applications Internet avec Silverlight 5
v_buffer_sommet.VertexCount / 3);
}
e.InvalidateSurface();
}
Quand on a ajouté au tableau vertices des structures VertexPositionColor, on a
utilisé un sens d’entrée des structures qui était celui du sens des aiguilles d’une
montre. On a ajouté en premier le sommet tr_droit, puis le sommet tr_haut, et
enfin le sommet tr_gauche. Par défaut c’est le sens contraire des aiguilles d’une
montre qui est utilisé.
Si on avait inséré les structures VertexPositionColor pour les sommets avec le sens
des aiguilles d’une montre, on aurait eu aucun rendu du triangle.
//creation d’un contenu 3d
private void Contenu3D() {
...
//association d’une couleur a un sommet
VertexPositionColor[] vertices = new VertexPositionColor[3];
vertices[0] = new VertexPositionColor(tr_droit, color1);
vertices[1] = new VertexPositionColor(tr_haut, color3);
vertices[2] = new VertexPositionColor(tr_gauche, color2);
...
}
La propriété RasterizerState de GraphicsDevice permet d’obtenir l’objet
RasterizerState pour effectuer des réglages. Cet objet RasterizerState expose des
propriétés dont:
• la propriété CullMode qui définit si l’on souhaite différencier une face avant et une
face arrière pour un triangle de base; la valeur énumérée CullMode.CullCounter
ClockwiseFace indique le sens des aiguilles d’une montre pour différencier une
face avant d’une face arrière; la valeur énumérée CullMode.CullClockwiseFace
pour le sens contraire des aiguilles d’une montre (valeur par défaut); la valeur
énumérée CullMode.None pour indiquer aucun sens.
• la propriété FillMode qui définit le mode de rendu d’un triangle sous une forme
pleine (valeur énumérée FillMode.Solid, valeur par défaut) ou sous une forme
filaire (FillMode.WireFrame).
Copyright 2012 Patrice REY

Pour notre triangle, si on passe les sommets dans le sens des aiguilles d’une
montre pour différencier une face avant d’une face arrière, il faut instancier un
nouvel objet RasterizerState dont la propriété CullMode est fixée à CullMode.
CullCounterClockwiseFace. Et si l’on veut un rendu sous une forme solide, il faut
fixer la propriété FillMode à FillMode.Solid. Le repère n°1 de la figure 15.15 illustre
le résultat obtenu. Si l’on veut un rendu filaire, on fixe la propriété FillMode à
CHAPITRE 15 □ La modélisation 3D 479

FillMode.WireFrame. Le repère n°2 de la figure 15.15 illustre le résultat obtenu.


FIGURE 15.15
1 2

device.RasterizerState = device.RasterizerState =
new RasterizerState() { new RasterizerState() {
CullMode = CullMode =
CullMode.CullCounterClockwiseFace, CullMode.CullCounterClockwiseFace,
FillMode = FillMode.Solid FillMode = FillMode.Wireframe
}; };

//creation d’un contenu 3d


private void Contenu3D() {
...
vertices[0] = new VertexPositionColor(tr_gauche, color1);
vertices[1] = new VertexPositionColor(tr_haut, color3);
vertices[2] = new VertexPositionColor(tr_droit, color2);
...
}
//evenement draw de x_scene_3d
private void x_scene_3d_Draw(object sender, DrawEventArgs e) {
...
device.RasterizerState = new RasterizerState() {
CullMode = CullMode.CullCounterClockwiseFace,
FillMode = FillMode.Solid
};
...
}
//creation d’un contenu 3d
private void Contenu3D() {
...
vertices[0] = new VertexPositionColor(tr_gauche, color1);
vertices[1] = new VertexPositionColor(tr_haut, color3);
vertices[2] = new VertexPositionColor(tr_droit, color2);
...
}
//evenement draw de x_scene_3d
480 Développez des applications Internet avec Silverlight 5
private void x_scene_3d_Draw(object sender, DrawEventArgs e) {
...
device.RasterizerState = new RasterizerState() {
CullMode = CullMode.CullCounterClockwiseFace,
FillMode = FillMode.WireFrame
};
...
}

2 - Modéliser un cube

Nous allons réaliser une scène 3D dans laquelle un cube est modélisé (figure
15.16). Les faces du cube ont des couleurs unies différentes pour se repérer plus
facilement au départ. Des boutons permettent de visualiser le cube en faisant
tourner la caméra autour de l’axe Y et de l’axe X. L’UserControl UnCube.xaml (dans
le projet Modelisation3d.sln dans le dossier chapitre15) illustre cet exemple.
FIGURE 15.16

Copyright 2012 Patrice REY


CHAPITRE 15 □ La modélisation 3D 481

Un cube est composé de 6 faces et chaque face est composée de 2 triangles. Un


cube compte 8 sommets. La figure 15.17 visualise le cube avec ses sommets et ses
faces.
FIGURE 15.17

G (0,1,0) F (1,1,0)
face
face dessus
gauche

G (0,1,0) B (0,1,1) Y B (0,1,1) C (1,1,1)

H (0,0,0) A (0,0,1) G (0,1,0) F (1,1,0) face


arrière

F (1,1,0) G (0,1,0)

B (0,1,1) C (1,1,1)
E (1,0,0) H (0,0,0)

H (0,0,0) E(1,0,0)
X

A (0,0,1) D (1,0,1) C (1,1,1) F (1,1,0)

face
droite

Z
D (1,0,1) E (1,0,0)
A (0,0,1) D (1,0,1)
B (0,1,1) C (1,1,1)
face
face
dessous
avant

A (0,0,1) D (1,0,1) H (0,0,0) E (1,0,0)


482 Développez des applications Internet avec Silverlight 5
Nous allons réaliser des faces avec des couleurs unies. Pour cela on instancie trois
structures de type Color intitulées coul_noire, coul_gris_fonce et coul_gris_clair.
//couleurs
Color coul_noire = Color.Black;
Color coul_gris_fonce = new Color(169, 169, 169);
Color coul_gris_clair = new Color(200, 200, 200);
Les 8 sommets sont repérés par des structures Vector3 intitulées pt_a, pt_b, pt_c,
pt_d, pt_e, pt_f, pt_g et pt_h.
//8 sommets
Vector3 pt_a = new Vector3(0, 0, 1);
Vector3 pt_b = new Vector3(0, 1, 1);
Vector3 pt_c = new Vector3(1, 1, 1);
Vector3 pt_d = new Vector3(1, 0, 1);
Vector3 pt_e = new Vector3(1, 0, 0);
Vector3 pt_f = new Vector3(1, 1, 0);
Vector3 pt_g = new Vector3(0, 1, 0);
Vector3 pt_h = new Vector3(0, 0, 0);
Comme l’on a 6 faces qui sont composées de 2 triangles, nous allons générer un
tableau de 36 associations de structures VertexPositionColor.
//association d’une couleur a un sommet
VertexPositionColor[] vertices = new VertexPositionColor[36];
La face avant est repérée par les points A, B, C et D. On utilise le sens des aiguilles
d’une montre pour différencier la face avant de la face arrière (propriété CullMode
de l’objet Rasterizer fixée à CullMode.CullCounterClockwiseFace). D’après la figure
15.17 pour la face avant, nous avons le triangle ABC et le triangle CDA. Nous
entrons donc les sommets de ces triangles dans cet ordre.
//face avant abcd
vertices[0] = new VertexPositionColor(pt_a, coul_noire);
vertices[1] = new VertexPositionColor(pt_b, coul_noire);
vertices[2] = new VertexPositionColor(pt_c, coul_noire);
vertices[3] = new VertexPositionColor(pt_c, coul_noire);
vertices[4] = new VertexPositionColor(pt_d, coul_noire);
vertices[5] = new VertexPositionColor(pt_a, coul_noire);
La face de droite est repérée par les points D, C, E et F. D’après la figure 15.17 pour
Copyright 2012 Patrice REY

la face de droite, nous avons le triangle DCF et le triangle FED. Nous entrons donc
les sommets de ces triangles dans cet ordre.
//face droite dcfe
vertices[6] = new VertexPositionColor(pt_d, coul_gris_fonce);
vertices[7] = new VertexPositionColor(pt_c, coul_gris_fonce);
vertices[8] = new VertexPositionColor(pt_f, coul_gris_fonce);
vertices[9] = new VertexPositionColor(pt_f, coul_gris_fonce);
CHAPITRE 15 □ La modélisation 3D 483

vertices[10] = new VertexPositionColor(pt_e, coul_gris_fonce);


vertices[11] = new VertexPositionColor(pt_d, coul_gris_fonce);
La face arrière est repérée par les points E, F, G et H. D’après la figure 15.17 pour la
face arrière, nous avons le triangle EFG et le triangle GHE. Nous entrons donc les
sommets de ces triangles dans cet ordre.
//face arriere efgh
vertices[12] = new VertexPositionColor(pt_e, coul_noire);
vertices[13] = new VertexPositionColor(pt_f, coul_noire);
vertices[14] = new VertexPositionColor(pt_g, coul_noire);
vertices[15] = new VertexPositionColor(pt_g, coul_noire);
vertices[16] = new VertexPositionColor(pt_h, coul_noire);
vertices[17] = new VertexPositionColor(pt_e, coul_noire);
La face de gauche est repérée par les points H, G, B et A. D’après la figure 15.17
pour la face de gauche, nous avons le triangle HGB et le triangle BAH. Nous entrons
donc les sommets de ces triangles dans cet ordre.
//face gauche hgba
vertices[18] = new VertexPositionColor(pt_h, coul_gris_fonce);
vertices[19] = new VertexPositionColor(pt_g, coul_gris_fonce);
vertices[20] = new VertexPositionColor(pt_b, coul_gris_fonce);
vertices[21] = new VertexPositionColor(pt_b, coul_gris_fonce);
vertices[22] = new VertexPositionColor(pt_a, coul_gris_fonce);
vertices[23] = new VertexPositionColor(pt_h, coul_gris_fonce);
La face de dessus est repérée par les points B, G, F et C. D’après la figure 15.17 pour
la face de dessus, nous avons le triangle BGF et le triangle FCB. Nous entrons donc
les sommets de ces triangles dans cet ordre.
//face dessus bgfc
vertices[24] = new VertexPositionColor(pt_b, coul_gris_clair);
vertices[25] = new VertexPositionColor(pt_g, coul_gris_clair);
vertices[26] = new VertexPositionColor(pt_f, coul_gris_clair);
vertices[27] = new VertexPositionColor(pt_f, coul_gris_clair);
vertices[28] = new VertexPositionColor(pt_c, coul_gris_clair);
vertices[29] = new VertexPositionColor(pt_b, coul_gris_clair);
La face de dessous est repérée par les points H, A, D et E. D’après la figure 15.17
pour la face de dessous, nous avons le triangle HAD et le triangle DEH. Nous entrons
donc les sommets de ces triangles dans cet ordre.
//face dessous hade
vertices[30] = new VertexPositionColor(pt_h, coul_gris_clair);
vertices[31] = new VertexPositionColor(pt_a, coul_gris_clair);
vertices[32] = new VertexPositionColor(pt_d, coul_gris_clair);
vertices[33] = new VertexPositionColor(pt_d, coul_gris_clair);
vertices[34] = new VertexPositionColor(pt_e, coul_gris_clair);
vertices[35] = new VertexPositionColor(pt_h, coul_gris_clair);
484 Développez des applications Internet avec Silverlight 5
On configure la caméra en la positionnant et en lui fixant une ligne de visée.
Puis on configure la projection perspective. On en déduit la vue de projection
viewProjection.
//configuration de la camera
Matrix view = Matrix.CreateLookAt(new Vector3(0, 0, 3),
new Vector3(-0, -0, -3), new Vector3(0, 1, 0));
Matrix projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(30), 1.5f, 1, 10);
Matrix viewProjection = view * projection;
Quatre boutons permettent d’effectuer des rotations de la caméra autour des axes
X et Y. Pour effectuer une rotation autour de l’axe X, par exemple, on instancie
une matrice de type Matrix, intitulée rotation, par la méthode statique Matrix.
CreateRotationX. Cette méthode reçoit en paramètre une valeur d’angle en radians
(la méthode statique MathHelper.ToRadians permet de convertir une valeur d’un
angle en degrés vers une valeur d’un angle en radians). Il suffit ensuite de multiplier
la matrice v_effect.World par la matrice rotation. On utilise ce même procédé pour
les quatre boutons pour effectuer des rotations autour des axes X et Y par pas de
10 degrés.
//
private void x_btn_plus_y_Click(object sender, RoutedEventArgs e) {
Matrix rotation = Matrix.CreateRotationY(MathHelper.ToRadians((float)10));
v_effect.World = v_effect.World * rotation;
}
//
private void x_btn_moins_y_Click(object sender, RoutedEventArgs e) {
Matrix rotation = Matrix.CreateRotationY(MathHelper.ToRadians((float)-10));
v_effect.World = v_effect.World * rotation;
}
//
private void x_btn_plus_x_Click(object sender, RoutedEventArgs e) {
Matrix rotation = Matrix.CreateRotationX(MathHelper.ToRadians((float)10));
v_effect.World = v_effect.World * rotation;
}
//
private void x_btn_moins_x_Click(object sender, RoutedEventArgs e) {
Copyright 2012 Patrice REY

Matrix rotation = Matrix.CreateRotationX(MathHelper.ToRadians((float)-10));


v_effect.World = v_effect.World * rotation;
}

3 - Ajouter une texture

Nous allons réaliser une scène 3D dans laquelle un cube est modélisé (figure
15.18). Les faces du cube reçoivent une texture identique. Des boutons permettent
CHAPITRE 15 □ La modélisation 3D 485

de visualiser le cube en faisant tourner la caméra autour de l’axe Y et de l’axe X.


L’UserControl UnCubeAvecTexture.xaml (dans le projet Modelisation3d.sln dans le
dossier chapitre15) illustre cet exemple.
FIGURE 15.18

Le cube modélisé est le même que celui du paragraphe précédent. La différence est
que les faces sont recouvertes d’une texture 2D. Dans le système de coordonnées
à deux dimensions, une texture 2D possède quatre sommets intitulés t_pos_a, t_
pos_b, t_pos_c et t_pos_d, de type Vector2 (figure 15.19). On instancie les quatre
structures Vector2 avec leurs composantes.
//4 positions de la texture 2d
Vector2 t_pos_a = new Vector2(0, 1);
Vector2 t_pos_b = new Vector2(0, 0);
Vector2 t_pos_c = new Vector2(1, 0);
Vector2 t_pos_d = new Vector2(1, 1);
486 Développez des applications Internet avec Silverlight 5
FIGURE 15.19

(0,0) (1,0) t_pos_b t_pos_c

(0,1) t_pos_a
(1,1) t_pos_d

Au lieu d’associer une couleur à un sommet (VertexPositionColor)


comme précédemment, on associe une structure Vector2 à un sommet
(VertexPositionTexture).
Le cube a 6 faces, chaque face est composée de 2 triangles, cela représente donc
36 associations de structures VertexPositionTexture à ajouter au tableau vertices.
//association d’une texture a un sommet
VertexPositionTexture[] vertices = new VertexPositionTexture[36];
Le principe de l’ordre d’entrée des sommets reste identique à celui du paragraphe
précédent. Cependant, pour pouvoir appliquer la texture dans le bon sens, il faut
que le mappage de la texture soit dans le même ordre que celui des sommets des
triangles. La figure 15.20 illustre la façon de faire pour la face avant du cube (face
ABCD). Le triangle (ABC) reçoit les positions de texture (t_pos_a, t_pos_b, t_pos_c).
Le triangle (CDA) reçoit les positions de texture (t_pos_c, t_pos_d, t_pos_a).
FIGURE 15.20

t_pos_b t_pos_c B C
Copyright 2012 Patrice REY

A D
t_pos_a t_pos_d
CHAPITRE 15 □ La modélisation 3D 487

//face avant abcd


vertices[0] = new VertexPositionTexture(pt_a, t_pos_a);
vertices[1] = new VertexPositionTexture(pt_b, t_pos_b);
vertices[2] = new VertexPositionTexture(pt_c, t_pos_c);
vertices[3] = new VertexPositionTexture(pt_c, t_pos_c);
vertices[4] = new VertexPositionTexture(pt_d, t_pos_d);
vertices[5] = new VertexPositionTexture(pt_a, t_pos_a);
Pour la face de droite, on a les triangles DCF et FED. Ils sont mappés à la texture par
respectivement (t_pos_a,t_pos_b,t_pos_c) et (t_pos_c,t_posd,t_pos_a).
//face droite dcfe
vertices[6] = new VertexPositionTexture(pt_d, t_pos_a);
vertices[7] = new VertexPositionTexture(pt_c, t_pos_b);
vertices[8] = new VertexPositionTexture(pt_f, t_pos_c);
vertices[9] = new VertexPositionTexture(pt_f, t_pos_c);
vertices[10] = new VertexPositionTexture(pt_e, t_pos_d);
vertices[11] = new VertexPositionTexture(pt_d, t_pos_a);
Il suffit de faire la même chose pour toutes les autres faces du cube. L’initialisation
du buffer contenant les sommets se fera de la même façon que dans le paragraphe
précédent, en précisant seulement qu’il s’agit de structure VertexPositionTexture.
//initialisation du buffer contenant les sommets
GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
v_buffer_sommet = new VertexBuffer(device, typeof(VertexPositionTexture),
vertices.Length, BufferUsage.WriteOnly);
v_buffer_sommet.SetData(0, vertices, 0, vertices.Length, 0);
Les initialisations de la vue, de la projection et de la projection de la vue sont
inchangées. L’initialisation de l’effet BasicEffect est identique à un détail près: il faut
désactiver la coloration des sommets (v_effect.VertexColorEnabled=false) et il faut
activer l’application des textures par l’affectation de la valeur booléenne true à la
propriété BasicEffect.TextureEnabled. Et il faut charger un objet Texture2D, intitulé
texture_2d, puis l’affecter à la propriété BasicEffect.Texture. La texture utilisée ici
est un fichier image nommé texture_face.png et elle est stockée dans le dossier
contenu comme une ressource incorporée. On récupère cette ressource et on
l’affecte à un objet BitmapImage (démarche déjà vue dans un chapitre précédent).
L’objet texture_2d est instancié et reçoit en paramètre le périphérique d’affichage,
la largeur et la hauteur de l’image. La méthode CopyTo de BitmapImage permet de
copier l’image dans la texture_2d.
//configuration de la camera
Matrix view = Matrix.CreateLookAt(new Vector3(0, 0, 3),
new Vector3(-0, -0, -3), new Vector3(0, 1, 0));
Matrix projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(30), 1.5f, 1, 10);
488 Développez des applications Internet avec Silverlight 5
Matrix viewProjection = view * projection;
//initialisation des effets
v_effect = new BasicEffect(device);
v_effect.World = Matrix.Identity;
v_effect.View = view;
v_effect.Projection = viewProjection;
//on charge une texture depuis une ressource incorporee
//et on la place dans un bitmapimage
string uri = «/Modelisation3d;component/contenu/texture_face.png»;
Stream s = Application.GetResourceStream(new Uri(uri, UriKind.Relative)).
Stream;
BitmapImage bmp = new BitmapImage();
bmp.SetSource(s);
//on copie le bitmapimage dans un objet texture2d
Texture2D texture_2d = new Texture2D(device, bmp.PixelWidth, bmp.PixelHeight);
bmp.CopyTo(texture_2d);
//on configure l’effet BasicEffect pour utiliser une texture
//v_effect.VertexColorEnabled = true;
v_effect.TextureEnabled = true;
v_effect.Texture = texture_2d;
Fréquemment les textures ont des dimensions en puissance de 2 comme 2x2,
4x4, 8x8, 32x32, etc. Si ce n’est pas le cas pour la texture que l’on choisit (ici
elle est de 100x100), il faut désactiver ce comptage de dimension. Cela peut
être fait en récupérant l’objet SamplerState de la collection SamplerStates de la
propriété GraphicsDevice.SamplerStates, puis lui appliquer la méthode statique
SamplerState.LinearClamp.
//evenement draw de x_scene_3d
private void x_scene_3d_Draw(object sender, DrawEventArgs e) {
GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
device.SamplerStates[0] = SamplerState.LinearClamp;
...
e.InvalidateSurface();
}

4 - L’éclairage Copyright 2012 Patrice REY

Il est important de se rendre compte que le modèle d’éclairage de Silverlight ne


réagit pas comme la lumière dans le monde réel. Si le système d’éclairage est
construit pour une émulation se rapprochant de celui du monde réel, les calculs
de réflexion de la lumière représentent une tâche intensive pour le processeur.
Pour ces raisons, Silverlight simplifie les choses:
• les effets de lumière sont calculés individuellement pour chaque objet; la
lumière réfléchie d’un objet ne se réfléchit pas sur un autre objet; de même,
CHAPITRE 15 □ La modélisation 3D 489

un objet ne provoque pas d’ombre sur un autre objet.


• l’éclairage est calculé au sommet de chaque triangle et Silverlight en déduit
par interpolation l’éclairage sur le reste du triangle; il en résulte que des
objets composés de peu de triangles sont moins bien éclairés que ceux qui en
possèdent un grand nombre.
Silverlight supporte deux types d’éclairage: l’éclairage ambiant qui illumine partout
et qui illumine chaque objet de la même façon, et l’éclairage directionnel qui
illumine dans une direction spécifique et qui illumine les surfaces qui se trouvent
en face.
Pour réaliser un éclairage dans le cadre du rendu avec l’effet basique (BasicEffect),
il suffit de fixer la couleur de la lumière à la propriété AmbientLightColor
de BasicEffect. Cette valeur est de type Vector3 dont les trois composantes
représentent une fraction (entre 0 et 1) des composantes RGB.
Par exemple, une lumière rouge sera décrite par un AmbientLightColor = new
Vector3(1,0,0), ce qui correspond à un new Color(255,0,0) dans la notation RGB.
Pour que ce système d’éclairage soit effectif, il faut affecter la valeur true à la
propriété LightingEnabled de BasicEffect. En utilisant par exemple cet éclairage, le
reflet sera à base de rouge.
L’éclairage directionnel est plus intéressant. Si par exemple un cube est éclairé par
un éclairage directionnel, si le cube tourne sur lui-même, les faces réfléchiront la
lumière différemment. L’effet BasicEffect permet d’ajouter jusqu’à trois éclairages
directionnels (propriétés DirectionalLight0, DirectionalLight1 et DirectionalLight2).
Chaque éclairage directionnel possède sa propre direction et deux couleurs (une
couleur diffuse et une couleur spéculaire). La méthode EnableDefaultLighting de
BasicEffect active l’éclairage ambiant et l’éclairage directionnel.
Tout cela n’est pas suffisant puisqu’il faut configurer les sommets des objets 3D.
Le problème est que Silverlight a besoin d’informations pour calculer comment
la lumière illumine chaque sommet. Un sommet représente juste un point dans
l’espace. Il faut fournir en plus un vecteur normal pour chaque sommet. Ce vecteur
normal indique comment la surface est orientée. Les surfaces qui sont en face de
la source de lumière réfléchiront plus que les autres surfaces.
La figure 15.21 visualise les vecteurs normaux aux sommets d’un cube.
Pour ajouter un vecteur normal à un sommet, il suffit de choisir un objet
VertexPositionNormalTexture qui associe un sommet avec un vecteur normal et
avec une coordonnée de texture.
490 Développez des applications Internet avec Silverlight 5
FIGURE 15.21

Nous allons réaliser une scène 3D dans laquelle un cube est modélisé (figure
15.22). Les faces du cube reçoivent une texture identique. Le cube est éclairé
par un éclairage ambiant et un éclairage directionnel. Des boutons permettent
de visualiser le cube en faisant tourner la caméra autour de l’axe Y et de l’axe
X. L’UserControl UnCubeTextureLumiere.xaml (dans le projet Modelisation3d.sln
dans le dossier chapitre15) illustre cet exemple.
FIGURE 15.22

Copyright 2012 Patrice REY


CHAPITRE 15 □ La modélisation 3D 491

Le cube modélisé avec sa texture est identique à celui du paragraphe précédent.


Nous allons juste rajouter les vecteurs normaux aux faces. Il y a 6 faces donc il y a
6 vecteurs normaux.
//6 vecteurs normaux
Vector3 f_avant_normal = new Vector3(0, 0, 1);
Vector3 f_arriere_normal = new Vector3(0, 0, -1);
Vector3 f_dessus_normal = new Vector3(0, 1, 0);
Vector3 f_dessous_normal = new Vector3(0, -1, 0);
Vector3 f_gauche_normal = new Vector3(-1, 0, 0);
Vector3 f_droite_normal = new Vector3(1, 0, 0);
Le tableau vertices est composé de structures de type VertexPositionNormalTexture
qui associent un sommet avec une position de texture et un vecteur normal.
//association d’une texture a un sommet avec une normale
VertexPositionNormalTexture[] vertices = new VertexPositionNormalTexture[36];
Pour la face avant, il faut ajouter le vecteur normal f_avant_normal. On en fait de
même avec les autres faces et leur vecteur normal respectif.
//face avant abcd
vertices[0] = new VertexPositionNormalTexture(pt_a,f_avant_normal ,t_pos_a);
vertices[1] = new VertexPositionNormalTexture(pt_b, f_avant_normal, t_pos_b);
vertices[2] = new VertexPositionNormalTexture(pt_c, f_avant_normal, t_pos_c);
vertices[3] = new VertexPositionNormalTexture(pt_c, f_avant_normal, t_pos_c);
vertices[4] = new VertexPositionNormalTexture(pt_d, f_avant_normal, t_pos_d);
vertices[5] = new VertexPositionNormalTexture(pt_a, f_avant_normal, t_pos_a);
...
//face dessous hade
vertices[30] = new VertexPositionNormalTexture(pt_h,f_dessous_normal,
t_pos_a);
vertices[31] = new VertexPositionNormalTexture(pt_a, f_dessous_normal,
t_pos_b);
vertices[32] = new VertexPositionNormalTexture(pt_d, f_dessous_normal,
t_pos_c);
vertices[33] = new VertexPositionNormalTexture(pt_d, f_dessous_normal,
t_pos_c);
vertices[34] = new VertexPositionNormalTexture(pt_e, f_dessous_normal,
t_pos_d);
vertices[35] = new VertexPositionNormalTexture(pt_h, f_dessous_normal,
t_pos_a);
L’initialisation du buffer contenant les sommets doit tenir compte du type
VertexPositionNormalTexture.
//initialisation du buffer contenant les sommets
GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
v_buffer_sommet = new VertexBuffer(device, typeof(VertexPositionNormalTexture)
,vertices.Length, BufferUsage.WriteOnly);
492 Développez des applications Internet avec Silverlight 5
v_buffer_sommet.SetData(0, vertices, 0, vertices.Length, 0);
La méthode EnableDefaultLighting de l’effet BasicEffect permet d’activer la lumière
ambiante avec les 3 lumières directionnelles.
v_effect.EnableDefaultLighting();
Si l’on souhaite une lumière ambiante de coloration rouge, la propriété
AmbientLightColor sera affectée de la valeur Vector3(1,0,0), et la propriété
LightingEnabled sera fixée à true.
v_effect.AmbientLightColor = new Vector3(1.0f, 0.0f, 0.0f);
v_effect.LightingEnabled = true;

5 - Dupliquer des objets 3D

Nous allons réaliser une scène 3D dans laquelle nous positionnons trois
instances d’un cube 3D et une instance d’un sol 3D (figure 15.23). Les faces du
cube reçoivent une texture identique. Le sol est représenté par une face plane
remplie d’une couleur unie grise. L’UserControl InstanceCube.xaml (dans le projet
Modelisation3d.sln dans le dossier chapitre15) illustre cet exemple.
FIGURE 15.23

Copyright 2012 Patrice REY


CHAPITRE 15 □ La modélisation 3D 493

La programmation objet permet justement de pouvoir créer un modèle (comme


un cube 3D ou un sol 3D). Ajouter plusieurs cubes dans une scène 3D revient à
instancier trois objets de type cube, et à les positionner sur la scène 3D.
Une classe Cube3D est déclarée. On la dote de la propriété World pour permettre
au cube de changer sa matrice pour le faire bouger ou tourner, de la propriété
View pour permettre au cube de changer sa matrice de vue pour faire bouger ou
tourner la caméra, et de la propriété Projection pour permettre au cube de changer
sa projection de vue pour changer le ratio d’aspect si la taille du DrawingSurface
est modifiée.
public class Cube3D {
private VertexBuffer v_buffer_sommet;
private BasicEffect v_effect;
//permettre au cube de changer la matrice de son monde
//pour le faire bouger ou tourner
public Matrix World {
get { return v_effect.World; }
set { v_effect.World = value; }
}
//permettre au cube de changer sa matrice de vue
//pour faire bouger ou tourner la camera
public Matrix View {
get { return v_effect.View; }
set { v_effect.View = value; }
}
//permettre au cube de changer sa projection de vue
//pour changer le ratio d’aspect si la taille du drawingsurface
//est modifiee
public Matrix Projection {
get { return v_effect.Projection; }
set { v_effect.Projection = value; }
}
...
}
Le constructeur de l’objet Cube3D reçoit en paramètre le GraphicsDevice pour
l’affichage, le sommet H de référence (pour déterminer la position des autres
sommets), une largeur pour le cube, l’URI de la texture à appliquer, un ratio
d’aspect et une matrice de vue. Les 8 sommets sont déterminés par rapport à un
sommet de référence (point H ici). La génération des sommets est identique à celle
du paragraphe précédent.
//constructeur
public Cube3D(GraphicsDevice device, Vector3 pt_ref_h, float largeur,
string uri_texture, float ratio_aspect, Matrix view) {
//4 positions de la texture 2d
494 Développez des applications Internet avec Silverlight 5
Vector2 t_pos_a = new Vector2(0, 1);
Vector2 t_pos_b = new Vector2(0, 0);
Vector2 t_pos_c = new Vector2(1, 0);
Vector2 t_pos_d = new Vector2(1, 1);
//8 sommets
Vector3 pt_a = new Vector3(pt_ref_h.X, pt_ref_h.Y,
pt_ref_h.Z + largeur);//A(0,0,1)
Vector3 pt_b = new Vector3(pt_ref_h.X, pt_ref_h.Y + largeur,
pt_ref_h.Z + largeur);//B(0,1,1)
Vector3 pt_c = new Vector3(pt_ref_h.X + largeur, pt_ref_h.Y + largeur,
pt_ref_h.Z + largeur);//C(1,1,1)
Vector3 pt_d = new Vector3(pt_ref_h.X + largeur, pt_ref_h.Y,
pt_ref_h.Z + largeur);//D(1,0,1)
Vector3 pt_e = new Vector3(pt_ref_h.X + largeur,
pt_ref_h.Y, pt_ref_h.Z);//E(1,0,0)
Vector3 pt_f = new Vector3(pt_ref_h.X + largeur, pt_ref_h.Y + largeur,
pt_ref_h.Z);//F(1,1,0)
Vector3 pt_g = new Vector3(pt_ref_h.X, pt_ref_h.Y + largeur,
pt_ref_h.Z);//G(0,1,0)
Vector3 pt_h = new Vector3(pt_ref_h.X, pt_ref_h.Y, pt_ref_h.Z);//H(0,0,0)
//tableau des positions des sommets avec la texture
VertexPositionTexture[] vertices = new VertexPositionTexture[36];
//face avant abcd
vertices[0] = new VertexPositionTexture(pt_a, t_pos_a);
vertices[1] = new VertexPositionTexture(pt_b, t_pos_b);
vertices[2] = new VertexPositionTexture(pt_c, t_pos_c);
vertices[3] = new VertexPositionTexture(pt_c, t_pos_c);
vertices[4] = new VertexPositionTexture(pt_d, t_pos_d);
vertices[5] = new VertexPositionTexture(pt_a, t_pos_a);
...
//face dessous hade
vertices[30] = new VertexPositionTexture(pt_h, t_pos_a);
vertices[31] = new VertexPositionTexture(pt_a, t_pos_b);
vertices[32] = new VertexPositionTexture(pt_d, t_pos_c);
vertices[33] = new VertexPositionTexture(pt_d, t_pos_c);
vertices[34] = new VertexPositionTexture(pt_e, t_pos_d);
vertices[35] = new VertexPositionTexture(pt_h, t_pos_a);
//initialisation du buffer contenant les sommets
v_buffer_sommet = new VertexBuffer(device, typeof(VertexPositionTexture),
vertices.Length, BufferUsage.WriteOnly);
Copyright 2012 Patrice REY

v_buffer_sommet.SetData(0, vertices, 0, vertices.Length, 0);


...
}
Le paramétrage de l’effet de rendu BasicEffect, et le chargement avec l’application
d’une texture sous la forme d’une ressource incorporée, se font de la même façon
que celle précédemment vue.
CHAPITRE 15 □ La modélisation 3D 495

//constructeur
public Cube3D(GraphicsDevice device, Vector3 pt_ref_h, float largeur,
string uri_texture, float ratio_aspect, Matrix view) {
...
//configure la camera de type perspective
Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
ratio_aspect, 1, 100);
//initialise l’effet BasicEffect
v_effect = new BasicEffect(device);
v_effect.World = Matrix.Identity;
v_effect.View = view;
v_effect.Projection = view * projection;
//on charge une texture depuis une ressource incorporee
//et on la place dans un bitmapimage
Stream s = Application.GetResourceStream(
new Uri(uri_texture, UriKind.Relative)).Stream;
BitmapImage bmp = new BitmapImage();
bmp.SetSource(s);
//on copie le bitmapimage dans un objet texture2d
Texture2D texture;
texture = new Texture2D(device, bmp.PixelWidth, bmp.PixelHeight);
bmp.CopyTo(texture);
//on configure l’effet BasicEffect pour utiliser une texture
//v_effect.VertexColorEnabled = true;
v_effect.TextureEnabled = true;
v_effect.Texture = texture;
}
La déclaration d’une méthode Draw permet d’effectuer la modélisation du cube
sur le périphérique d’affichage.
//dessiner le cube
public void Draw(GraphicsDevice device) {
device.SetVertexBuffer(v_buffer_sommet);
device.SamplerStates[0] = SamplerState.LinearClamp;
foreach (EffectPass pass in v_effect.CurrentTechnique.Passes) {
pass.Apply();
device.DrawPrimitives(PrimitiveType.TriangleList, 0,
v_buffer_sommet.VertexCount / 3);
}
}
Pour matérialiser un sol, on choisit de modéliser une face plane dont on fournit
une longueur de côté. Le procédé de modélisation est rigoureusement identique à
celui du cube au détail près que la face plane est remplie d’une couleur unie.
public class Sol3D {
private VertexBuffer v_buffer_sommet;
private BasicEffect v_effect;
//permettre au cube de changer la matrice de son monde
496 Développez des applications Internet avec Silverlight 5
//pour le faire bouger ou tourner
public Matrix World {
get { return v_effect.World; }
set { v_effect.World = value; }
}
//permettre au cube de changer sa matrice de vue
//pour faire bouger ou tourner la camera
public Matrix View {
get { return v_effect.View; }
set { v_effect.View = value; }
}
//permettre au cube de changer sa projection de vue
//pour changer le ratio d’aspect si la taille du drawingsurface
//est modifiee
public Matrix Projection {
get { return v_effect.Projection; }
set { v_effect.Projection = value; }
}
//
public Sol3D(GraphicsDevice device, float aspectRatio, Matrix view,
float larg_cote) {
// Create a large flat floor square, consisting of two triangles.
Vector3 pt_a = new Vector3(-larg_cote, 0, larg_cote);
Vector3 pt_b = new Vector3(-larg_cote, 0, -larg_cote);
Vector3 pt_c = new Vector3(larg_cote, 0, -larg_cote);
Vector3 pt_d = new Vector3(larg_cote, 0, larg_cote);
Color coul_gris = new Color(170, 170, 170);
VertexPositionColor[] vertices = new VertexPositionColor[6];
vertices[0] = new VertexPositionColor(pt_a, coul_gris);
vertices[1] = new VertexPositionColor(pt_b, coul_gris);
vertices[2] = new VertexPositionColor(pt_c, coul_gris);
vertices[3] = new VertexPositionColor(pt_c, coul_gris);
vertices[4] = new VertexPositionColor(pt_d, coul_gris);
vertices[5] = new VertexPositionColor(pt_a, coul_gris);
// Set up the vertex buffer.
v_buffer_sommet = new VertexBuffer(device, typeof(VertexPositionColor),
vertices.Length, BufferUsage.WriteOnly);
v_buffer_sommet.SetData(0, vertices, 0, vertices.Length, 0);
// Configure the camera.
Matrix projection = Matrix.CreatePerspectiveFieldOfView(
Copyright 2012 Patrice REY

MathHelper.PiOver4, aspectRatio, 1, 100);


// Set up the effect.
v_effect = new BasicEffect(device);
v_effect.World = Matrix.Identity;
v_effect.View = view;
v_effect.Projection = view * projection;
v_effect.VertexColorEnabled = true;
}
//
CHAPITRE 15 □ La modélisation 3D 497

public void Draw(GraphicsDevice device) {


device.SetVertexBuffer(v_buffer_sommet);
foreach (EffectPass pass in v_effect.CurrentTechnique.Passes) {
pass.Apply();
device.DrawPrimitives(PrimitiveType.TriangleList, 0,
v_buffer_sommet.VertexCount / 3);
}
}
}//end class
Dans le fichier InstanceCube.xaml, on positionne sur le Canvas x_cnv_root un
contrôle Border qui contient comme unique enfant un contrôle DrawingSurface,
nommé x_scene_3d. La largeur et la hauteur de ce contrôle sont fixées à 800 et
400 pixels respectivement, ce qui donne un ratio d’aspect de 800/400 = 2. Un
gestionnaire est ajouté pour l’événement Draw de x_scene_3d.
<Canvas x:Name= «x_cnv_root» HorizontalAlignment= «Center»
VerticalAlignment= «Top» Height= «500» Width= «800»>
<Border BorderThickness= «2» Width= «800» Height= «430» BorderBrush= «Black»
Canvas.Top= «0»>
<DrawingSurface Width= «800» Height= «400» Name= «x_scene_3d»
Draw= «x_scene_3d_Draw»></DrawingSurface>
</Border>
</Canvas>
Pour ajouter trois cubes et un sol, on déclare les instances m_cube1, m_cube2 et
m_cube3, de type Cube3D, et m_sol de type Sol3D.
//objets 3d
private Cube3D m_cube1;
private Cube3D m_cube2;
private Cube3D m_cube3;
private Sol3D m_sol;
Dans le constructeur InstanceCube, on récupère le périphérique d’affichage
courant par la méthode statique GraphicsDeviceManager.Current.GraphicsDevice.
On instancie une vue nommée view, de type Matrix, qui sera partagée par tous
les objets 3D. On définit une variable ratio_aspect que l’on fixe à la valeur de 2
(type float, correspondant au rapport de la largeur par la hauteur du contrôle
DrawingSurface). Puis on instancie les objets 3D avec leurs paramètres demandés.
//constructeur
public InstanceCube() {
InitializeComponent();
GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
string uri_texture = «/Modelisation3d;component/contenu/texture_face.png»;
//on cree une vue partagee par tous les objets
Matrix view = Matrix.CreateLookAt(new Vector3(1, 1, 3), Vector3.Zero,
498 Développez des applications Internet avec Silverlight 5
Vector3.Up);
float ratio_aspect = 2f;
//on cree 3 cubes et un sol
m_cube1 = new Cube3D(device, new Vector3(0, 0, -0.5f), 1.5f, uri_texture,
ratio_aspect, view);
m_cube2 = new Cube3D(device, new Vector3(-2, 0, 0), 1f, uri_texture,
ratio_aspect, view);
m_cube3 = new Cube3D(device, new Vector3(0, 0, 1.5f), 1f, uri_texture,
ratio_aspect, view);
m_sol = new Sol3D(device, ratio_aspect, view,3f);
}
Le gestionnaire de l’événement Draw de x_scene_3d consiste à appliquer une
couleur de fond (blanc) pour effacer le rendu précédent (méthode Clear), puis
d’invoquer la méthode Draw pour m_cube1, m_cube2, m_cube3 et m_sol.
La méthode InvalidateSurface permet de redessiner le rendu par un appel de
l’événement Draw.
//evenement draw de x_scene_3d
private void x_scene_3d_Draw(object sender, DrawEventArgs e) {
GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
device.Clear(new Color(255,255,255));
m_cube1.Draw(device);
m_cube2.Draw(device);
m_cube3.Draw(device);
m_sol.Draw(device);
e.InvalidateSurface();
}

6 - Les mouvements des objets 3D

Nous allons réaliser une scène 3D dans laquelle nous positionnons un cube 3D
(figure 15.24). Les faces du cube reçoivent une texture identique. Trois contrôles
de type Slider permettent d’effectuer des rotations du cube par rapport aux axes
X, Y et Z. L’UserControl Mouvements.xaml (dans le projet Modelisation3d.sln dans
le dossier chapitre15) illustre cet exemple.
Sur le Canvas x_cnv_root, on positionne un contrôle DrawingSurface dont le ratio
Copyright 2012 Patrice REY

d’aspect est égal à 2 (une longueur de 800 et une largeur de 400). Trois contrôles
de type Slider sont positionnés et leurs propriétés Value sont liées par databinding
à trois TextBlock.
<Canvas x:Name= «x_cnv_root» HorizontalAlignment= «Center»
VerticalAlignment= «Top» Height= «587» Width= «800»>
<Border BorderThickness= «2» Width= «800» Height= «430» BorderBrush= «Black»
Canvas.Top= «0»>
CHAPITRE 15 □ La modélisation 3D 499
FIGURE 15.24

<DrawingSurface Width= «800» Height= «400» Name= «x_scene_3d»


Draw= «x_scene_3d_Draw»></DrawingSurface>
</Border>
<!-- slider rotation x -->
<TextBlock Canvas.Left= «171» Canvas.Top= «451» FontFamily= «Verdana»
FontSize= «14»>Rotation axe X:</TextBlock>
<Slider Canvas.Left= «284» Canvas.Top= «445» Cursor= «Hand» Height= «32»
LargeChange= «10» Maximum= «360» Minimum= «0» Name= «x_slider_cube_x»
SmallChange= «1» Value= «0» Width= «259»
ValueChanged= «x_slider_cube_ValueChanged» />
<TextBlock Canvas.Left= «553» Canvas.Top= «451» FontFamily= «Verdana»
FontSize= «14» FontWeight= «Bold» Height= «26»
Name= «x_text_rot_x» Text= «{Binding ElementName=x_slider_cube_x,
Path=Value, StringFormat=\{0:F\}}» Width= «41» />
<!-- slider rotation y -->
<TextBlock Canvas.Left= «171» Canvas.Top= «501» FontFamily= «Verdana»
500 Développez des applications Internet avec Silverlight 5
FontSize= «14»>Rotation axe Y:</TextBlock>
<Slider Canvas.Left= «284» Canvas.Top= «495» Width= «259» Minimum= «0»
Maximum= «360» SmallChange= «1» LargeChange= «10»
Name= «x_slider_cube_y» Value= «0» Cursor= «Hand»
ValueChanged= «x_slider_cube_ValueChanged» Height= «32»></Slider>
<TextBlock FontFamily= «Verdana» FontSize= «14»
FontWeight= «Bold» Canvas.Left= «553» Canvas.Top= «501» Height= «26»
Width= «41» Text= «{Binding ElementName=x_slider_cube_y, Path=Value,
StringFormat=\{0:F\}}» Name= «x_text_rot_y»></TextBlock>
<!-- rotation z -->
<TextBlock Canvas.Left= «171» Canvas.Top= «548» FontFamily= «Verdana»
FontSize= «14»>Rotation axe Z:</TextBlock>
<Slider Canvas.Left= «284» Canvas.Top= «542» Cursor= «Hand» Height= «32»
LargeChange= «10» Maximum= «360» Minimum= «0» Name= «x_slider_cube_z»
SmallChange= «1» Value= «0» Width= «259»
ValueChanged= «x_slider_cube_ValueChanged»/>
<TextBlock Canvas.Left= «553» Canvas.Top= «548» FontFamily= «Verdana»
FontSize= «14» FontWeight= «Bold» Height= «26» Name= «x_text_rot_z»
Text= «{Binding ElementName=x_slider_cube_z, Path=Value,
StringFormat=\{0:F\}}» Width= «41» />
</Canvas>
La propriété World de la variable v_effect permet d’effectuer des rotations selon
les axes X, Y et Z. On commence par initialiser la propriété World en lui affectant
la matrice identité (Matrix.Identity). On instancie trois matrices, de type Matrix,
intitulées rotation_x, rotation_y et rotation_z, pour effectuer des rotations d’angle
en fonction de la propriété Value des contrôles Slider respectifs. Pour obtenir la
position du cube, il suffit de multiplier la matrice de la propriété World par les
matrices de rotation. Il ne faut pas oublier d’appeler la méthode Invalidate de
DrawingSurface pour mettre à jour les changements.
//valeur slider modifiee
private void x_slider_cube_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e) {
v_effect.World = Matrix.Identity;
Matrix rotation_x = Matrix.CreateRotationX(
MathHelper.ToRadians((float)x_slider_cube_x.Value));
Matrix rotation_y = Matrix.CreateRotationY(
MathHelper.ToRadians((float)x_slider_cube_y.Value));
Copyright 2012 Patrice REY

Matrix rotation_z = Matrix.CreateRotationZ(


MathHelper.ToRadians((float)x_slider_cube_z.Value));
v_effect.World = v_effect.World * rotation_x * rotation_y * rotation_z;
x_scene_3d.Invalidate();
}
La figure 15.25 visualise le résultat obtenu si on effectue une rotation par rapport
à l’axe Y seul en fonction de la position de départ du cube. La figure 15.26 visualise
le résultat obtenu si on effectue une rotation par rapport à l’axe X seul en fonction
CHAPITRE 15 □ La modélisation 3D 501

de la position de départ du cube.


FIGURE 15.25

Y Y

X X
Z Z

FIGURE 15.26

Y Y

X X
Z Z
502 Développez des applications Internet avec Silverlight 5
La figure 15.27 visualise le résultat obtenu si on effectue une rotation par rapport
à l’axe Z seul en fonction de la position de départ du cube. La figure 15.28 visualise
le résultat obtenu si on effectue une rotation par rapport à l’axe X, une rotation par
rapport à l’axe Y et une rotation par rapport à l’axe Z.
FIGURE 15.27 Y Y

X X
Z Z

FIGURE 15.28 Y Y

X X
Z Z
Copyright 2012 Patrice REY
INDEX
Index 505
Symboles BasedOn 357
BasicEffect 464
<Application> 167 Begin 271
<Application.Resources> 44 BeginLoad 209
<DoubleAnimation.EasingFunction> 287 BeginTime 272
<Image.OpacityMask> 258 Bevel 221
<object> 31 BezierSegment 226, 230
<param> 31 Binding 48, 378
<ResourceDictionary> 44 BindingValidationError 90
<ToolTipService.ToolTip> 125 BitmapCache 323
BitmapImage 114
A BitmapSource 263
accélération matérielle 321 BlackoutDates 159
AcceptsReturn 137 Block 143
acquisition vidéo 345 BlurEffect 259
Action de génération 178 BlurRadius 260
AlternatingRowBackground 404 Bold 112
AmbientLightColor 489 Border 69
Angle 239 BorderBrush 69
animation 270 BorderThickness 69
animations réalistes 283 Both 70
Application 164 BounceEase 284
ApplicationLifetimeObjects 169 Bounces 287
AppManifest 22, 164 Bounciness 287
ArcSegment 226, 228 Brush 250
AreaSeries 443 BubbleSeries 456
ARGB 251 bubbling 91
ARGB32 265 Button 88, 118
Arrange 55, 85 ButtonBase 118
assembly 165 byte 253
AssemblyInfo 22
AssemblyPart 165 C
Auto 60 cache de composition 322
AutoCompleteBox 137 CacheMode 323
AutoGenerateColumns 402 Calendar 159
AutoPlay 328 callback 80
AutoReverse 272 CancelEventArgs 202
autoUpgrade 31 CancelLoad 209
CanExecute 104
B CanExecuteChanged 105
BackEase 284 CanGoBack 205
background 31 CanGoForward 205
Balance 332 CanLoad 209
BarSeries 446 CanUserReorderColumns 405
506 Index
CanUserResizeColumns 404 ColumnDisplayIndexChanged 405
Canvas 54, 56 ColumnReordered 405
Canvas.Left 56 ColumnReordering 405
Canvas.Top 56 ColumnSeries 450
CaptureDevice 345 ColumnWidth 404
CaptureDeviceConfiguration 346 ComboBox 132
CaptureImageAsync 349 ComboBoxItem 132
CaptureImageCompleted 349 Command 118
CaptureImageCompletedEventArgs 349 Commande 93
CaptureMouse 101 CommandParameter 118
CaptureSource 346 CommonStates 372
CaptureState.Started 349 compilation 28
caractères spéciaux 42 CompositeTransform 235, 243
CaretBrush 138 CompositionMode 469
CategorieAxis 449 CompositionTarget 312
CenterOfRotationX 245 Content 380
CenterOfRotationY 245 ContentControl 116
CenterRotationZ 245 ContentLoader 212
CenterX 237 ContentPresenter 363
CenterY 237 ContentPropertyAttribute 429
CheckAndDownloadUpdateAsync 169 Contenu 178
CheckAndDownloadUpdateCompleted 169 Control 103
CheckBox 118 ControlTemplate 362
Child 126 ConverterCulture 408
Children 55 CornerRadius 69, 363
ChildWindow 201 CounterClockwise 229
CircleEase 284 CreatePerspectiveFieldOfView 476
classe partielle 36 CubicEase 284
Click 88 CullClockwiseFace 478
ClickMode 118 CullCounterClockwiseFace 478
ClientBin 26 CullMode 478
Clip 233 Current 169
clipping 233 CurrentCellChanged 404
Clockwise 229 CurrentColumn 404
Closed 126, 202 CurrentItem 404
Closing 202 CustomValidation 397
code-behind 36
Collapsed 68 D
Collection<T> 438 databinding 48
Color 251 DataContext 378
ColorAnimation 270 DataContextChanged 90
ColorAnimationUsingKeyFrames 270, 301 DataForm 382
ColorKeyFrame 301 DataGrid 400
ColumnDefinition 58 DataGridCheckBoxColumn 404
ColumnDefinitions 58
Index 507
DataGridColumn 403 Draw 469
DataGridRow 403 DrawingSurface 464, 469
DataGridTemplateColumn 404, 411 DrawPrimitives 477
DataGridTextColumn 403 Drop 89
DataPager 420 DropShadowEffect 259
DataTemplate 411, 433 Duration 272
DatePicker 159
DateValidationError 161 E
découpe 233 EaseIn 284
Delta 101 EaseInCore 295
DependencyObject 78 EaseInOut 284
DependencyProperty 79 EaseOut 284
DependencyPropertyChangedEventArgs 81 EasingColorKeyFrame 302, 309
Deployment 165 EasingDoubleKeyFrame 309
Description 382 EasingFunctionBase 283, 295
DescriptionStates 391 EasingPointKeyFrame 309
DescriptionViewer 387 Effect 259
DesignHeight 35 EffectiveValueEntry 78
DesignWidth 35 effet de pixellisation 325
DesiredFormat 345 ElasticEase 284, 288
DesiredSize 86 ElementName 49, 381
DialogResult 202 Ellipse 218
dictionnaires de ressources 45 EllipseGeometry 224
Direction 260 EnableDefaultLighting 492
DirectionalLight0 489 enableHtmlAccess 32
DiscreteObjectKeyFrame 302 EndLoad 209
DispatcherTimer 312, 335 EndPoint 254
Display 382 EntryPointAssembly 165
DisplayAttribute 382 EntryPointType 166
DisplayDateEnd 159 environnement de développement 18
DisplayDateStart 159 espaces de noms 34
DisplayMemberPath 131 événements 42
DisplayMode 159 événements routés 88
DockPanel 54, 61 EvenOdd 223
DockPanel.Dock 61 Execute 104
DoubleAnimation 270 Exit 167
DoubleAnimationUsingKeyFrames 270, 301 ExponentialEase 284
DoubleKeyFrame 301
DoubleTap 89 F
DownloadProgressChanged 182
DownloadProgressChangedEventArgs 183 Fill 115, 216
DownOnly 70 FillBehavior 272
DragEnter 89 FillMode 478
DragLeave 89 FillRule 222
DragOver 89 FirstDayOfWeek 159
508 Index
Flat 219 Grid.RowSpan 58
focus 103 GroupDescription 417
FocusManager 104
FocusStates 372, 375 H
FontFamily 112 Handled 98
FontSize 112 HasDescription 391
FontSource 138 HasElevatedPermissions 169
FontStretch 112 Header 136
FontStyle 112 HeaderedItemsControl 425, 433
FontWeight 112 HeaderStyle 404
Foreground 112 HeadersVisibility 404
Forever 272 Height 217
Frame 205 héritage de style 357
FramesPerSecond 345 HierarchicalDataTemplate 433
FrameworkElement 54, 90 Hold 89
FriendlyName 345 HoldEnd 273
From 300 HorizontalAlignment 58
FrozenColumnCount 405 HorizontalContentAlignment 118
G HorizontalOffset 126
HorizontalScrollBarVisibility 71, 137
GeneralTransform 236 Host 169
GeneratedDuration 375 Hyperlink 144
GeometryGroup 224 HyperlinkButton 118
gestion de l’interactivité 88
GetCellContent 404 I
GetIndex 404 ICollectionView 416
GetResourceStream 169 ICommand 104
GlobalOffsetX 245 IEasingFunction 283
GlobalOffsetY 245 IEnumerable 400
GlobalOffsetZ 245 IEnumerable<T> 440
GoBack 205 Image 114
GoForward 205 ImageBrush 250, 255
GotFocus 88, 89 ImageFailed 116, 256
GPU 321 ImageSource 255, 263
GPUAccelerationDisabled 466 ImplicitInputBrush 250
GradientStop 254 INavigationContentLoader 209
Graphic Processor Unit 321 Indeterminate 120
GraphicsDevice 464 InitializeComponent 36
GraphicsDeviceManager 464, 466 initParams 32, 167
Grid 54, 58 Inline 112
Grid.Column 58 InlineUIContainer 144
Grid.ColumnSpan 58 INotifyPropertyChanged 392
GridLinesVisibility 404 Install 169
Grid.Row 58 InstallState 169
Index 509
InstallStateChanged 170 L’alignement 66
interpolation réaliste 309 langage déclaratif 34
Invalidate 469 LargeChange 155
InvalidateSurface 477 LastChildFill 62
InvalidFocused 391 layout 54
InvalidUnfocused 391 LayoutUpdated 90
IsChecked 120 LightingEnabled 489
IsDirectionReversed 157 Line 219
IsDropDownOpen 141 LinearDoubleKeyFrame 301
ISelectionAdapter 141 LinearGradientBrush 250, 253
IServiceProvider 394 LineBreak 112
IsExpanded 426 LineGeometry 224
IsFocused 118 LineHeight 110
IsIndeterminate 156 LineSegment 226, 228
IsLargeArc 229 LineSeries 454
IsMouseOver 118 ListBox 129
IsOpen 126 LoadComponent 169
IsPressed 118 Loaded 91
IsReadOnly 138 LoadingRow 404
IsRunningOutOfBrowser 169 LocalOffsetX 245
IsTabStop 104 LocalOffsetY 245
IsTextCompletionEnabled 141 LocalOffsetZ 245
IsThreeState 120 LostFocus 88, 89
IsTodayHighlighted 159 LostMouseCapture 89
Italic 112
ItemCollection 128 M
ItemContainerGenerator 425 M11 243
ItemContainerStyle 425 M12 243
Items 128 M21 243
ItemsControl 128 M22 243
ItemsSource 130 MainWindow 169
IValueConverter 412 ManipulationCompleted 89
K ManipulationDelta 89
ManipulationStarted 89
Key 94 mapping d’URI 207
Keyboard 93 mapping XAML 35
KeyDown 88, 89, 93 marges 65
KeyFrame 300 Margin 65
KeySpline 307 markup 48
KeyTime 304 masque d’opacité 257
KeyUp 88, 89, 93 MathHelper 476
Matrix 243, 473
L MatrixTransform 235
Label 380 MaxDropDownHeight 141
510 Index
Maximum 155 O
Measure 55, 85
MediaCommand 89 ObjectAnimationUsingKeyFrames 270, 301
MediaElement 328 ObjectCollection 438
MediaEnded 332 ObjectKeyFrame 301
MediaFailed 332 ObservableObjectCollection 131
MediaOpened 332 OffsetX 243
mini-langage 232 OffsetY 243
Minimum 155 OldValue 81
minRuntimeVersion 31 onError 31
Miter 221 OneTime 391
Mode 51 OneWay 51, 391
mode de routage 91 onglet Design 23
modèle 362 onLoad 32
mode unidirectionnel 51 OnPropertyChanged 392
ModifierKeys 93 onResize 32
Modifiers 93 onSilverlightError 168
MouseButtonEventArgs 98 onSourceDownloadComplete 32
MouseEventArgs 97 onsourcedownloadprogresschanged 177
MouseLeftButtonDown 88 onSourceDownloadProgressChanged 32
MouseLeftButtonUp 88 Opacity 257, 260
MouseMove 88 OpacityMask 257
MouseRightButtonDown 88 Opened 126
MouseRightButtonUp 88 OpenReadAsync 182
MouseWheelEventArgs 99 OpenReadCompleted 182
MP3 328 Order 382
MPEG-4 328 Orientation 57

N P
NaturalDuration 333 Padding 66, 110
NaturalVideoHeight 339 PageChanged 420
NaturalVideoWidth 339 PageChanging 420
Navigated 205 PageChangingEventArgs 420
NavigateUri 119 PagedCollectionView 416
Navigating 205 PageIndex 420
NavigatingCancelEventArgs 205 PageResourceContentLoader 209
NavigationService 209 Panel 54
NavigationStopped 205 Paragraph 144
NaviguerVersPage 199 param 170
NewValue 81 Parts 165
NoDescription 391 PasswordBox 137
NonZero 223 PasswordChar 139
Not3DCapable 466 Path 49, 224
NotifyOnValidationError 393 PathFigure 226
PathFigureCollection 226
Index 511
PathGeometry 224 PropertyGroupDescription 417
PathSegment 226 PropertyMetadata 80
PathSegmentCollection 226 PropertyPath 277
Pause 271 propriété attachée 84
PenLineCap 219 propriétés 37
perspective 245 propriétés attachées 40
PieSeries 458 propriétés complexes 39
pinceau vidéo 341 propriétés de dépendances 74
PixelHeight 345 propriété simple 76
Pixels 266 propriétés implicites 41
pixel shader 262 propriétés simples 38
PixelShader 464
PixelWidth 345 Q
Placement 124 QuadraticBezierSegment 226, 230
PlacementTarget 124 QuadraticEase 284
PlaneProjection 245 QuarticEase 284
PlatformKeyCode 94 QuinticEase 284
plug-in 54
Point1 230 R
Point2 230
Point3 230 RadialGradientBrush 250, 253
PointAnimation 270 RadioButton 118
PointAnimationUsingKeyFrames 270, 301 RadiusX 217
PointCollection 221 RadiusY 217
PointKeyFrame 301 RangeBase 154
Points 221 RasterizerState 478
PolyBezierSegment 226 readonly 76
Polygon 222 Rectangle 74, 217
Polyline 221 RectangleGeometry 224
PolylineSegment 226 RegisterAttached 85
PolyQuadraticBezierSegment 226 ReleaseMouseCapture 101, 103
Populated 141 RenderAtScale 325
Populating 142 Rendering 312
Popup 126 RenderMode 466
Power 291 RenderModeReason 466
PowerEase 284, 291 RenderTransform 237
profondeur 67 RepeatBehavior 272
ProgressBar 155 RepeatButton 118, 158
ProgressPercentage 183 RequiredAttribute 382
projection parallèle 474 Resource 178
projection perspective 474 ResourceDictionary 47
projet hébergé 26 Resources 169
Property 352 ressources 43
propertyChangedCallback 80 Resume 271
PropertyChangedEventArgs 392 RichTextBlock 153
512 Index
RichTextBlockOverflow 153 ShowGridLines 59
RichTextBox 137, 146 SineEase 284
RootVisual 167, 169 site web ASP.NET 20
RotateTransform 235, 239 site web ordinaire 20
RotationAngle 229 SizeChanged 91
RotationX 245 SkewTransform 235, 240
RotationY 245 Slider 157
RotationZ 245 SmallChange 155
Round 219, 221 Software Development Kit 18
RoutedEventArgs 92 SolidColorBrush 250, 251
Row 414 SortDescription 418
RowBackground 404 source 31
RowDefinition 58 SourceName 341
RowDefinitions 58 Span 112
RowDetailsVisibilityChanged 415 SpeedRatio 272
RowDetailsVisibilityMode 415 splash screen 175
RowHeight 404 splashScreenSource 32
Run 112 SplineColorKeyFrame 302
RuntimeVersion 166 Springiness 290
RVB prémultipliées 265 Square 219
StackPanel 54, 57
S StartPoint 229, 254
SamplerState 488 Startup 167, 170
ScaleTransform 235 StartupEventArgs 167
ScaleX 237 State 349
ScaleY 237 StaticResource 356
ScrollViewer 69 Stop 271
SecurityBlocked 466 Storyboard 270
SelectedDatesChanged 160 Storyboard.SetTarget 277
SelectedItem 133 Storyboard.SetTargetProperty 277
SelectedItemChanged 427 Storyboard.TargetName 275
SelectedText 138 Storyboard.TargetProperty 275
SelectionBackground 139 StreamResourceInfo 180
SelectionChanged 133 Stretch 58, 217
SelectionLength 138 stretching 58
SelectionStart 138 StringLengthAttribute 382, 385
Selector 132 Stroke 216
Setter 352 StrokeDashArray 220
SetVertexBuffer 477 StrokeDashCap 220
SetWindow 126 StrokeDashOffset 220
ShaderEffect 259 StrokeEndLineCap 219
ShadowDepth 260 StrokeLineJoin 221
Shape 216 StrokeStartLineCap 219
Show 204 StrokeThickness 75, 216
style 352
Index 513
style implicite 355 TranslateTransform 235
SweepDirection 229 TreeView 424
système de disposition 54 TreeViewItem 424
Triangle 219
T ttf 114
TabControl 134 TwoWay 391
TabIndex 104 U
TabItem 134
TabStripPlacement 136 UIElement 54, 89
Tap 90 Unchecked 120
Target 381 Underline 112
TargetName 119 UnhandledException 168, 170
TargetType 353 Uniform 70, 115
template 362 Uniform Resource Identifier 168
Template 362 UniformToFill 116
TemplateBinding 365 Unknown 93
TemplateVisualState 370 Unloaded 91
TemporarilyUnavailable 466 UnloadingRow 404
Text 112 UpOnly 70
TextAlignment 110 UriMapper 207
TextBlock 110 UriMapping 207
TextBox 137 UriMappings 207
TextChanged 138 UriSource 114
TextDecorations 110, 112
TextElement 143 V
texte statique 110 ValidatesOnExceptions 393
TextInput 90 ValidationAttribute 394
TextInputStart 90 ValidationContext 393
TextInputUpdate 90 ValidationException 394
TextSelection 150 ValidationResult 397
Texture2D 487 ValidationStates 391
TextWrapping 110 ValidationSummary 398
Thumb 157 Validator 394
Tick 340 ValidFocused 391
Timeline 271 ValidUnfocused 391
TimeSpan 272 Value 155, 352
To 300 ValueChanged 155
ToggleButton 118 ValueMemberBinding 141
ToolTip 123, 124 ValueMemberPath 141
ToolTipService 123 Vector2 485
Track 157 Vector3 471
Transform 236 VertexBuffer 470
TransformGroup 235, 241 VertexPositionColor 472
Transitions 375 VertexPositionNormalTexture 491
514 Index
VertexPositionTexture 486 x:Name 36
VertexShader 464
VerticalAlignment 58 Y
VerticalContentAlignment 118 Y1 219
VerticalOffset 126 Y2 219
VerticalScrollBarVisibility 71, 137
VideoBrush 250, 341 Z
VideoCaptureDevice 345, 346
VideoFormat 345 ZIndex 67
vidéo réfléchie 343
Viewbox 69
visibilité 68
Visibility 68
Visible 68
VisibleWhenSelected 415
VisualState 370
VisualStateGroup 370, 375
VisualStateManager 370
VisualTransition 375
Volume 332

W
Watermark 138
WebBrowserBrush 250
WebClient 182
Width 217
windowless 32
Windows Media Audio 328
Windows Media Video 328
WireFrame 479
WMA 328
WrapPanel 54, 63
wrapper 85
WriteableBitmap 263

X
X1 219
X2 219
XAML 34
XML 34
xmlns 78
XmlReader 185
XmlReader.Create 185
XNA 464

Vous aimerez peut-être aussi