Académique Documents
Professionnel Documents
Culture Documents
La
Programmation
Graphique 2D
de WPF 4
Avec l’environnement
de développement
.NET Framework 4 et
Visual Studio 2010
de MICROSOFT
La
programmation
graphique 2D
de
WPF 4
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.
É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-1199-7
Dépôt légal : Juin 2011
Auteur
Patrice REY
85 rue de vincennes
App 23 B
33000 BORDEAUX
e-mail : reypatrice@orange.fr
Table des matières
Avant-propos .......................................................................................... 7
Annexes
1. Liste des UserControl réalisés ........................................................... 275
2. Vue sur l’organisation du programme ............................................ 277
Pour réaliser plus facilement des programmes en 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
10 Avant-propos
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.
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 (figure A1): 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 (figure A2): http://www.
microsoft.com/france/visualstudio/download
Figure A1
Figure A2
Avant-propos 11
Le contenu du livre
1 – Le système de coordonnées 2D
Quand vous créez un objet graphique dans WPF, vous devez définir où il
sera visualisé (à l’écran, vers une imprimante, à destination d’un fichier PDF).
Pour cela, vous devez connaître la façon dont WPF affiche les coordonnées
des objets graphiques. Chaque point, dans une fenêtre ou dans une page
WPF, possède une abscisse X et une ordonnée Y. Nous allons voir différents
systèmes de coordonnées.
Canvas représentant
une zone de rendu
point A(4,5)
axe Y
n x_radbtn_option1
14 La programmation graphique 2D de WPF 4
L’abscisse (axe des X) est horizontale et dirigée vers la droite. L’ordonnée (axe
des Y) est verticale et dirigée vers le bas.
Les coordonnées 2D sont gérées dans WPF par des propriétés de type double
représentant une position (X, Y, Top, Left) ou une dimension (Width, Height).
Dans un système vectoriel, le type double offre un niveau de précision parfait.
La structure Point permet de stocker les coordonnées d’un point qu’elle expose
dans ses propriétés X et Y. La structure Rect permet de définir une surface
rectangulaire et elle expose le coin supérieur gauche dans ses propriétés X et
Y, la largeur dans sa propriété Width et la hauteur dans sa propriété Height.
L’unité de mesure 2D de WPF est le Device Independant Pixel (DIP), c’est-à-
dire un pixel logique indépendant de la résolution du périphérique d’affichage
(écran ou imprimante). Le DIP est une unité de mesure qui accepte les valeurs
décimales.
Chaque périphérique d’affichage, que ce soit un écran ou une imprimante
par exemple, possède une définition et une densité. La définition représente le
nombre de points de la matrice d’affichage (par exemple un écran de 1280
par 1024 comporte 1280 fois 1024 points). La densité représente la densité des
points par unité de mesure. Elle s’exprime en nombre de points par pouce et
peut utiliser trois unités équivalentes : le PPP (Points Par Pouce), le DPI (Dot Per
Inch) ou le PPI (Pixel Per Inch). Rappelons qu’un pouce est égal à 2,54 cm.
Quel que soit la densité du périphérique d’affichage, 1 DIP correspond à
1/96ème de pouce et donc 96 DIP correspondent à 1 pouce. Sur un périphérique
dont la densité est de 96 pixels par pouce, 1 DIP correspond donc à 1 pixel
physique. Sur un périphérique dont la densité est de 120 pixels par pouce, 96
DIP correspondent à 120 pixels physiques, donc 1 DIP correspond à 1,25 pixel
physique.
Dans le dossier chapitre01, le contrôle utilisateur SystemeCoordonneeDefaut.
xaml (figure 1-2) dessine dans un Canvas x_cnv_appli une ligne x_ligne_defaut
de type Line, avec un point de départ de coordonnées X1=50 et Y1=50, et
un point d’arrivée de coordonnées X2=150 et Y2=150. L’objet Line expose la
propriété Stroke pour indiquer la couleur de la ligne dessinée et la propriété
Copyright 2011 Patrice REY
Figure 1-2
ml
n1
n2
16 La programmation graphique 2D de WPF 4
<Canvas Height='238' Name='x_cnv_appli' Width='744'
Background='WhiteSmoke' HorizontalAlignment='Left'
VerticalAlignment='Top'>
<Line x:Name='x_ligne_defaut' X1='50' Y1='50' X2='150' Y2='150'
Stroke='Black' StrokeThickness='2'></Line>
<Line x:Name='x_ligne_personnalise' X1='3.5in' Y1='1.5cm' X2='520'
Y2='155pt' Stroke='Blue' StrokeThickness='0.1cm'></Line>
</Canvas>
Avoir l’origine du repère en haut à gauche n’est pas toujours très pratique,
notamment quand on veut dessiner la représentation de l’équation d’une
courbe ou bien un graphique à barres. Sur la figure 1-3, le point A de
coordonnées X=4 et Y=5 se situe à droite et en haut par rapport à l’origine.
L’origine du Canvas a changé d’emplacement, passant du coin haut gauche au
coin bas gauche.
Figure 1-3
axe Y
point A(4,5)
Canvas représentant
une zone de rendu
Origine O(0,0)
axe X
ml
n1
n2
Le zoom permet d’agrandir ou de réduire une zone à l’écran pour mieux voir
les détails. Zoomer un ensemble de figures qui sont dessinées dans un Canvas
consiste tout simplement à effectuer une transformation de mise à l’échelle en
appliquant un facteur d’échelle identique dans les deux directions.
Dans le dossier chapitre01, le contrôle utilisateur ZoomerFigure.xaml (figure
1-5) affiche dans un Canvas x_cnv_appli une ligne rouge x_ligne_defaut et un
rectangle x_rectangle rempli par un dégradé de couleurs.
On positionne un contrôle Slider x_slider pour faire varier le zoom d’une valeur
allant de 0 à 3 et avec une valeur de départ fixée à 1. Un TextBlock x_text_zoom
permet d’afficher la valeur du x_slider. Un gestionnaire d’événement est ajouté
au x_slider pour gérer le changement de sa valeur (événement ValueChanged).
A noter qu’il faut positionner le TextBlock avant le Slider en xaml, car dans le
cas contraire, l’événement ValueChanged du x_slider génèrera une erreur lors
de l’appel de la méthode d’initialisation InitializeComponent().
20 La programmation graphique 2D de WPF 4
Figure 1-5
ml
n1
n2
ClipToBounds=
«False»
ClipToBounds=
«True»
CHAPITRE 1 □ Les bases du graphisme 2D 21
<!-- slider de zoom -->
<TextBlock Canvas.Left='659' Canvas.Top='340' Height='23'
Name='x_text_zoom' Text='zoom = 1' FontWeight='Bold'
FontSize='14' TextAlignment='Center' Width='99'></TextBlock>
<Slider Name='x_slider' Minimum='0' Maximum='3' Value='1'
TickPlacement='BottomRight' TickFrequency='0.2'
IsSnapToTickEnabled='True' Canvas.Left='157' Canvas.Top='334'
Width='496' Background='DarkOrange'
Height='33' Cursor='Hand'
ValueChanged='x_slider_ValueChanged'>
</Slider>
Quand la valeur du Slider est fixée au départ, puis est modifiée par la suite, il
faut que le rectangle et la ligne s’agrandissent ou se réduisent en fonction du
facteur d’échelle correspondant à la valeur du zoom.
On commence par ajouter au rectangle et à la ligne, une transformation
ScaleTransform. Puis il faut relier le facteur d’échelle ScaleX et le facteur
d’échelle ScaleY à la valeur en cours du Slider. On utilise donc un objet Binding
(dans l’espace de noms System.Windows.Data) qui fournit un accès de niveau
supérieur à la définition d’une liaison, qui connecte les propriétés d’objets de
cible de liaison (en général, des éléments WPF) et toute source de données (par
exemple, une base de données, un fichier XML ou tout objet qui contient des
données).
La propriété ElementName définit le nom de l’élément à utiliser comme objet
de source de liaison (ici ElementName=x_slider) et la propriété Path affecte au
chemin d’accès la propriété de source de liaison (ici Path=Value puisque Value
est la propriété du Slider qui retourne la valeur en cours du contrôle).
De ce fait, ScaleX et ScaleY sont reliés en permanence à la propriété Value du
x_slider.
ml
n1
n2
(m_x_min,m_y_min) (m_x_max,m_y_min)
24 La programmation graphique 2D de WPF 4
Puis nous allons réaliser l’histogramme de cette image c’est-à-dire compter
pour chaque niveau de gris le nombre de pixels trouvés. Enfin, afficher dans
un Canvas x_cnv_appli cet histogramme. Le haut de la figure 1-6 visualise le
résultat obtenu.
Une image en 256 niveaux de gris signifie que chaque pixel de l’image possède
une valeur comprise entre 0 et 255. Le type intégral byte représente un entier
8 bits non signé dont la plage de valeur est comprise entre 0 et 255. Nous
commençons par créer un tableau tab_pixel de 256 lignes et 256 colonnes que
nous remplissons aléatoirement par une couleur coul_gris de type byte.
PixelFormat pf = PixelFormats.Gray8;
BitmapSource bitmap_creer = BitmapSource.Create(256, 256,72, 72, pf,
null, data, 256);
x_img.Source = bitmap_creer;
m_x_min = 0;
m_x_max = 255;
m_y_min = 0;
m_y_max = 350;
//affichage des lignes rouges sur le canvas
for (int lig = 0; lig < 256; lig++) {
Point pt_1 = new Point(lig, 0);
Point pt_2 = new Point(lig, histogramme[lig, 0]);
Line line = new Line();
line.X1 = XNormalise(pt_1.X);
line.Y1 = YNormalise(pt_1.Y);
line.X2 = XNormalise(pt_2.X);
line.Y2 = YNormalise(pt_2.Y);
line.Stroke = new SolidColorBrush(Colors.DarkGray);
line.StrokeThickness = 2;
x_cnv_appli.Children.Add(line);
}
//normalisation de la coordonnee x
private double XNormalise(double x) {
double result = (x - m_x_min) * x_cnv_appli.Width / (m_x_max -
m_x_min);
return result;
}
//normalisation de la coordonnee y
private double YNormalise(double y) {
Copyright 2011 Patrice REY
Un objet Line dessine une droite reliant deux points donnés. Ses propriétés de
dépendance sont X1 et Y1 pour spécifier le point d’origine de la ligne, et X2
et Y2 pour spécifier le point de terminaison de la ligne. Ces propriétés sont de
type double. Pour qu’une ligne soit affichée, il faut que ses propriétés Stroke et
StrokeThickness soient définies.
Copyright 2011 Patrice REY
ml
n1
n2
30 La programmation graphique 2D de WPF 4
Figure 1-9
ml
n1
n2
d’un espace de longueur 2, suivi d’un deuxième tiret de longueur 3, suivi d’un
espace de longueur 1.
La propriété StrokeDashCap spécifie la manière dont les extrémités d’un tiret
sont dessinées, par l’énumération PenLineCap.
Une ligne Line peut être dessinée par code de programmation. Le bas de la
figure 1-9 montre un exemple de ligne noire en pointillé exprimée par code.
CHAPITRE 1 □ Les bases du graphisme 2D 31
2.2 - Les classes Rectangle et Ellipse
ml
n1
n2
ml
n1
n2
ml
n1
n2
ml
n1
n2
36 La programmation graphique 2D de WPF 4
La classe Polygon dessine un polygone qui est une série connectée de lignes qui
forment une figure fermée. Elle fonctionne de façon identique à la classe Polyline.
Les points des sommets sont spécifiés, avec leurs coordonnées x et y, dans la
propriété Points de type PointCollection. Un segment relie automatiquement le
dernier point au premier point pour fermer la figure. L’intérieur de la figure est
rempli par l’intermédiaire de la couleur affectée à la propriété Fill.
Dans le dossier chapitre01, le contrôle utilisateur DemoPolygone.xaml (figure
1-14) visualise différents polygones.
Un polygone basique est représenté par l’objet x_polygon_1. Les propriétés
Stroke, StrokeThickness, Fill et Points doivent être définies.
Dans le bas de la figure 1-14, on affecte à la propriété Points d’un polygone une
collection de points qui simule une sinusoïde. Cela est possible par code en
calculant tous les points d’une sinusoïde sur un intervalle donné. Puis le dernier
point de cette collection est relié automatiquement au premier point par un
segment de droite. Les méthodes statiques SetLeft(..) et SetTop(..) permettent
de positionner le polygone dans son conteneur parent qui est ici x_cnv_appli.
Puis la méthode Add(..) ajoute le polygone à l’arbre visuel du Canvas par sa
propriété Children.
Les polygones x_polygon_2_gauche et x_polygon_2_droite sont identiques mais
avec une propriété FillRule différente. Le premier a la propriété FillRule égale
à l’énumération FilleRule.EvenOdd, le deuxième avec l’énumération FillRule.
NonZero.
Comme 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.
Le comportement par défaut est FillRule.EvenOdd et spécifie que la zone est
colorée si elle est séparée de l’extérieur de la figure par un nombre impair de
segments. FillRule.NonZero spécifie que la zone est colorée en fonction de la
Copyright 2011 Patrice REY
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. La
figure 1-15 illustre les zones de remplissage selon les 2 cas de l’énumération.
CHAPITRE 1 □ Les bases du graphisme 2D 37
Figure 1-14
ml
n1
n2
38 La programmation graphique 2D de WPF 4
Figure 1-15
1 8 1 10
5 4 6 7
8
6
7 5 4 9
1 2 3 2 3
2
1 segment trouvé
2 segments trouvés
Les transformations 2D
Les vecteurs et les matrices jouent un rôle important dans le processus des
transformations. Quand une zone graphique contient plusieurs objets et que
l’on souhaite bouger par exemple un seul de ces objets, il est alors très pratique
d’appliquer une transformation à cet objet, et cela sans toucher aux autres.
A2 (x2,y2)
A1 (x1,y1) V12
1
2
V1
V2
X
O (0,0)
Sx 0
( x1 y1 ) = ( x y ) = ( Sxx Syy )
0 Sy
CHAPITRE 2 □ Les transformations 2D 41
La figure 2-2 visualise différentes transformations de mise à l’échelle par des
matrices de transformation (agrandissement et réduction).
Figure 2-2
Y Y
0.5 0
0 0.5
X X
O O
Y Y
0.5 0
0 2
X X
O O
L’effet miroir selon l’axe des X est obtenu par la matrice Sx=-1 et Sy=1. L’effet
miroir selon l’axe des Y est obtenu par la matrice Sx=1 et Sy=-1. La figure 2-3
visualise les effets miroir à axe vertical et horizontal.
On emploie aussi le terme de réflexion, dans le jargon informatique, pour
parler de l’effet miroir.
42 La programmation graphique 2D de WPF 4
Figure 2-3
-1 0
0 1
1 0
X
O 0 -1
1.3 - Rotation
Une rotation est une transformation géométrique qui consiste à faire tourner
à partir d’un point et selon un angle. Un point A1 se trouvant à une distance r
de l’origine O (figure 2-4) a comme coordonnées x1 et y1 :
Copyright 2011 Patrice REY
x1 = r cos(α)
y1 = r sin(α)
Si A2 est un point correspondant au point A1 après une rotation d’un angle α,
alors A2 aura comme coordonnées x2 et y2 :
x2 = r cos(α+β) = r cos(α)cos(β) - r sin(α)sin(β)
y2 = r sin(α+β) = r sin(α)cos(β) - r cos(α)sin(β)
CHAPITRE 2 □ Les transformations 2D 43
En substituant dans cette équation r cos(α) = x1 et r sin(α) = y1, on obtient :
x2 = x1 cos(β) - y1 sin(β)
y2 = x1 sin(β) + y1 cos(β)
Figure 2-4
A2 (x2,y2)
r sin(α+β)
β A1 (x1,y1)
r sin(α)
α
X
O (0,0) r cos(α+β) r cos(α)
Sous forme de matrice, un point (x1,y1) donnera un point (x2,y2) par rotation
d’un angle β en appliquant la matrice de transformation suivante:
cos(β) sin(β)
Rotation (angle β)=
-sin(β) cos(β)
1.4 - Translation
A1 (x1,y1)
dx
dy
A2 (x2,y2)
X
O (0,0)
1 0 0
( x2 y2 1 ) = ( x1 y1 1 ) 0 1 0
dx dy 1
A3=A1.T(dx1,dy1).T(dx2,dy2)
A3=(A1.S(Sx1,Sy1)).S(Sx2,Sy2)
suivante:
cos(β) sin(β) 0
( x2 y2 1 ) = ( x1 y1 1 ) -sin(β) cos(β) 0
0 0 1
CHAPITRE 2 □ Les transformations 2D 47
Cette équation s’écrit aussi A2=A1.R(β) où R(β) est une matrice de rotation en
coordonnées homogènes.
Si on effectue deux rotations successives à un point A1 pour obtenir un point
A3, on a A2=A1. R(β1) et A3=A2. R(β2) ce qui donne en substituant un terme
cos(β1)cos(β2)-sin(β1)sin(β2) cos(β1)sin(β2)+sin(β1)cos(β2) 0
= -sin(β1)cos(β2)-cos(β1)sin(β2) cos(β1)cos(β2)-sin(β1)sin(β2) 0
0 0 1
cos(β1+β2) sin(β1+β2) 0
= -sin(β1+β2) cos(β1+β2) 0
0 0 1
Y Y
A A
X X
O O
au début on a cette on effectue une
configuration translation
Y Y
A A
α
X X
O O
Pour ces différentes étapes, on effectue une translation T(-x1,-y1), puis une
rotation d’un angle R(α), enfin une translation inverse de T(x1,y1).
1 0 0 cos(α) sin(α) 0 1 0 0
0 1 0 -sin(α) cos(α) 0 0 1 0
-x1 -y1 1 0 0 1 x1 y1 1
cos(α) -sin(α) 0
= sin(α) cos(α) 0
x1.(1-cos(α)) + y1.sin(α) y1.(1-cos(α)) - x1.sin(α) 1
Dans WPF, un vecteur est défini par une structure de type Vector. Les structures
et les classes se ressemblent de par leur nature, avec une différence principale
qui fait qu’une structure est un type valeur et une classe est un type référence.
Un type valeur est stocké directement dans la pile alors qu’un type référence
stocke une référence à un objet alloué dynamiquement.
Généralement les structures ont un avantage de performance sur les
classes parce qu’elles sont allouées directement sur la pile et sont retirées
immédiatement de la pile dès qu’elles ne sont plus utilisées. L’inconvénient
d’une structure est qu’elle ne peut pas hériter comme une classe. De
nombreuses fonctions mathématiques sont définies sous forme de structure à
cause de leur avantage de performance.
La classe Vector est définie dans l’espace de noms System.Windows. Un objet
Vector est un tableau de données à une ligne avec trois coordonnées homogènes.
Comme la troisième coordonnée est égale à 1, il est juste nécessaire de fournir
les deux premières coordonnées. Par exemple on instancie un vecteur par
Vector vecteur=new Vector(5,10).
Un vecteur peut être défini aussi en utilisant des points. Les écritures suivantes
sont valides:
50 La programmation graphique 2D de WPF 4
Vector v1 = new Point(5, 10) - new Point(25, 30);
Vector v2 = (Vector)new Point(5, 15);
Point pt1 = (Point)new Vector(5, 15);
M11 M12
M21 M22
OffsetX OffsetY
La matrice unité, avec une valeur de 1 pour les cellules (1,1) et (2,2), et une
valeur de 0 pour les autres cellules, peut être obtenue au moyen de la propriété
statique Matrix.Identity. La construction d’une structure Matrix est par défaut
52 La programmation graphique 2D de WPF 4
une structure Matrix.Identity.
On peut noter que les membres de la structure Matrix correspondent aux
paramètres des transformations de haut niveau comme 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,
• M12 est le facteur d’inclinaison sur l’axe des Y,
• OffsetX est le déplacement en DIP sur l’axe des X,
• OffsetY est le déplacement en DIP sur l’axe des Y.
Dans les méthodes et les propriétés, la matrice de transformation est
généralement spécifiée comme un vecteur à six membres seulement, comme
suit : (M11, M12, M21, M22, OffsetX, OffsetY).
Un exemple de matrice de type translation matrice_translation qui déplace une
figure de 3 unités sur l’axe X et de 2 unités sur l’axe Y:
double dx = 3;
double dy = 2;
Matrix matrice_translation = new Matrix(1, 0, 0, 1, dx, dy);
M12
M11
1 0 0
M21 M22
0 1 0
dx dy 1
OffsetX
OffsetY
double sx = 0.5;
double sy = 1.5;
Matrix matrice_mise_a_echelle = new Matrix(sx, 0, 0, sy, 0, 0);
CHAPITRE 2 □ Les transformations 2D 53
Matrix matrice_mise_a_echelle = new Matrix(sx, 0, 0, sy, 0, 0)
M12
M11
Sx 0 0
M21 M22
0 Sy 0
0 0 1
OffsetX
OffsetY
Un exemple de matrice de type rotation matrice_rotation qui fait tourner une
figure de 45 degrés par rapport à l’origine du repère:
M12
M11
cos sin 0
M21 M22
-sin cos 0
0 0 1
OffsetX
OffsetY
-2 + 2*1.5 1 - 2*0.5 0 1 0 0
= -2*3 + 4*1.5 3 - 4*0.5 0 = 0 1 0
0 0 1 0 0 1
Examinons la multiplication des matrices M1 par M2. Nous allons voir que la
multiplication de matrice n’est pas commutative c’est-à-dire que M1*M2 est
différent de M2*M1.
// multiplication de matrices
AjouterTexte(«-----------------------------------------------»);
Matrix m1 = new Matrix(1, 2, 3, 4, 0, 1);
AjouterTexte(«matrice M1 -> (« + m1.ToString() + «)»);
Matrix m2 = new Matrix(0, 1, 2, 1, 0, 1);
AjouterTexte(«matrice M2 -> (« + m2.ToString() + «)»);
Matrix m12 = Matrix.Multiply(m1, m2);
AjouterTexte(«matrice M1*M2 -> (« + m12.ToString() + «)»);
Matrix m21 = Matrix.Multiply(m2, m1);
AjouterTexte(«matrice M2*M1 -> (« + m21.ToString() + «)»);
1 2 0 0 1 0 4 3 0
M1*M2 3 4 0 2 1 0 = 8 7 0
0 1 1 0 1 1 2 2 1
0 1 0 1 2 0 3 4 0
M2*M1 2 1 0 3 4 0 = 5 8 0
0 1 1 0 1 1 3 5 1
CHAPITRE 2 □ Les transformations 2D 57
La structure Matrix dans WPF fournit des méthodes pour la mise à l’échelle, pour la
rotation et pour la translation des matrices. L’UserControl TransformationMatrice.
xaml dans le dossier chapitre02 visualise des transformations effectuées sur les
matrices (figure 2-8).
Figure 2-8
//matrice de départ
Matrix matrice_depart = new Matrix(1, 2, 3, 4, 0, 1);
AjouterTexte(«matrice de départ => (« + matrice_depart.ToString() +
«)»);
//mise a l’échelle
AjouterTexte(«Mise à l’échelle -----------------------------»);
Matrix matrice_echelle = matrice_depart;
matrice_echelle.Scale(1, 0.5);
AjouterTexte(«mise à l’échelle (Scale) => (« +
matrice_echelle.ToString() + «)»);
Matrix matrice_echelle_prepend = matrice_depart;
matrice_echelle_prepend.ScalePrepend(1, 0.5);
AjouterTexte(«mise à l’échelle (ScalePrepend) => (« +
matrice_echelle_prepend.ToString() + «)»);
Le résultat obtenu est une matrice (1,1,3,2,0,0.5) par Scale(..) et une matrice
(1,2,1.5,2,0,1) par ScalePrepend (..). Une vérification par le calcul donne :
matrice de matrice de mise matrice
départ à l’échelle résultante
1 2 1 0 1 1
3 4 0 0.5 = 3 2
0 1 0 0 0 0.5
//translation
AjouterTexte(«Translation ----------------------------------»);
Matrix matrice_translation = matrice_depart;
matrice_translation.Translate(1, 0.5);
AjouterTexte(«translation (Translate) => (« +
matrice_translation.ToString() + «)»);
Matrix matrice_translation_prepend = matrice_depart;
matrice_translation_prepend.TranslatePrepend(1, 0.5);
AjouterTexte(«translation (TranslatePrepend) => (« +
matrice_translation_prepend.ToString() + «)»);
1 2 1 0 1 2
3 4 0 1 = 3 4
0 1 1 0.5 1 1.5
Les méthodes Rotate(..) et RotateAt(..) sont utilisées pour faire tourner une
figure. La méthode Rotate(..) applique une rotation d’un angle spécifié par
rapport à l’origine d’une structure Matrix. Elle prend en paramètre un angle
de type double. La méthode RotateAt(..) fait pivoter une matrice par rapport au
point spécifié. Elle prend en paramètre l’angle de rotation, l’abscisse du centre
de la rotation et l’ordonnée du centre de la rotation (abscisse et ordonnée de
type double).
//rotation
AjouterTexte(«Rotation ----------------------------------»);
60 La programmation graphique 2D de WPF 4
Matrix matrice_rotation = matrice_depart;
matrice_rotation.Rotate(45);
AjouterTexte(«rotation (Rotate) => (« +
ArrondirMatrice(matrice_rotation).ToString() + «)»);
Matrix matrice_rotation_prepend = matrice_depart;
matrice_rotation_prepend.RotatePrepend(45);
AjouterTexte(«rotation (RotatePrepend) => (« +
ArrondirMatrice(matrice_rotation_prepend).ToString() + «)»);
= ( x1 + y1*tan(angleX) x1*tan(angleY) + y1 1)
Nous effectuons ici une inclinaison de 45 degrés suivant X et de 30 degrés
suivant Y. Le calcul des matrices de transformation
( x1 + y1*tan(angleX) donne:
x1*tan(angleY) + y1 1 )
matrice de matrice
départ d’inclinaison
1 tan(30) 1 2 1 0.577
1 2
tan(45) 1 = 3 4 1 1
3 4
0 0 0 1 0 0
0 1
3 2.577
matrice
= 7 5.732
résultante
1 1
Par programme, nous obtenons le même résultat, ce qui est normal (toujours
à l’arrondi près).
//inclinaison
AjouterTexte(«Inclinaison -----------------------»);
Matrix matrice_inclinaison = matrice_depart;
matrice_inclinaison.Skew(45, 30);
AjouterTexte(«inclinaison (Skew) => (« +
ArrondirMatrice(matrice_inclinaison).ToString() + «)»);
Matrix matrice_inclinaison_prepend = matrice_depart;
matrice_inclinaison_prepend.SkewPrepend(45, 30);
AjouterTexte(«inclinaison (SkewPrepend) => (« +
ArrondirMatrice(matrice_inclinaison_prepend).ToString() + «)»);
Copyright 2011 Patrice REY
On commence par dessiner une ligne m_ligne_1 de type Line, de couleur bleue
et d’épaisseur 4, entre un point de début pt1 de coordonnées (50,50) et un
point de terminaison pt2 de coordonnées (300,200). Cette ligne est ajoutée
au Canvas x_cnv_appli. Les affichages, au format texte des coordonnées des
points, se font par l’intermédiaire de TextBlock m_texte_pt1 et m_texte_pt2.
v1
O (0,0)
X
pt1 (50,50)
v1 normalisé *100
v1 normalisé
pt3 (249,286)
//calcul de la perpendiculaire
Vector v1 = pt1 - pt2;
Matrix m1 = new Matrix();
Point pt3 = new Point();
Point pt4 = new Point();
m1.Rotate(-90);
66 La programmation graphique 2D de WPF 4
v1.Normalize();
v1 *= 100;
m_ligne_2 = new Line();
m_ligne_2.Stroke = Brushes.Red;
m_ligne_2.StrokeThickness = 4;
m_ligne_2.StrokeDashArray = DoubleCollection.Parse(«1,1»);
pt3 = pt2 + v1 * m1;
m1 = new Matrix();
m1.Rotate(90);
pt4 = pt2 + v1 * m1;
m_ligne_2.X1 = pt3.X;
m_ligne_2.Y1 = pt3.Y;
m_ligne_2.X2 = pt4.X;
m_ligne_2.Y2 = pt4.Y;
x_cnv_appli.Children.Add(m_ligne_2);
pt3.X = Math.Round(pt3.X, 0);
pt3.Y = Math.Round(pt3.Y, 0);
pt4.X = Math.Round(pt4.X, 0);
pt4.Y = Math.Round(pt4.Y, 0);
m_texte_pt3 = new TextBlock();
m_texte_pt3.FontSize = 14;
Canvas.SetLeft(m_texte_pt3, pt3.X);
Canvas.SetTop(m_texte_pt3, pt3.Y );
m_texte_pt3.Text = «Point3 (« + pt3.X.ToString() + «,» +
pt3.Y.ToString() + «)»;
x_cnv_appli.Children.Add(m_texte_pt3);
m_texte_pt4 = new TextBlock();
m_texte_pt4.FontSize = 14;
Canvas.SetLeft(m_texte_pt4, pt4.X);
Canvas.SetTop(m_texte_pt4, pt4.Y);
m_texte_pt4.Text = «Point4 (« + pt4.X.ToString() + «,» +
pt4.Y.ToString() + «)»;
x_cnv_appli.Children.Add(m_texte_pt4);
<Rectangle.RenderTransform>
<MatrixTransform x:Name='x_matrix_transform' />
</Rectangle.RenderTransform>
</Rectangle>
</TranslateTransform>
</Rectangle.RenderTransform>
</Rectangle>
D ans ce chapitre nous allons voir comment créer et manipuler des objets
graphiques 2D complexes par l’utilisation d’une importante et puissante
classe qu’est la classe Path. Nous verrons comment développer des figures 2D
de façon interactive, en utilisant la classe Geometry. Nous aborderons à la fin
de ce chapitre la façon de réaliser des figures personnalisées, réutilisables dans
toute application WPF.
de la même manière que les classes Line, Rectangle et Ellipse vues dans les
précédents chapitres.
En xaml, une ligne de type Line peut être dessinée par un objet Path dont sa
propriété Data sera affectée d’un objet LineGeometry:
La principale différence est qu’une figure de type Line expose des propriétés
X1, X2, Y1 et Y2 alors qu’une géométrie de type LineGeometry expose des
propriétés StartPoint et EndPoint.
On procède de façon similaire pour un rectangle. Un rectangle de type Rectangle
peut être dessiné par un objet Path dont sa propriété Data sera affectée d’un
objet RectangleGeometry:
On voit bien que l’objet Rectangle expose les propriétés Width et Height, alors
que RectangleGeometry expose la propriété Rect (composée de 4 valeurs qui
correspondent, pour les deux premières aux coordonnées du coin supérieur
haut gauche, et pour les deux dernières à la largeur et la hauteur). Il en est de
même pour une ellipse:
Au lieu d’utiliser deux éléments Path avec dans chacun d’eux, un objet
EllipseGeometry, avec un GeometryGroup, on les regroupe ensemble. Il faut voir
aussi qu’avec ce type de code xaml, on a la possibilité de le réutiliser dans
d’autres parties du programme, et aussi de le mettre comme une ressource
réutilisable.
L’UserControl ClasseGeometryGroup.xaml dans le dossier chapitre03 illustre
l’utilisation d’un objet GeometryGroup (figure 3-2).
Copyright 2011 Patrice REY
<UserControl.Resources>
...
<GeometryGroup x:Key='r_GeometryNonzero' FillRule='Nonzero'>
<EllipseGeometry RadiusX='50' RadiusY='50' Center='65,60' />
<EllipseGeometry RadiusX='30' RadiusY='30' Center='65,60' />
</GeometryGroup>
<GeometryGroup x:Key='r_GeometryEvenOdd' FillRule='EvenOdd'>
<EllipseGeometry RadiusX='50' RadiusY='50' Center='65,60' />
<EllipseGeometry RadiusX='30' RadiusY='30' Center='65,60' />
</GeometryGroup>
</UserControl.Resources>
90 La programmation graphique 2D de WPF 4
Sur le Canvas x_cnv_appli, on ajoute un objet Path, rempli par la couleur bleue
et avec une géométrie de type r_GeometryNonZero affectée à sa propriété
Data, puis un autre objet Path, rempli par la couleur or et avec une géométrie
de type r_GeometryNonZero affectée à sa propriété Data. Pour utiliser une
ressource, il faut lui faire référence par l’intermédiaire de StaticResource
(on écrit Data=»{StaticResource r_GeometryNonzero}»). On effectue la même
démarche pour créer deux autres objets Path avec une géométrie de type r_
GeometryEvenOdd.
Dans la troisième figure, nous construisons une ellipse avec deux arcs de
cercle. Le premier PathFigure commence à StartPoint (100,50). On lui apporte
un ArcSegment avec la propriété Point égale à (200,50) qui représente le point
de terminaison de l’arc elliptique, Size égale à (50,30) qui représente les rayons
suivant X et Y de l’arc elliptique, enfin SweepDirection égale à Counterclockwise
qui représente le sens contraire des aiguilles d’une montre. Le deuxième
ArcSegment apporté au PathFigure, est dessiné avec le sens des aiguilles d’une
montre (Clockwise).
Pour matérialiser les points de contrôle Point2 et Point3, nous réalisons deux
lignes de type LineGeometry, x_ligne_1 et x_ligne_2, ainsi que deux ellipses de
type EllipseGeometry pour matérialiser leur position (x_ellipse_1 et x_ellipse_2).
EndPoint='150,50' />
<EllipseGeometry x:Name='x_ellipse_1'
Center='150,50' RadiusX='5' RadiusY='5' />
<LineGeometry x:Name='x_ligne_2' StartPoint='60,160'
EndPoint='250,230' />
<EllipseGeometry x:Name='x_ellipse_2'
Center='60,160' RadiusX='5' RadiusY='5' />
</GeometryGroup>
CHAPITRE 3 □ Les géométries et les tracés 2D 99
</Path.Data>
</Path>
Avec une animation de type Storyboard, nous faisons varier la position des
points de contrôles Point2 et Point3 de x_bezier_segment, en utilisant une
animation de type PointAnimation qui cible une propriété de type double. Nous
voyons ainsi au cours du temps l’influence des points de contrôles sur la forme
de la courbe de Bézier (figure 3-6).
Figure 3-6
déplacement du point de
contrôle horizontalement
déplacement
du point de
contrôle
verticalement
<Canvas.Triggers>
<EventTrigger RoutedEvent='Canvas.Loaded'>
<BeginStoryboard>
<Storyboard RepeatBehavior='Forever'
AutoReverse='True'>
<PointAnimation
Storyboard.TargetName='x_bezier_segment'
Storyboard.TargetProperty='Point1'
From='50 20' To='250 20'
Duration='0:0:5' />
<PointAnimation
Storyboard.TargetName='x_ligne_1'
Storyboard.TargetProperty='EndPoint'
From='50 20' To='250 20'
Duration='0:0:5' />
<PointAnimation
Storyboard.TargetName='x_ellipse_1'
Storyboard.TargetProperty='Center'
100 La programmation graphique 2D de WPF 4
From='50 20' To='250 20'
Duration='0:0:5' />
<PointAnimation
Storyboard.TargetName='x_bezier_segment'
Storyboard.TargetProperty='Point2'
From='60 50' To='60 250'
Duration='0:0:5' />
<PointAnimation
Storyboard.TargetName='x_ligne_2'
Storyboard.TargetProperty='StartPoint'
From='60 50' To='60 250'
Duration='0:0:5' />
<PointAnimation
Storyboard.TargetName='x_ellipse_2'
Storyboard.TargetProperty='Center'
From='60 50' To='60 250'
Duration='0:0:5' />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
Toutes les actions vont se dérouler quand le curseur de la souris est sur le
Canvas x_cnv_appli. En effet, pour dessiner un carré, il faut que le bouton
gauche de la souris soit enfoncé quand il se trouve sur le Canvas. Ce bouton
doit être maintenu enfoncé pour donner une taille à la forme. Enfin il doit être
relâché pour signifier que la forme doit être générée. Il faut donc ajouter au
Canvas les gestionnaires d’événements MouseLeftButtonDown, MouseMove et
MouseLeftButtonUp.
Nous allons voir en premier la démarche pour dessiner un carré. Il en sera de
même pour dessiner les autres formes basiques. On commence par cliquer
une fois sur le RadioButton affichant le carré, il est donc sélectionné. Le bouton
Copyright 2011 Patrice REY
La figure 3-8 visualise les étapes précédentes décrites pour le traçage d’une
forme rectangle.
un MouseLeftButtonUp sur le
Canvas
Dans le cas contraire, c’est une forme qui est à l’origine de l’événement, alors
on stocke dans originalElement le tracé de type Path qui a déclenché l’événement.
Le booléen isDown passe à true (pour signifier que l’on a sélectionné une forme).
originalElement.Data = geometry;
x_cnv_appli.Children.Add(originalElement);
}
x_cnv_appli.Children.Remove(movingElement);
movingElement = null;
}
isDragging = false;
isDown = false; }
CHAPITRE 3 □ Les géométries et les tracés 2D 113
La figure 3-9 visualise les étapes précédentes décrites pour le déplacement
d’une forme rectangle.
Figure 3-9
un MouseLeftButtonUp sur la
forme
Le but est de combiner deux formes en une seule, selon certaines méthodes de
combinaison (celles que nous avons vues pour les géométries).
Si la souris n’est pas capturée sur le Canvas, alors on relève la position de
la souris sur le Canvas (v_point_depart). Si le bouton de combinaison d_
combiner est coché (d_combiner.IsChecked == true), on effectue la méthode
TraiterCombinerDeuxForme(..).
La façon de faire pour combiner deux formes est la suivante: on clique sur
une première forme et elle est sélectionnée; on clique sur une deuxième
forme et elle est sélectionnée; on fait un clic droit sur une de ces deux formes
sélectionnées pour faire apparaitre un menu contextuel et pour choisir la
méthode de combinaison des formes.
La variable path1 de type Path reçoit la première forme sélectionnée et la variable
path2 reçoit la deuxième forme sélectionnée. On ajoute dynamiquement un
menu contextuel cm à cet ensemble. A ce menu contextuel, on lui ajoute des
rubriques men_item de type MenuItem: la propriété Header reçoit le texte de la
rubrique (ici Union, Xor, Intersect et Exclude) et un gestionnaire pour l’événement
Click est associé (Union_Click, Xor_Click, Intersect_Click et Exclude_Click).
path2 = (Path)e.Source;
path2.Cursor = Cursors.Hand;
path2.Stroke = selectBorderColor;
ContextMenu cm = new ContextMenu();
path2.ContextMenu = cm;
MenuItem men_item = new MenuItem();
men_item.Header = «Union»;
men_item.Click += new RoutedEventHandler(Union_Click);
CHAPITRE 3 □ Les géométries et les tracés 2D 115
cm.Items.Add(men_item);
men_item = new MenuItem();
men_item.Header = «Xor»;
men_item.Click += new RoutedEventHandler(Xor_Click);
cm.Items.Add(men_item);
men_item = new MenuItem();
men_item.Header = «Intersect»;
men_item.Click += new RoutedEventHandler(Intersect_Click);
cm.Items.Add(men_item);
men_item = new MenuItem();
men_item.Header = «Exclude»;
men_item.Click += new RoutedEventHandler(Exclude_Click);
cm.Items.Add(men_item);
}
}
Figure 3-10
la nouvelle forme composite créée un clic droit pour afficher le menu contextuel
et choisir la combinaison (ici Union par
exemple)
Visual formant un noeud de cet arbre. Une modification d’un élément Visual
est répercutée dans l’arbre, et est automatiquement prise en compte lors du
prochain cycle d’affichage.
L’UserControl TestDatteinte.xaml dans le dossier chapitre03 illustre l’utilisation
de la méthode statique HitTest de la classe VisualTreeHelper (figure 3-11).
CHAPITRE 3 □ Les géométries et les tracés 2D 119
Figure 3-11
120 La programmation graphique 2D de WPF 4
Nous positionnons sur le Canvas x_cnv_appli deux rectangles, une ellipse et
un cercle. La propriété Cursor du Canvas est fixé à Cross de façon à afficher
le curseur de la souris sur le Canvas avec une forme de croix. Le gestionnaire
MouseLeftButtonDown est ajouté au Canvas.
rouge_semi_transparent.B = 128;
rouge_semi_transparent.A = 128;
m_coul_remplissage_rouge =
new SolidColorBrush(rouge_semi_transparent);
Ce que nous voulons réaliser c’est un test d’atteinte dans lequel un clic
gauche sur le Canvas nous informe des objets Visuals qui contiennent le point
CHAPITRE 3 □ Les géométries et les tracés 2D 121
correspondant au clic.
Lors du clic gauche de la souris sur le Canvas, on commence par remplir toutes
les figures en bleu. On relève la position point_souris de la souris sur le Canvas.
On affiche ces coordonnées dans le TextBlock x_infos. La liste m_liste_objet_
atteint, qui contient les objets qui seront trouvés dans l’arbre visuel, est vidée.
Les figures basiques telles que le rectangle et l’ellipse ne suffisent pas toujours
pour réaliser des applications graphiques évoluées. Pour y remédier, vous
pouvez créer des figures personnalisées qui héritent de la classe abstraite
Shape. Dans ce cas, votre figure personnalisée hérite de toutes les propriétés
et de toutes les méthodes de la classe mère Shape, et devient de fait un objet
FrameworkElement. Par la suite vous pouvez réutiliser votre figure personnalisée
comme un simple contrôle dans vos applications WPF.
CHAPITRE 3 □ Les géométries et les tracés 2D 123
Nous allons voir comment créer des figures personnalisées réutilisables, avec
comme exemple une étoile et une flèche. En suivant la façon de faire présentée,
vous pourrez créer vos propres librairies de composants réutilisables.
Réaliser une figure personnalisée est une chose assez simple. Vous devez
faire hériter votre figure personnalisée par la classe abstraite Shape et redéfinir
la propriété DefiningGeometry qui est en lecture seule (elle retourne un objet
Geometry qui définit la géométrie de votre figure personnalisée). Nous
déclarons une classe ComposantEtoile héritant de la classe Shape. Le fichier
ComposantEtoile.cs se trouve dans le dossier chapitre03.
Notre figure aura comme géométrie un PathGeometry m_geo_etoile, déclaré
protected, qui sera retourné par la propriété redéfinie DefiningGeometry (propriété
de la classe de base). Nous ajoutons deux autres propriétés de dépendance qui
appartiennent à notre figure personnalisée, qui sont DpCentre (de type Point)
et DpRayon (de type double).
La création d’un identifiant de propriété de dépendance (dependency property)
passe par la création d’une instance de la classe DependencyProperty. Cette
instance doit être référencée comme champ static public de la classe supportant
la propriété. Elle doit être aussi enregistrée 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 le dictionnaire statique privé PropertyFromName
de la même classe, et lui affecte un identifiant de type disponible dans la
propriété GlobalIndex.
On déclare, par la méthode statique Register(..), une propriété de dépendance
DpCentrePropriete de type DependencyProperty, qui porte le nom DpCentre,
qui est de type Point, dont celui qui l’expose est ComposantEtoile, avec
des coordonnées (50,50), et avec un type de comportement de propriété
AffectsMeasure au niveau de l’infrastructure.
Pour pouvoir accès en lecture et en écriture à DpCentre, on crée un accesseur
DpCentre en utilisant les méthodes SetValue(..) pour fixer la propriété de
dépendance, et GetValue(..) pour obtenir la propriété de dépendance.
Comme le montre la figure 3-12, pour dessiner l’étoile, nous utilisons une ligne
composée de segments, de type PolyLineSegment. Cette ligne passe par les
points pt1, pt2, pt3, pt4 et pt5. Tous ces points peuvent être situés par rapport
aux coordonnées du centre DpCentre.X et DpCentre.Y en tenant compte des
angles α, β et de la longueur du rayon DpRayon.
De cette manière on compose une figure en forme d’étoile, centrée sur
DpCentre et inscrite dans un cercle de rayon DpRayon. A noter que l’on utilise
pour la géométrie la règle FillRule.Nonzero, suite aux recoupements des lignes.
pt1
pt3 pt4
DpCentre α
X
β
DpRayon
pt2
pt5
une instance de
ComposantEtoile
une instance de
ComposantEtoile
Maintenant nous allons animer l’étoile par une animation de mise à l’échelle
et de translation. On ajoute à l’instance du composant un RenderTransform
par visuel:ComposantEtoile.RenderTransform, avec un TransformGroup qui est
Copyright 2011 Patrice REY
profondeur de la tête
pt3
point bas
droit
//redefinition de la geometrie
protected override Geometry DefiningGeometry {
get {
double x = DpPointHautGauche.X;
double y = DpPointHautGauche.Y;
double largeur_boite = DpPointBasDroit.X - DpPointHautGauche.X;
CHAPITRE 3 □ Les géométries et les tracés 2D 131
double hauteur_boite = DpPointBasDroit.Y - DpPointHautGauche.Y;
//tete
PathFigure pf_tete = new PathFigure();
pf_tete.StartPoint = new Point(x + DpProfondeurTete, y);
PolyLineSegment pls_tete = new PolyLineSegment();
pls_tete.Points.Add(new Point(x, y + hauteur_boite / 2));
pls_tete.Points.Add(new Point(x + DpProfondeurTete, y + hauteur_
boite));
if (DpTypeTete == Tete.fermee) {
pls_tete.Points.Add(new Point(x + DpProfondeurTete, y));
}
pf_tete.Segments.Add(pls_tete);
m_geo_figure.Figures.Add(pf_tete);
//tige
PathFigure pf_tige = new PathFigure();
pf_tige.StartPoint = new Point(x + DpProfondeurTete, y + hauteur_
boite / 2);
LineSegment line_tige = new LineSegment();
line_tige.Point = new Point(x + largeur_boite, y + hauteur_boite
/ 2);
pf_tige.Segments.Add(line_tige);
m_geo_figure.Figures.Add(pf_tige);
//regle remplissage
m_geo_figure.FillRule = FillRule.Nonzero;
return m_geo_figure;
}
<RotateTransform x:Name='x_fleche_1_rotate'
CenterX='100' CenterY='25' Angle='0'></RotateTransform>
</visuel:ComposantFleche.RenderTransform>
</visuel:ComposantFleche>
<!-- animation -->
<Canvas.Triggers>
<EventTrigger RoutedEvent='Canvas.Loaded'>
<BeginStoryboard>
CHAPITRE 3 □ Les géométries et les tracés 2D 133
<Storyboard RepeatBehavior='Forever'
AutoReverse='False'>
<DoubleAnimation
Storyboard.TargetName='x_fleche_1_rotate'
Storyboard.TargetProperty='Angle'
From='0' To='360' Duration='0:0:5' />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
CHAPITRE 4
T out ce qui est visible à l’écran d’un ordinateur fait appel aux couleurs. Par
exemple, il est courant d’utiliser un pinceau de couleurs pour peindre le fond
d’un bouton, l’arrière-plan d’un texte, le remplissage d’une figure, etc. Jusqu’à
présent nous avons utilisé dans ce livre des couleurs de type SolidColorBrush.
Or tous les objets graphiques peuvent être peints en utilisant des couleurs
simples, des dégradés de couleurs, des motifs, des images, etc.
Ce chapitre nous montre le système de couleurs utilisé par WPF ainsi que les
pinceaux de couleurs, les dégradés, la répétition de motif et les transformations
appliquées aux pinceaux de couleurs.
1 - Les couleurs
Dans WPF, une couleur est représentée par la structure Color qui se trouve
dans l’espace de noms System.Windows.Media (assembly PresentationCore.
dll). La structure Color décrit une couleur en termes de canaux alpha (A pour
l’opacité), rouges (R), verts (G) et bleus (B). Les composantes peuvent être
exprimées dans l’espace de couleur standard sRGB (propriétés A, R, G et B,
en valeurs entières de 0 à 255) ou dans l’espace de couleur ScRGB (propriétés
ScA, ScR, ScG et ScB, en valeurs de type float de 0.0 à 1.0, offrant davantage
de précision).
La classe statique Colors implémente un jeu de couleurs prédéfinies, au nombre
de 141, en fournissant des objets Color pour les couleurs standard (Color.Black,
Color.Red, etc.).
La classe statique SystemColors expose les couleurs système sous la forme
d’objets Color (WindowColor), d’objets pinceaux (WindowBrush), ou de clés de
ressources (WindowColorKey, WindowBrushKey). Cela permet de les utiliser
sous la forme de références statiques ou dynamiques en XAML (par exemple
Fill = «{x:Static SystemColors.WindowBrush}»).
De plus la classe ColorConverter (espace de noms System.Windows.Media.
ColorConverter) convertit les instances d’autres types vers et à partir d’une
instance de Color. Elle contient notamment une méthode statique qui tente de
136 La programmation graphique 2D de WPF 4
convertir une chaîne en Color.
StackPanel x_stack_coul
Rectangle
x_rect_select
TextBlock x_info_rvb_r
TextBlock x_info_rvb_v
TextBlock x_info_rvb_b
Copyright 2011 Patrice REY
TextBlock x_info_rvb_a
rouge
(255,0,0)
blanc
(255,255,255)
noir
(0,0,0)
vert
(0,255,0)
bleu
(0,0,255)
142 La programmation graphique 2D de WPF 4
Toutes les couleurs situées dans un plan parallèle à ce triangle ont également
toutes la même luminance. Perpendiculairement à ce triangle, la diagonale qui
relie le noir au blanc définit l’axe achromatique.
Le principal problème quand on cherche à analyser une couleur est que celle-
ci dépend de sa chrominance et de sa luminance. Dès lors, deux stimuli de
même chrominance, mais de luminances différentes, peuvent parfaitement
avoir des coordonnées trichromatiques différentes. Toutes les couleurs qui se
trouvent dans le triangle de Maxwell, ont comme particularité que la somme
de leurs composantes est égale à 255.
Pour établir un panel de couleurs pour notre sélecteur, nous allons choisir des
couleurs qui se trouvent sur la droite passant par le point rouge (255,0,0) et le
point bleu (0,0,255), des couleurs qui se trouvent sur la droite passant par le
point bleu (0,0,255) et le point vert (0,255,0), et des couleurs qui se trouvent
sur la droite passant par le point vert (0,255,0) et le point rouge (255,0,0).
Toutes ces couleurs auront la même luminance.
Les couleurs qui se trouvent sur la droite entre le point rouge et le point bleu
sont des couleurs qui ont la composante verte à 0. Il suffit de faire une boucle
en bloquant la composante verte à 0, et en faisant varier la composante rouge
de 255 à 0, et la composante bleue de 0 à 255. Le même principe est appliqué
pour obtenir les couleurs des deux autres droites.
On implémente cela par une boucle de 3 tours qui effectue un parcours xx
allant de 0 à 255, avec à l’intérieur une instruction switch pour fixer le mode de
calcul des composantes. On se limite à un jeu partiel de couleurs en ne prenant
que la couleur pour xx=0 et xx=255, ainsi que les couleurs pour xx%4=0.
L’objet couleur de type Color obtenu est ajouté à une liste m_liste_coul_base de
type List<Color>. Une fois la liste remplie, on récupère chaque objet Color de
la liste et on ajoute une ligne de cette couleur au StackPanel x_stack_coul (on
obtient ainsi un panel de 195 couleurs).
double comp_R = 0;
double comp_G = 0;
double comp_B = 0;
for (int tour = 1; tour <= 3; tour++) {
for (int xx = 0; xx <= 255; xx++) {
switch (tour) {
case 1:
CHAPITRE 4 □ Les couleurs et les pinceaux 143
comp_A = 255;
comp_R = 255 - xx;
comp_G = 0;
comp_B = xx;
break;
case 2:
comp_A = 255;
comp_R = 0;
comp_G = xx;
comp_B = 255 - xx;
break;
case 3:
comp_A = 255;
comp_R = xx;
comp_G = 255 - xx;
comp_B = 0;
break;
}
if (xx == 0 || xx == 255 || xx % 4 == 0) {
Color couleur = new Color();
couleur.A = (byte)comp_A;
couleur.R = (byte)comp_R;
couleur.G = (byte)comp_G;
couleur.B = (byte)comp_B;
m_liste_coul_base.Add(couleur);
}
}
}
for (int xx = m_liste_coul_base.Count - 1; xx >= 0; xx--) {
AjouterLigneCouleurAuStackpanel(m_liste_coul_base[xx]);
}
...
}
//dans SelecteurCouleur.xaml.cs :
public Color SelectionCouleur {
get { return m_couleur_select; }
}
2 - Les pinceaux
x_rect_1.Fill = Brushes.CornflowerBlue;
x_text_1.Text = «remplissage avec un Brushes :»;
x_rect_2.Fill = new SolidColorBrush(Colors.Crimson);
x_text_2.Text = «remplissage avec un objet Colors»;
SolidColorBrush brush = new SolidColorBrush(
Color.FromArgb(100, 0, 0, 255));
x_rect_3.Fill = brush;
x_text_3.Text = «remplissage avec des valeurs sRGB d’une structure
Color»;
brush = new SolidColorBrush(Color.FromScRgb(0.5f, 0.7f, 0.0f, 0.5f));
x_rect_4.Fill = brush;
x_text_4.Text = «remplissage avec des valeurs ScRGB d’une structure
Color»;
brush = new SolidColorBrush(
(Color)ColorConverter.ConvertFromString(«#CBFFFFAA»));
x_rect_5.Fill = brush;
x_text_5.Text = «remplissage avec une chaine hexadécimale»;
x_rect_6.Fill = new SolidColorBrush(x_selecteur.SelectionCouleur);
x_text_6.Text = «remplissage avec la couleur du sélecteur»;
dégradé linéaire
vertical
dégradé linéaire
dégradé dégradé dégradé
horizontal
circulaire circulaire circulaire
RadiusX= 1 RadiusX= 1 RadiusX= 0.5
RadiusY= 1 RadiusY= 1 RadiusY= 0.5
dégradé linéaire en
Center= 0.5, 0.5 Center= 0 , 0 Center= 0.5 , 0.5
diagonale avec un
offset de 1
dégradé linéaire en
diagonale avec un
offset de 0.5
dégradé linéaire
dégradé dégradé dégradé
vertical avec
circulaire circulaire circulaire
plusieurs couleurs RadiusX= 0.5 RadiusX= 1 RadiusX= 0.5
dégradé linéaire en RadiusY= 0.5 RadiusY= 0.5 RadiusY= 1
diagonale avec Center= 0 , 0 Center= 0.5 , 0.5 Center= 0.5 , 0.5
plusieurs couleurs
</Ellipse>
</Rectangle.Fill>
</Rectangle>
Le rectangle x_rect_2 est rempli par l’image avec le mode d’étirement égal à
Fill (le contenu est mis à l’échelle pour s’ajuster aux dimensions; toutefois, les
proportions du contenu sont conservées).
CHAPITRE 4 □ Les couleurs et les pinceaux 155
Figure 4-7
156 La programmation graphique 2D de WPF 4
<Rectangle Canvas.Left='287' Canvas.Top='158' Height='200'
Name='x_rect_2' Stroke='Black' Width='250'>
<Rectangle.Fill>
<ImageBrush
ImageSource='/ProgGraphiqueWpf;
component/contenu/chap04/couleur_carre_100x100.jpg'
Stretch='Fill' TileMode='None' />
</Rectangle.Fill>
</Rectangle>
Le rectangle x_rect_3 est rempli par l’image avec le mode d’étirement égal à
Uniform (le contenu est mis à l’échelle afin qu’il remplisse complètement la
zone de sortie mais conserve ses proportions d’origine).
Le rectangle x_rect_4 est rempli par l’image avec le mode d’étirement égal à
UniformToFill (le contenu est mis à l’échelle pour s’ajuster à la mosaïque; puisque
la hauteur et la largeur du contenu sont mises à l’échelle indépendamment, les
proportions d’origine du contenu ne peuvent pas être conservées; autrement
dit, le contenu peut être déformé pour remplir complètement la mosaïque de
sortie).
<Rectangle.Fill>
<ImageBrush
ImageSource='/ProgGraphiqueWpf;
component/contenu/chap04/couleur_carre_100x100.jpg'
Stretch='UniformToFill' TileMode='None' />
</Rectangle.Fill>
</Rectangle>
CHAPITRE 4 □ Les couleurs et les pinceaux 157
La figure 4-7 montre l’exemple correspondant dans ces quatre modes
d’étirement possibles.
</TextBlock.Foreground>
</TextBlock>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
CHAPITRE 4 □ Les couleurs et les pinceaux 159
Le StackPanel x_stackpanel_reflet (exemple de droite sur la figure 4-8) contient
un Canvas x_cnv_reflet et un autre Canvas x_cnv_reflechi qui est la représentation
visuelle du x_cnv_reflet, mais avec un effet de réflexion.
<Canvas x:Name='x_cnv_reflechi'
Copyright 2011 Patrice REY
Height='{Binding Path=(Canvas.ActualHeight),
ElementName=x_cnv_reflet}'
Width='{Binding Path=(Canvas.ActualWidth),
ElementName=x_cnv_reflet}'>
<Canvas.Background>
<VisualBrush x:Name='myVisualBrush' Stretch='None'
AlignmentX='Left' AlignmentY='Top' Opacity='1'
CHAPITRE 4 □ Les couleurs et les pinceaux 161
Visual='{Binding ElementName=x_cnv_reflet}'>
<VisualBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform ScaleX='1' ScaleY='-1' />
<TranslateTransform Y='1' />
</TransformGroup>
</VisualBrush.RelativeTransform>
</VisualBrush>
</Canvas.Background>
<Canvas.OpacityMask>
<LinearGradientBrush StartPoint='0.5,0' EndPoint='0.5,1'>
<LinearGradientBrush.GradientStops>
<GradientStop Color='#FF000000' Offset='0.0' />
<GradientStop Color='#00000000' Offset='0.7' />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Canvas.OpacityMask>
</Canvas>
<Canvas.Triggers>
<EventTrigger RoutedEvent='Canvas.Loaded'>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName='x_balle'
Storyboard.TargetProperty='(Canvas.Top)'
From='0' To='100' Duration='0:0:3'
RepeatBehavior='Forever'
AutoReverse='True'
AccelerationRatio='1.0' />
<DoubleAnimation Storyboard.TargetName='x_balle'
Storyboard.TargetProperty='(Canvas.Left)'
From='0' To='350' Duration='0:0:20'
RepeatBehavior='Forever'
AutoReverse='True'
162 La programmation graphique 2D de WPF 4
AccelerationRatio='1.0' />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
<Canvas.Background>
<DrawingBrush Viewport='0,0,50,50' ViewportUnits='Absolute'
TileMode='Tile'>
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Geometry='M0,0 L50,0'>
Copyright 2011 Patrice REY
<GeometryDrawing.Pen>
<Pen Thickness='2' Brush='MediumBlue' />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry='M0,10 L50,10'>
<GeometryDrawing.Pen>
<Pen Thickness='1' Brush='LightCoral' />
</GeometryDrawing.Pen>
CHAPITRE 4 □ Les couleurs et les pinceaux 163
Figure 4-9
motif de base de la
grille
164 La programmation graphique 2D de WPF 4
</GeometryDrawing>
...
<GeometryDrawing Geometry='M0,0 L0,50'>
<GeometryDrawing.Pen>
<Pen Thickness='2' Brush='MediumBlue' />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry='M10,0 L10,50'>
<GeometryDrawing.Pen>
<Pen Thickness='1' Brush='LightCoral' />
</GeometryDrawing.Pen>
</GeometryDrawing>
...
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Canvas.Background>
A droite de la figure 4-9, on réalise trois boutons dont le contenu est peint avec
une image dont la source est un DrawingImage. Le bouton x_btn_selection permet
de sélectionner par exemple une figure dans un logiciel de dessin vectoriel. On
réalise son contenu avec une flèche orientée à 45 degrés. La propriété Drawing
du DrawingImage reçoit un GeometryDrawing dont le pinceau de remplissage
est un Brush = «Cornsilk». La propriété Geometry du GeometryDrawing reçoit
une géométrie PathGeometry représentant une flèche orientée à 45 degrés (la
propriété Figures reçoit les points d’une fléche horizontale auquelle on effectue
une rotation). Le GeometryDrawing est dessiné avec un Pen dont le Brush est
DarkBlue et le Thickness est de 3.
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush='Cornsilk'>
<GeometryDrawing.Geometry>
<PathGeometry
Figures='M25,75 L 50,0 75,75 60,75 60,100 40,100,40,75Z'>
CHAPITRE 4 □ Les couleurs et les pinceaux 165
<PathGeometry.Transform>
<RotateTransform CenterX='50'
CenterY='50' Angle='45' />
</PathGeometry.Transform>
</PathGeometry>
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Brush='DarkBlue' Thickness='3' />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
Le bas de la figure 4-9 montre les trois boutons affichant leur ToolTip quand le
curseur de la souris survole les boutons en prenant l’apparence Cursor.Hand.
Figure 4-11
L’effet de flou est assuré par la classe BlurEffect qui expose les propriétés
suivantes:
• Radius: l’intensité du flou est réglable au moyen de la propriété Radius
(une 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.
• KernelType: détermine le type d’algorithme employé, soit Box, soit Gaussian
(valeur par défaut).
• RenderingBias: définit le comportement privilégié, Performance (par défaut)
ou Quality.
Le contrôle image x_img_blur_gaus affiche une image en ressource pour
laquelle un effet BlurEffect lui est appliquée avec une propriété KernelType
fixée à Gaussian. L’image x_img_blur_box affiche la même image mais avec la
propriété KernelType fixée à Box.
<Image.Effect>
<BlurEffect KernelType='Gaussian' RenderingBias='Performance'
Radius='5' />
</Image.Effect>
</Image>
<Image Canvas.Left='538' Canvas.Top='233' Height='169'
Name='x_img_blur_box'
CHAPITRE 4 □ Les couleurs et les pinceaux 169
Source='/ProgGraphiqueWpf;
component/contenu/chap04/rocks.jpg'
Stretch='Fill' Width='180'>
<Image.Effect>
<BlurEffect KernelType='Box' Radius='5'
RenderingBias='Performance' />
</Image.Effect>
</Image>
Les animations
WPF permet d’animer les valeurs des propriétés des éléments en configurant
leur progression sur une durée donnée au moyen de différentes stratégies. La
progression peut être linéaire entre une valeur de départ et une valeur cible.
Elle peut suivre une application d’algorithmes d’interpolation prédéfinis entre
une valeur de départ et une valeur cible, dans le but d’augmenter le réalisme.
Elle peut être modifiée parmi une série de valeurs intermédiaires. Elle peut
aussi suivre des valeurs définies dans une géométrie.
Le système d’animation s’applique aux propriétés de dépendance des objets
supportant l’interface IAnimatable. La plupart des objets WPF, y compris ceux
qui sont non visuels, sont concernés. Comme les propriétés de dépendance
ne sont pas toutes du même type, compte tenu que l’animation est définie au
moyen d’un objet qui dépend du type de la propriété de dépendance ciblée,
WPF fournit un grand nombre de classes pour les principaux types susceptibles
d’être animés.
Une animation basique peut définir une valeur de départ (propriété From),
une valeur finale (propriété To) et un incrément (propriété By) avec différentes
combinaisons:
• From et To: de la valeur de départ From à la valeur finale To.
• From et By: de la valeur de départ From à la valeur From incrémentée de By.
• From seul: de la valeur de départ From à la valeur de base de la propriété.
• To seul: de la valeur courante à la valeur finale To.
• By seul: de la valeur courante à la valeur courante incrémentée de By.
• Aucune spécification: retour à la valeur initiale.
L’UserControl AnimationSimple.xaml, dans le dossier chapitre05, montre
l’utilisation d’une animation basique pour animer le déplacement d’un
rectangle (figure 5-1).
Le Canvas x_cnv_appli est rempli par un dégradé de couleurs. Un rectangle
x_rect est positionné en haut du Canvas avec une position (0,0). Copyright 2011 Patrice REY
phases successives:
exemple ci-dessous:
• AnimationTimeline: définit les paramètres d’une animation; un
AnimationTimeline est un type de chronologie qui produit des valeurs de
sortie; lorsque vous associez une animation à une propriété, l’animation
met à jour la valeur de la propriété lors de sa lecture, contribuant ainsi à
l’animer.
CHAPITRE 5 □ Les animations 179
• TimelineGroup: conteneur qui permet d’organiser de façon hiérarchique des
objets Timeline; cette classe abstraite est l’ancêtre de la classe Storyboard
(qui définit une propriété cible) et de la classe ParallelTimeline (qui permet
de regrouper des animations au sein d’un storyboard).
• MediaTimeline: un MediaTimeline est un type de chronologie qui contrôle la
lecture d’un fichier multimédia.
• ParallelTimeline: un ParallelTimeline est un type de chronologie qui regroupe
d’autres chronologies.
• Storyboard: un Storyboard est un type spécial de ParallelTimeline qui propose
des informations de ciblage sur les objets et les propriétés destinées aux
chronologies qu’il contient.
La figure 5-2 visualise l’arbre d’héritage de la classe Animatable. On voit bien
où se situe Storyboard, Timeline, et toutes les animations de propriétés (comme
DoubleAnimation, etc.).
Figure 5-2
180 La programmation graphique 2D de WPF 4
Le storyboard est un conteneur d’objets Timeline, il est donc possible d’imbriquer
plusieurs storyboards au sein d’un Timeline. De ce fait, cette imbrication
sous forme d’organisation hiérarchique permet d’élaborer des scénarios de
séquencement évolués. Car la plupart des propriétés d’un objet Timeline ont
un effet relatif au conteneur dans lequel il évolue.
Un objet Timeline a une durée de base déterminée par la propriété Duration.
En XAML une durée de 4 secondes s’exprimera par Duration = «0:0:4». Par
le code, on utilise un objet TimeSpan (structure représentant un intervalle de
temps) qui est passé au constructeur par l’intermédiaire de la méthode statique
TimeSpan.FromSeconds. Une durée de 4 secondes s’exprimera par Duration
duree = new Duration(TimeSpan.FromSeconds(4)). A noter que la durée d’une
animation est toujours précisée en unités de temps finies. Pour cette raison,
des valeurs spéciales peuvent être utilisées pour Storyboard et ParallelTime
(Duration.Automatic qui est la valeur par défaut indiquant que la chronologie
s’arrête avec la dernière animation contenue, et Duration.Forever indiquant une
durée infinie).
La propriété BeginTime, de type TimeSpan, indique le temps à partir duquel
l’exécution commence. Ce temps est relatif à l’objet Timeline conteneur. Pour
un storyboard, on exprimera en XAML <Storyboard BeginTime = «0:0:4»>.
La propriété RepeatBehavior permet de configurer le comportement à
répétition d’une chronologie c’est-à-dire si l’exécution d’un objet Timeline doit
être répétée et comment. La propriété RepeatBehavior définit soit le nombre
d’exécutions de l’objet Timeline (propriété Count), soit la durée d’exécution
(propriété Duration). Une itération Count spécifie le nombre de répétitions de
la chronologie, une valeur TimeSpan spécifie la durée totale de la période active
de cette chronologie ou la valeur spéciale Forever qui spécifie la répétition à
l’infini de la chronologie. La valeur par défaut est un RepeatBehavior ayant une
valeur Count de 1, ce qui indique que la chronologie s’accomplit une seule
fois. En XAML on exprimera cela par <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. Elle indique si la chronologie
Copyright 2011 Patrice REY
<UserControl.Resources>
<Storyboard x:Key='k_story_btn_mouse_enter_taille_1'>
<DoubleAnimation Storyboard.TargetName='x_btn1'
Storyboard.TargetProperty='Width' Duration='0:0:0.2'
To='200' />
<DoubleAnimation Storyboard.TargetName='x_btn1'
Storyboard.TargetProperty='Height' Duration='0:0:0.2'
To='45' />
</Storyboard>
</UserControl.Resources>
Le survol du bouton x_btn2 (figure 5-4) déclenche une animation qui consiste
Copyright 2011 Patrice REY
CHAPITRE 5 □ Les animations 185
<Button Canvas.Left='23' Canvas.Top='195' Content='bouton 2'
Cursor='Hand' Height='29' Name='x_btn2' Width='126'
Style='{StaticResource s_btn_anime}' FontSize='14'
FontFamily='Verdana'>
</Button>
Le bouton x_btn3 (figure 5-4) montre comment contrôler une animation par
code. On réalise un storyboard qui fait varier l’arrière-plan d’un bouton en
utilisant différentes couleurs. On ajoute une couleur unie x_btn3_couleur, de
type SolidColorBrush, à la propriété Background du bouton.
Par code, on instancie un nouvel objet Storyboard story1. Pour faire varier
une couleur de type Color, on utilise une animation color_anim1 de type
ColorAnimation. On fait varier les couleurs de Colors.Blue à Colors.Yellow sur
une durée de 5 secondes (TimeSpan(0,0,5) comme intervalle de temps). Par la
méthode statique Storyboard.SetTargetName, on cible l’animation color_anim1
au bouton x_btn3. Puis par Storyboard.SetTargetProperty, on cible l’animation
à la propriété Color du bouton avec un objet PropertyPath (SolidColorBrush.
ColorProperty). On ajoute à l’arbre visuel du storyboard story1 l’animation color_
anim1 par la propriété Children. Et on démarre le storyboard par la méthode
Begin(..).
Le bouton x_btn4 (figure 5-4), lors de son événement Click, déclenche une
animation qui consiste à modifier la taille en largeur de 6 rectangles identiques,
avec différentes propriétés du storyboard. Cela permet de mettre en évidence
l’impact des propriétés SpeedRatio, AccelerationRatio et DecelerationRatio.
<EventTrigger RoutedEvent='Button.Click'
SourceName='x_btn_demarrer'>
<EventTrigger.Actions>
<BeginStoryboard Name='x_story_ellipse_anim'>
188 La programmation graphique 2D de WPF 4
<Storyboard>
<DoubleAnimation
Storyboard.TargetName='x_ellipse_anim'
Storyboard.TargetProperty='Fill.RadiusX'
From='0' To='1' Duration='0:0:2'
RepeatBehavior='5x' />
<DoubleAnimation
Storyboard.TargetName='x_ellipse_anim'
Storyboard.TargetProperty='Fill.RadiusY'
From='0' To='1' Duration='0:0:2'
RepeatBehavior='5x' />
<ColorAnimation
Storyboard.TargetName='x_ellipse_anim'
Storyboard.TargetProperty='Fill.GradientStops[2].Color'
To='Black' Duration='0:0:2'
RepeatBehavior='5x' />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent='Button.Click'
SourceName='x_btn_pause'>
<PauseStoryboard BeginStoryboardName='x_story_ellipse_anim' />
</EventTrigger>
<EventTrigger RoutedEvent='Button.Click'
SourceName='x_btn_reprendre'>
<ResumeStoryboard BeginStoryboardName='x_story_ellipse_anim' />
</EventTrigger>
Copyright 2011 Patrice REY
<EventTrigger RoutedEvent='Button.Click'
SourceName='x_btn_arret'>
<StopStoryboard BeginStoryboardName='x_story_ellipse_anim' />
</EventTrigger>
CHAPITRE 5 □ Les animations 189
L’animation est réalisée ici par code. Le déplacement des balles se fait sur
une longueur de 450 pixels. On instancie une animation double_anim de type
DoubleAnimation, qui fait varier de 0 à 450 pour une durée de 5 secondes. On
cible la propriété Canvas.LeftProperty que l’on anime avec double_anim. De plus
on veut que la balle tourne d’un angle au fur et à mesure de son déplacement.
On agit donc sur la rotation ellipse1Rotate dont on fait varier l’angle de rotation
de 0 à nbre_rotation. La propriété ciblée est RotateTransform.AngleProperty. Le
booléen AutoReverse des animations est à true de façon à ce que les animations
reviennent en sens inverse.
La première balle se déplace avec des valeurs par défaut pour les animations,
donc à vitesse constante. Dans le cas de la deuxième balle, on fixe une vitesse
d’accélération égale à 0.4 (m_double_anim.AccelerationRatio = 0.4), ce qui
modifie le déplacement. Dans le cas de la troisième balle, on fixe une vitesse
de décélération égale à 0.6 (m_double_anim.DecelerationRatio = 0.6), ce qui
modifie le déplacement. Dans le cas de la quatrième balle, on fixe une vitesse
d’accélération et une vitesse de décélération (m_double_anim.DecelerationRatio =
0.6 et m_double_anim.AccelerationRatio = 0.4), ce qui modifie le déplacement.
La figure 5-6 montre bien que pour un temps donné, les balles ne sont pas
194 La programmation graphique 2D de WPF 4
forcément aux mêmes emplacements compte tenu des accélérations et des
décélérations.
L’animation n°3 (figure 5-5) permet d’animer un bouton lors du survol par le
curseur de la souris. Cette animation consiste à effectuer des transformations
combinées simultanées. La figure 5-6 montre le bouton quand il tourne.
Le bouton x_btn_ex3 a une taille de 180 de long par 30 de haut. On lui affecte
la valeur Hand à sa propriété Cursor.
<Button.RenderTransform>
<TransformGroup>
<ScaleTransform CenterX='90' CenterY='15' ScaleX='1'
ScaleY='1'>
</ScaleTransform>
<SkewTransform CenterX='90' CenterY='15' AngleX='0'
AngleY='0'>
</SkewTransform>
<RotateTransform CenterX='90' CenterY='15' Angle='0'>
Copyright 2011 Patrice REY
</RotateTransform>
</TransformGroup>
</Button.RenderTransform>
<Button.Triggers>
<EventTrigger RoutedEvent='Button.MouseEnter'>
<EventTrigger.Actions>
<BeginStoryboard Name='bouton_story'>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty=
«RenderTransform.Children[0].ScaleX'
To='1.5' Duration='0:0:1'
RepeatBehavior='1x'>
</DoubleAnimation>
<DoubleAnimation
Storyboard.TargetProperty=
«RenderTransform.Children[0].ScaleY'
To='1.5' Duration='0:0:1'
RepeatBehavior='1x'>
</DoubleAnimation>
<DoubleAnimation
Storyboard.TargetProperty=
«RenderTransform.Children[1].AngleX'
To='30' Duration='0:0:1'
RepeatBehavior='1x'>
</DoubleAnimation>
<DoubleAnimation
Storyboard.TargetProperty=
«RenderTransform.Children[1].AngleY'
To='30' Duration='0:0:1'
RepeatBehavior='1x'>
</DoubleAnimation>
<DoubleAnimation
196 La programmation graphique 2D de WPF 4
Storyboard.TargetProperty=
«RenderTransform.Children[2].Angle'
To='360' Duration='0:0:1'
RepeatBehavior='1x'>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
...
</Button.Triggers>
<Button.Triggers>
...
<EventTrigger RoutedEvent='Rectangle.MouseLeave'>
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty=
«RenderTransform.Children[0].ScaleX'
Duration='0:0:0.5'>
</DoubleAnimation>
<DoubleAnimation
Storyboard.TargetProperty=
«RenderTransform.Children[0].ScaleY'
Duration='0:0:0.5'>
</DoubleAnimation>
<DoubleAnimation
Storyboard.TargetProperty=
«RenderTransform.Children[1].AngleX'
Copyright 2011 Patrice REY
Duration='0:0:0.5'>
</DoubleAnimation>
<DoubleAnimation
Storyboard.TargetProperty=
«RenderTransform.Children[1].AngleY'
Duration='0:0:0.5'>
</DoubleAnimation>
<DoubleAnimation
CHAPITRE 5 □ Les animations 197
Storyboard.TargetProperty=
«RenderTransform.Children[2].Angle'
Duration='0:0:0.5'>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
La figure 5-6 montre des copies d’écran quand le bouton est en cours de
transformation au cours du temps.
Une animation sur tracé est un type d’animation qui utilise un tracé pour
obtenir les valeurs qui servent au ciblage des propriétés. Elle définit la valeur
de la propriété cible en fonction des coordonnées d’un objet PathGeometry
spécifiées dans la propriété de même nom. Les types de propriété animables par
cette technique sont Point, Double et Matrix. La classe PointAnimationUsingPath
permet de déplacer un point le long d’un chemin défini par les points de la
géométrie. L’animation d’une valeur de type double peut se faire au moyen
d’une DoubleAnimationUsingPath, dont la valeur de sortie dépend de la valeur
affectée à sa propriété Source:
• X retourne la coordonnée X du point courant de la géométrie.
• Y retourne la coordonnée Y du point courant de la géométrie.
• Angle retourne l’angle de la tangente au point courant de la géométrie.
La classe MatrixAnimationUsingPath intègre toutes les informations au sein
d’une matrice. Elle s’avère particulièrement efficace pour animer la propriété
Matrix d’une transformation MatrixTransform appliquée à un objet. La propriété
DoesRotateWithTangent indique si une rotation est appliquée en fonction de
l’angle de la tangente. L’UserControl ExPathAnimation.xaml, dans le dossier
chapitre05, montre l’utilisation des tracés pour l’animation (figure 5-7).
La première animation consiste à faire déplacer une balle jaune, avec son
centre matérialisé en rouge, en suivant un tracé en forme de huit. On réalise un
tracé de couleur MediumBlue et d’épaisseur 2, composé d’une géométrie path1
représentant une forme de huit (on utilise le mini-langage pour le traçage de
la géométrie).
198 La programmation graphique 2D de WPF 4
Figure 5-7
La balle jaune est représentée par un objet Path, lequel est rempli avec un
dégradé à base de jaune et un centre en rouge, et qui est composé d’une
géométrie x_cercle1 de type EllipseGeometry (un centre aux coordonnées 10,10
et des rayons RadiusX=10 et RadiusY=10).
Le bas de la figure 5-7 montre des copies d’écran où la balle jaune suit la forme
du huit. La vitesse est constante dans la mesure où rien n’a été précisé.
La deuxième animation consiste à faire suivre une boule bleue avec un centre
rouge sur une ligne brisée de couleur rouge. La ligne brisée représente une
route, commençant par un plat, puis une descente et terminant par un plat.
Pour augmenter le réalisme, la boule accélèrera puis décélèrera en fin de
parcours. Quatre boutons permettront de lancer l’animation, de la mettre en
pause, de la reprendre et de l’arrêter. Cette fois c’est une ellipse de type Ellipse
qui va servir de boule pour le déplacement. Nous allons donc utiliser une
autre démarche pour effectuer cette animation, et cela tout en XAML.
Il faut commencer par déterminer le tracé de la route qui va servir de support
pour les valeurs à fournir à l’animation. On réalise un PathGeometry que l’on
ajoute aux ressources de l’UserControl et que l’on référence par x:Key = «k_
geometrie». Cette géométrie est composée de 3 lignes brisées, qui sont affectées
à sa propriété Figures, et qui sont visualisées sur la figure 5-8.
Figure 5-8 (0,0) (96,0)
(320,100) (475,100)
<UserControl.Resources>
<PathGeometry x:Key=»k_geometrie»
Copyright 2011 Patrice REY
RepeatBehavior=»Forever»
AutoReverse=»True»
PathGeometry=»{StaticResource k_geometrie}»
AccelerationRatio=»0.6»
DecelerationRatio=»0.4»>
</DoubleAnimationUsingPath>
<DoubleAnimationUsingPath
Storyboard.TargetName=»x_cercle_translate»
CHAPITRE 5 □ Les animations 203
Storyboard.TargetProperty=»Y»
Source=»Y» Duration=»0:0:5»
RepeatBehavior=»Forever»
AutoReverse=»True»
PathGeometry=»{StaticResource k_geometrie}»
AccelerationRatio=»0.6»
DecelerationRatio=»0.4»>
</DoubleAnimationUsingPath>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
...
</Canvas.Triggers>
On ajoute les autres EventTrigger pour le clic sur les boutons de pause, de
reprise et d’arrêt (x_btn_pause2, x_btn_reprendre2 et x_btn_arreter2).
début fin
AccelerationRatio = 1
AccelerationRatio = 0.33
AccelerationRatio = 0.33
DecelerationRatio = 0.33
AccelerationRatio = 0.60
Copyright 2011 Patrice REY
DecelerationRatio = 0.40
<Storyboard>
<ColorAnimationUsingKeyFrames
RepeatBehavior='Forever'
AutoReverse='True'
Storyboard.TargetProperty=
«(Rectangle.Fill).(SolidColorBrush.Color)'>
<LinearColorKeyFrame KeyTime='0:0:0'
Value='Red' />
CHAPITRE 5 □ Les animations 207
Figure 5-10
208 La programmation graphique 2D de WPF 4
Figure 5-11
<LinearColorKeyFrame KeyTime='0:0:2'
Value='Yellow' />
<LinearColorKeyFrame KeyTime='0:0:4'
Value='Cyan' />
<LinearColorKeyFrame KeyTime='0:0:6'
Copyright 2011 Patrice REY
Value='Blue' />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
CHAPITRE 5 □ Les animations 209
L’interpolation discrète signifie une utilisation par palier. Ce n’est pas vraiment
une méthode d’interpolation proprement dit, puisque son évolution se fait par
des paliers successifs. Elle définit une évolution de valeur par palier à chaque
KeyFrame. La valeur ne subit aucune interpolation sinon elle résulterait d’un
calcul particulier. Elle ne subit qu’une réaffectation de valeur en fonction de la
propriété Value du KeyFrame.
La deuxième animation (figure 5-10) anime la couleur de fond de deux
rectangles. Pour le rectangle de gauche, on fait varier la couleur de fond en
utilisant les mêmes couleurs que dans la première animation, mais avec une
interpolation discrète. Pour le rectangle de droite, on alterne la visibilité du
rectangle (un palier où le rectangle est visible, un palier où le rectangle n’est
plus visible).
La chronologie, pour le rectangle de gauche, dure 8 secondes. La figure 5-12
visualise les différents paliers avec les temps. Durant le temps qui s’écoule
pour un palier, le fond du rectangle reste de la même couleur.
Y
Figure 5-12
le fond est Blue
Pour une interpolation discrète pour des valeurs de type Color, on utilise des
images clés au travers d’objets DiscreteColorKeyFrame. La propriété KeyTime
détermine l’instant donné et la propriété Value reçoit le nom d’une couleur.
210 La programmation graphique 2D de WPF 4
<Rectangle Width='170' Height='42' Fill='Red' Canvas.Left='179'
Canvas.Top='43'>
<Rectangle.Triggers>
<EventTrigger RoutedEvent='Loaded'>
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ColorAnimationUsingKeyFrames
Storyboard.TargetProperty=
«(Rectangle.Fill).(SolidColorBrush.Color)'
Duration='0:0:8'
RepeatBehavior='Forever'>
<DiscreteColorKeyFrame
KeyTime='0:0:0' Value='Red' />
<DiscreteColorKeyFrame
KeyTime='0:0:2'
Value='Yellow' />
<DiscreteColorKeyFrame
KeyTime='0:0:4' Value='Cyan' />
<DiscreteColorKeyFrame
KeyTime='0:0:6' Value='Blue' />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
point de
contrôle 1
en (0,0)
(0,0) (1,0)
point de contrôle 1
en (0,1)
point de contrôle 2
en (1,0)
(0,0)
(1,0)
zone
d’accélération
(0,1)
point de contrôle 2
en (0,1)
point de contrôle 1
en (1,0)
(0,0) (1,0)
zone d’accélération
<EventTrigger RoutedEvent='Button.Click'>
<EventTrigger.Actions>
<BeginStoryboard Name='begin_story_missile'>
<Storyboard>
...
<!-- pour x_img33 -->
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName='x_img33'
CHAPITRE 5 □ Les animations 217
Storyboard.TargetProperty='(Canvas.Left)'
Duration='0:0:5'
AutoReverse='False'
RepeatBehavior='1x'>
<SplineDoubleKeyFrame
KeyTime='0:0:0'
KeySpline='1,0 0,1'
Value='184'>
</SplineDoubleKeyFrame>
<SplineDoubleKeyFrame
KeyTime='0:0:5'
KeySpline='1,0 0,1'
Value='584'>
</SplineDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
Les classes d’images clés dont le nom est préfixé par Easing (comme
EasingDoubleKeyFrame) peuvent utiliser une interpolation évoluée réaliste
définie au moyen de leur propriété EasingFunction par un objet supportant
l’interface IEasingFunction.
La quatrième animation consiste à animer le déplacement de l’image x_img41
en utilisant la fonction EasingFunction modélisant un rebond (BounceEase). La
figure 5-17 visualise cette fonction en indiquant les valeurs prises en fonction
du temps.
Les images clés sont représentées par des objets EasingDoubleKeyFrame. La
propriété EasingFunction reçoit la modélisation BounceEase. Nous verrons
d’autres modélisations un peu plus loin dans ce chapitre.
Temps
<BeginStoryboard.Storyboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames
AutoReverse='False'
Duration='0:0:5'
RepeatBehavior='1x'
Storyboard.TargetName='x_img41'
Storyboard.TargetProperty='(Canvas.Left)'>
<EasingDoubleKeyFrame
KeyTime='0:0:0' Value='184'>
<EasingDoubleKeyFrame.EasingFunction>
<BounceEase></BounceEase>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame
KeyTime='0:0:5' Value='584'>
<EasingDoubleKeyFrame.EasingFunction>
<BounceEase></BounceEase>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
Copyright 2011 Patrice REY
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
CHAPITRE 5 □ Les animations 219
geometry.Figures.Add(figure);
double resolution = 2000;
for (int i = 0; i <= resolution; i++) {
double d = ease.Ease(i / resolution);
LineSegment segment = new LineSegment();
segment.Point =
new Point(i * 100 / resolution, 100.0 - (d * 100.0));
figure.Segments.Add(segment);
}
}
Figure 5-22
CHAPITRE 5 □ Les animations 225
On ajoute un EventTrigger qui gère le clic sur le bouton x_btn_demarrer2.
L’événement lance le Storyboard story_2 dont la TargetName est x_balle2,
la TargetProperty est Canvas.Left, pour une plage de valeurs allant de 220 à
675, pour une durée de 4 secondes, en utilisant la méthode d’interpolation
EasingFunction.BounceEase (figure 5-23).
Figure 5-23
déplacement de Left de 220 à 675 pixels
Button x_btn_demarrer2
Figure 5-25
déplacement de Left de 220 à 675 pixels
Button x_btn_demarrer3
Button x_btn_demarrer4
AjouterDessinModelisationAnimation(x_cnv_ex4_graph,
new CircleEase() { EasingMode = EasingMode.EaseOut })
<EventTrigger SourceName='x_btn_demarrer4'
RoutedEvent='Button.Click'>
<BeginStoryboard Name='story_4'>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName='x_balle4'
Storyboard.TargetProperty='(Canvas.Left)'
From='220' To='675' Duration='0:0:4'>
<DoubleAnimation.EasingFunction>
<CircleEase EasingMode='EaseOut'></CircleEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Copyright 2011 Patrice REY
Button x_btn_demarrer5
AjouterDessinModelisationAnimation(x_cnv_ex5_graph,
new CubicEase() { EasingMode = EasingMode.EaseOut })
<EventTrigger SourceName='x_btn_demarrer4'
RoutedEvent='Button.Click'>
<BeginStoryboard Name='story_4'>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName='x_balle4'
Storyboard.TargetProperty='(Canvas.Left)'
From='220' To='675' Duration='0:0:4'>
<DoubleAnimation.EasingFunction>
<CircleEase EasingMode='EaseOut'></CircleEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
230 La programmation graphique 2D de WPF 4
Button x_btn_demarrer6
<EventTrigger SourceName='x_btn_demarrer6'
RoutedEvent='Button.Click'>
<BeginStoryboard Name='story_6'>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName='x_balle6'
Storyboard.TargetProperty='(Canvas.Left)'
From='220' To='675' Duration='0:0:4'>
<DoubleAnimation.EasingFunction>
<ElasticEase Oscillations='3' Springiness='3'
EasingMode='EaseOut'></ElasticEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Button x_btn_demarrer7
AjouterDessinModelisationAnimation(x_cnv_ex7_graph,
new ExponentialEase() { Exponent = 2, EasingMode = EasingMode.EaseOut })
CHAPITRE 5 □ Les animations 233
<EventTrigger SourceName='x_btn_demarrer7'
RoutedEvent='Button.Click'>
<BeginStoryboard Name='story_7'>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName='x_balle7'
Storyboard.TargetProperty='(Canvas.Left)'
From='220' To='675' Duration='0:0:4'>
<DoubleAnimation.EasingFunction>
<ExponentialEase EasingMode='EaseOut'
Exponent='2'></ExponentialEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Button x_btn_demarrer8
AjouterDessinModelisationAnimation(x_cnv_ex8_graph,
new PowerEase() { Power = 2, EasingMode = EasingMode.EaseOut })
<EventTrigger SourceName='x_btn_demarrer8'
RoutedEvent='Button.Click'>
<BeginStoryboard Name='story_8'>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName='x_balle8'
Storyboard.TargetProperty='(Canvas.Left)'
From='220' To='675' Duration='0:0:4'>
<DoubleAnimation.EasingFunction>
<PowerEase Power='2'
EasingMode='EaseOut'></PowerEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Button x_btn_demarrer9
AjouterDessinModelisationAnimation(x_cnv_ex9_graph,
new QuadraticEase() { EasingMode = EasingMode.EaseOut })
<EventTrigger SourceName='x_btn_demarrer9'
RoutedEvent='Button.Click'>
<BeginStoryboard Name='story_9'>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName='x_balle9'
Storyboard.TargetProperty='(Canvas.Left)'
From='220' To='675' Duration='0:0:4'>
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode='EaseOut'></QuadraticEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Button x_btn_demarrer10
AjouterDessinModelisationAnimation(x_cnv_ex10_graph,
new QuarticEase() { EasingMode = EasingMode.EaseOut })
<EventTrigger SourceName='x_btn_demarrer10'
RoutedEvent='Button.Click'>
<BeginStoryboard Name='story_10'>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName='x_balle10'
Storyboard.TargetProperty='(Canvas.Left)'
From='220' To='675' Duration='0:0:4'>
Copyright 2011 Patrice REY
<DoubleAnimation.EasingFunction>
<QuarticEase EasingMode='EaseOut'></QuarticEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
CHAPITRE 5 □ Les animations 237
Button x_btn_demarrer11
AjouterDessinModelisationAnimation(x_cnv_ex11_graph,
new QuinticEase() { EasingMode = EasingMode.EaseOut })
<EventTrigger SourceName='x_btn_demarrer11'
RoutedEvent='Button.Click'>
<BeginStoryboard Name='story_11'>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName='x_balle11'
238 La programmation graphique 2D de WPF 4
Storyboard.TargetProperty='(Canvas.Left)'
From='220' To='675' Duration='0:0:4'>
<DoubleAnimation.EasingFunction>
<QuinticEase EasingMode='EaseOut'></QuinticEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger SourceName='x_btn_demarrer12'
Copyright 2011 Patrice REY
RoutedEvent='Button.Click'>
<BeginStoryboard Name='story_12'>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName='x_balle12'
Storyboard.TargetProperty='(Canvas.Left)'
From='220' To='675' Duration='0:0:4'>
CHAPITRE 5 □ Les animations 239
<DoubleAnimation.EasingFunction>
<SineEase EasingMode='EaseOut'></SineEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Figure 5-43
déplacement de Left de 220 à 675 pixels
Button x_btn_demarrer12
AjouterDessinModelisationAnimation(x_cnv_ex12_graph,
new SineEase() { EasingMode = EasingMode.EaseOut })
Button x_btn_demarrer1
Copyright 2011 Patrice REY
AjouterDessinModelisationAnimation(x_cnv_ex1_graph,
new ModelisationLineaireEase())
//usercontrol chargé
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
this.UpdateLayout();
v_dernier_rendu = TimeSpan.FromTicks(DateTime.Now.Ticks);
CompositionTarget.Rendering +=
new EventHandler(CompositionTarget_Rendering);
}
Il suffit de mettre à jour la position des centres des objets x_balle1 et x_balle2
en fonction du temps écoulé entre deux rendus d’image.
Les styles
1 - Principe
<Style x:Key='style_rectangle'>
<Setter Property='Rectangle.Width' Value='150'></Setter>
Copyright 2011 Patrice REY
<UserControl.Ressources>
<Style x:Key='style_rectangle'>
<Setter Property='Rectangle.Width' Value='150'></Setter>
<Setter Property='Rectangle.Height' Value='200'></Setter>
</Style>
</UserControl.Ressources>
...
<Canvas>
<Rectangle x:Name='x_rect' Style='{StaticRessource style_rectangle}'>
</Rectangle>
</Canvas>
<Style.Triggers>
<Trigger Property='IsEnabled' Value='false'>
<Setter Property='Background' Value='#EEEEEE' />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property='HasItems' Value='false' />
<Condition Property='Width' Value='Auto' />
</MultiTrigger.Conditions>
<Setter Property='MinWidth' Value='120'/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property='HasItems' Value='false' />
Copyright 2011 Patrice REY
<Canvas.Resources>
<Style x:Key='s_style_btn_orange'>
<Setter Property='Button.FontSize' Value='22' />
<Setter Property='Button.Background' Value='Orange' />
<Setter Property='Button.Foreground' Value='Black' />
254 La programmation graphique 2D de WPF 4
Figure 6-1
<Canvas.Resources>
<Style x:Key='s_style_btn_bleu' TargetType='{x:Type Button}'>
<Setter Property='FontSize' Value='18' />
<Setter Property='Background' Value='LightSkyBlue' />
<Setter Property='Foreground' Value='Black' />
<Setter Property='Height' Value='50' />
<Setter Property='Width' Value='50' />
<Setter Property='RenderTransform'>
<Setter.Value>
<SkewTransform CenterX='0' CenterY='0'
AngleX='20' AngleY='0'></SkewTransform>
</Setter.Value>
</Setter>
<Setter Property='Cursor' Value='Hand'></Setter>
</Style>
...
</Canvas.Resources>
<Canvas.Resources>
...
<Style x:Key='s_style_btn_bleu_gras'
TargetType='{x:Type Button}'
BasedOn='{StaticResource s_style_btn_bleu}'>
CHAPITRE 6 □ Les styles 257
<Setter Property='Button.FontWeight' Value='Bold' />
</Style>
</Canvas.Resources>
<Canvas.Resources>
<Style x:Key='s_style_control'>
<Setter Property='Control.FontSize' Value='22' />
<Setter Property='Control.Background' Value='LightCoral' />
<Setter Property='Control.Foreground' Value='Yellow' />
<Setter Property='Control.Height' Value='50' />
<Setter Property='Control.Width' Value='50' />
<Setter Property='Control.RenderTransformOrigin'
Value='0.5,0.5' />
<Setter Property='Control.RenderTransform'>
<Setter.Value>
<RotateTransform Angle='-10' />
</Setter.Value>
</Setter>
</Style>
</Canvas.Resources>
Les différents contrôles auront donc leur propriété Style fixée par Style =
«{StaticResource s_style_control}» (animation n°3 dans figure 6-1).
<Canvas.Resources>
<Style TargetType='{x:Type Button}'>
<Setter Property='FontSize' Value='22' />
<Setter Property='Background' Value='MediumBlue' />
<Setter Property='Foreground' Value='White' />
<Setter Property='Height' Value='50' />
<Setter Property='Width' Value='50' />
<Setter Property='RenderTransformOrigin' Value='0.5,0.5' />
CHAPITRE 6 □ Les styles 259
<Setter Property='RenderTransform'>
<Setter.Value>
<RotateTransform Angle='10' />
</Setter.Value>
</Setter>
<Setter Property='Cursor' Value='Hand'></Setter>
</Style>
</Canvas.Resources>
Sur le Canvas, nous avons trois boutons nommés, de gauche à droite x_btn4_1,
x_btn4_2 et x_btn4_3. Les trois boutons n’ont pas de propriétés Style fixées
explicitement, donc le style appliqué à tous ces boutons sera celui qui est
défini localement (ici le style qui cible les boutons et qui est inscrit dans le
dictionnaire des ressources du Canvas). Pour le bouton x_btn4_3, on fixe
explicitement une propriété Background à YellowGreen, donc cette couleur sera
substituée à celle du style (animation n°4 dans figure 6-1).
<Canvas.Resources>
<Style x:Key='s_style_bouton' TargetType='{x:Type Button}'>
260 La programmation graphique 2D de WPF 4
<Style.Triggers>
<Trigger Property='IsMouseOver' Value='True'>
<Setter Property='RenderTransform'>
<Setter.Value>
<RotateTransform Angle='10' />
</Setter.Value>
</Setter>
<Setter Property='Foreground' Value='Black' />
</Trigger>
</Style.Triggers>
<Setter Property='FontSize' Value='22' />
<Setter Property='Background' Value='Red' />
<Setter Property='Foreground' Value='YellowGreen' />
<Setter Property='Height' Value='50' />
<Setter Property='Width' Value='50' />
<Setter Property='RenderTransformOrigin' Value='0.5,0.5' />
<Setter Property='Cursor' Value='Hand'></Setter>
</Style>
...
</Canvas.Resources>
<Canvas.Resources>
...
Copyright 2011 Patrice REY
Une caractéristique importante des contrôles WPF est qu’ils ne gèrent pas eux-
mêmes leur représentation visuelle. En effet, celle-ci est confiée à un objet de
type ControlTemplate, qui contient une hiérarchie d’objets Visual. Un template
signifie un modèle et le terme de template est couramment employé comme
terme général pour désigner un ControlTemplate. De ce fait, si le contrôle n’a
pas de template, il n’y a pas d’affichage du contrôle.
Cette notion de template fait apparaître la distinction entre l’arbre logique et
l’arbre visuel des éléments WPF affichés. L’arbre logique est défini par le code
XAML, alors que l’arbre visuel prend en compte la composition des templates
des éléments affichés.
Bien entendu, tous les contrôles ont un template par défaut qui est fourni. En
changeant de template au moyen de la propriété Template, il est possible de
personnaliser complètement l’apparence visuelle d’un contrôle.
La classe du contrôle ne gère donc pas son visuel mais définit sa fonction. La
représentation du contrôle est définie au moyen d’un template, qui exploite les
propriétés définies dans la classe du contrôle.
La classe ControlTemplate (espace de noms System.Windows.Controls dans
l’assembly PresentationFramework.dll) spécifie la structure visuelle et les aspects
comportementaux d’un Control qui peuvent être partagés par plusieurs
264 La programmation graphique 2D de WPF 4
instances du contrôle. Elle expose principalement les propriétés:
• Template qui définit une référence à l’objet qui enregistre ou lit les nœuds
XAML pour le modèle lorsque le modèle est défini.
• Triggers qui obtient une collection d’objets TriggerBase qui appliquent des
modifications de propriété ou effectuent des actions selon des conditions
spécifiées.
• VisualTree qui définit le nœud racine du modèle.
• TargetType qui définit le type auquel le ControlTemplate est destiné.
• Ressources qui définit la collection des ressources qui peuvent être utilisées
dans la portée de ce modèle.
Le ControlTemplate vous permet de spécifier la structure visuelle d’un contrôle.
L’auteur du contrôle peut définir le modèle ControlTemplate par défaut, tandis
que l’auteur de l’application peut substituer ce modèle ControlTemplate afin
de reconstruire la structure visuelle du contrôle. Un ControlTemplate est conçu
comme une unité indépendante de détail d’implémentation, invisible aux
utilisateurs extérieurs et aux objets, y compris les styles. La seule façon de
manipuler le contenu du modèle de contrôle est de le faire à partir du même
modèle de contrôle.
L’UserControl ExTemplate.xaml, dans le dossier chapitre07, visualise la
personnalisation des modèles de contrôle au travers d’exemples (figure 7-1).
<Canvas.Resources>
<ControlTemplate x:Key='t_btn21_template'>
CHAPITRE 7 □ Les modèles de contrôle 267
<Grid>
<Ellipse Width='100' Height='100'>
<Ellipse.Fill>
<LinearGradientBrush StartPoint='0,0'
EndPoint='0,1'>
<GradientStop Offset='0' Color='Blue' />
<GradientStop Offset='1' Color='Red' />
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse Width='80' Height='80'>
<Ellipse.Fill>
<LinearGradientBrush StartPoint='0,0'
EndPoint='0,1'>
<GradientStop Offset='0' Color='White' />
<GradientStop Offset='1' Color='Transparent' />
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
</ControlTemplate>
</Canvas.Resources>
<Canvas.Resources>
<ControlTemplate x:Key='t_btn22_template'>
<Grid>
<Ellipse x:Name='outerCircle' Width='100'
Height='100'>
<Ellipse.Fill>
<LinearGradientBrush StartPoint='0,0'
EndPoint='0,1'>
<GradientStop Offset='0' Color='Blue' />
<GradientStop Offset='1' Color='Red' />
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse Width='80' Height='80'>
<Ellipse.Fill>
<LinearGradientBrush StartPoint='0,0'
EndPoint='0,1'>
<GradientStop Offset='0' Color='White' />
<GradientStop Offset='1' Color='Transparent' />
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
Copyright 2011 Patrice REY
<ControlTemplate.Triggers>
<Trigger Property='Button.IsMouseOver' Value='True'>
<Setter TargetName='outerCircle' Property='Fill'
Value='Orange' />
</Trigger>
<Trigger Property='Button.IsPressed' Value='True'>
<Setter Property='RenderTransform'>
<Setter.Value>
CHAPITRE 7 □ Les modèles de contrôle 269
<ScaleTransform ScaleX='0.9' ScaleY='0.9' />
</Setter.Value>
</Setter>
<Setter Property='RenderTransformOrigin'
Value='0.5,0.5' />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Canvas.Resources>
Dans les deux boutons précedents, nous avons modifié leur propriété Template.
Or, dans l’instanciation du bouton, nous avons rempli la propriété Content par
Bouton 01, et pourtant, ce contenu n’était pas affiché.
Le ControlTemplate t_btn23_template permet d’afficher la propriété Content
du bouton. En reprenant à l’identique le template t_btn22_template, il suffit
d’ajouter en premier une propriété TargetType qui cible le contrôle Button par
TargetType = «{x:Type Button}». En deuxième, il faut ajouter, dans le Grid qui
contient la définition visuelle du bouton, un objet Viewbox avec à l’intérieur un
ContentPresenter dont la propriété Content fait référence à la propriété Content
du type Button. Cette référence se fait au moyen d’une extension markup
TemplateBinding qui pointe sur la propriété Content du Button.
<ControlTemplate x:Key='t_btn23_template'
TargetType='{x:Type Button}'>
<Grid>
...
<Viewbox>
<ContentPresenter Margin='20'
Content='{TemplateBinding Content}' />
</Viewbox>
270 La programmation graphique 2D de WPF 4
</Grid>
...
</ControlTemplate>
Depuis WPF 4, le visuel des états est géré par la classe VisualStateManager. Nous
avons vu des états gérés par des Trigger dans le ControlTemplate. Maintenant
nous allons voir des états qui sont gérés par VisualStateManager.
La documentation MSDN de Microsoft, à l’adresse suivante http://msdn.
microsoft.com/fr-fr/library/aa970773.aspx, donne pour chaque contrôle les
catégories des états visuels qui sont disponibles. Pour un contrôle de type
Button, on a les états de base (par défaut, survol de la souris, activé ou désactivé),
les états concernant le focus (avec focus, sans focus), et les états de validation.
Le modèle objet du VisualStateManager représente ces trois catégories d’états
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).
Pour la classe Button, les groupes d’états sont les suivants (d’après la
documentation):
• le groupe CommonStates contient les états Normal, MouseOver, Pressed et
Disabled.
• le groupe FocusStates contient les états Focused et Unfocused.
• le groupe ValidationStates contient les états Valid, InvalidFocused et
InvalidUnfocused.
Copyright 2011 Patrice REY
<ContentPresenter
Content='{TemplateBinding Content}'
ContentTemplate='{TemplateBinding ContentTemplate}'
VerticalAlignment=
'{TemplateBinding VerticalContentAlignment}'
HorizontalAlignment=
'{TemplateBinding HorizontalContentAlignment}'
Margin='{TemplateBinding Padding}' />
CHAPITRE 7 □ Les modèles de contrôle 273
<Rectangle Name='focusRectangle'
Style='{StaticResource RectangleStyle}'
Visibility='Collapsed'
Stroke='{StaticResource BlueBrush4}' />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Il ne reste plus qu’à ajouter les effets visuels avec le VisualStateManager par
sa propriété VisualStateGroups qui est une collection composée des objets
VisualStateGroup. Nous avons les VisualStateGroup CommonStates et FocusStates.
Ils sont composés, pour CommonStates par les VisualState Normal, MouseOver et
Pressed, et pour FocusStates par les VisualState Focused et Unfocused.
Le bas de la figure 7-1 visualise les effets sur le bouton quand il est survolé par
Copyright 2011 Patrice REY
L’arborescence des fichiers est visualisée sur la figure N-1. Le point d’entrée
du programme est la fenêtre MainWidow.xaml avec son code MainWindow.cs.
La fenêtre est composée par un Grid qui est divisé en lignes et colonnes pour
le placement des contrôles.
<Grid x:Name='x_grid_root'>
<Grid.RowDefinitions>
<RowDefinition Height='25'></RowDefinition>
<RowDefinition Height='*'></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width='200'></ColumnDefinition>
<ColumnDefinition Width='*'></ColumnDefinition>
</Grid.ColumnDefinitions>
...
</Grid>
Les onglets des chapitres sont matérialisés par des StackPanel à l’intérieur
desquels se trouve un Canvas contenant un TextBlock, muni d’un gestionnaire
d’événements.
A
285
Bounces 224
Bounciness 224
Box 168
Brush 27, 145
Button.IsMouseOver 268
A 135 Button.IsPressed 268
accélération matérielle 165 By 174
AccelerationRatio 180
Add 50
C
AffectsMeasure 123
agrandissement 41
Amplitude 226
Angle 76
AngleBetween 50
AngleProperty 193
Canvas.SetLeft(..) 31
AngleX 79
Canvas.SetTop(..) 31
AngleY 79
Canvas.TopProperty 176
animation 176
CaptureMouse 108
animation des transformations 189
CenterProperty 199
animations basiques 173
CenterX 73, 76, 79
animations d’images clés 205
CenterY 73, 76, 79
animations réalistes 219
Children 81
animation sur tracé 197
chrominance 142
AnimationTimeline 127, 178
CircleEase 227
ArcSegment 95, 97
clarté 141
AutoReverse 180
ClipToBounds 22
Color 135, 146
B
ColorAnimation 173
ColorAnimationUsingKeyFrames 206
ColorConverter 135
Colors 135
CombinedGeometry 86
CommonStates 270
B 135 CompositionTarget 241
BackEase 225 CompositionTarget.Rendering 241
BasedOn 250 Condition 252
BeginAnimation 127 ContentPresenter 266
BeginStoryboard 177 Control.Foreground 250
BeginTime 180 ControlPoint 101
BezierSegment 95 ControlTemplate 263
Binding 21 coordonnées homogènes 44
BitmapSource 24 coordonnées par défaut 13
BlurEffect 166 Count 180
BlurRadius 167 CrossProduct 50
BounceEase 224 CubicEase 228
D
286 Index
EasingVectorKeyFrame 205
Effect 165
effet miroir 41
Elapsed 244
ElapsedMilliseconds 244
Data 90 ElapsedTicks 244
databinding 178 ElasticEase 230
DecelerationRatio 180 ElementName 21
déclencheur 251 Ellipse 27
DefiningGeometry 128 EllipseGeometry 85
définition 14 EndPoint 87, 149
dégradés de couleurs 135 EnterActions 177
densité 14 Equals 50
DependencyProperty 123 espace projectif 44
Determinant 53 états visuels 270
DIP 14 EventHandler 241
Direction 167 EventSetter 247
Disabled 270 EventTrigger 177
DiscreteBooleanKeyFrame 205 Exclude 91
DiscreteColorKeyFrame 209 ExitActions 177
Divide 50 ExponentialEase 231
DoesRotateWithTangent 197 extension markup 266
DoubleAnimation 173
DoubleAnimationUsingKeyFrames 174
F
DoubleAnimationUsingPath 174, 197
DragFinishing 111
DragMoving 111
DragStarting 110
Drawing 146
DrawingBrush 146, 162
facteur d’échelle 40
Drawing.Children 162
Figures 93
DropShadowEffect 166
Fill 36
Duration 173
FillBehavior 181
FilleRule.EvenOdd 36
E
FillRule 36
FillRule.NonZero 36
Copyright 2011 Patrice REY
Focused 270
FocusStates 270
Forever 180
FrameworkContentElement 177
Easing 217 FrameworkElement 27, 177
EasingDoubleKeyFrame 217 Freezable 177, 239
EasingFunction 217 From 173
EasingFunctionBase 219 FromArgb 136
EasingMode 224 FromRgb 136
Index 287
FromScRgb 136 ImageBrush 146, 154
ImageSource 154
G
interpolation discrète 209
interpolation Easing 217
interpolation personnalisée 239
interpolation Spline 212
Intersect 91
InvalidFocused 270
G 135 InvalidUnfocused 270
Gaussian 168 Invert 54
géométrie projective 44 IsChecked 102
Geometry 85 IsClosed 95
Geometry1 90 IsFilled 95
Geometry2 90 IsIdentity 53
GeometryCombineMode 90 IsLargeArcFlag 101
GeometryDrawing 162 IsMouseCaptured 105
GeometryEvenOdd 90 IsMouseOver 251
GeometryGroup 85 IsRunning 244
GlobalIndex 123 IsSealed 250
GradientStop 149
K
Gray8 25
H
HasInverse 53
KernelType 168
KeyFrame 174, 205
KeyFrame.FromPercent 205
KeyFrame.FromTimeSpan 205
Height 14
Histogramme 22 KeySpline 212
HitTest 118 KeyTime.Paced 205
HitTestResultCallback 121 KeyTime.Uniform 205
HoldEnd 181
I L
L’abscisse 14
IAnimatable 173 La modélisation BackEase 225
Identity 53 La modélisation BounceEase 224
IEasingFunction 217 La modélisation CircleEase 227
288 Index
La modélisation CubicEase 228 MouseOver 270
La modélisation ElasticEase 230 Multiply 50, 54
La modélisation ExponentialEase 231 MultiTrigger 252
La modélisation PowerEase 233
N
La modélisation QuadraticEase 234
La modélisation QuarticEase 235
La modélisation QuinticEase 237
La modélisation SineEase 238
Length 50
LengthSquared 50
Line 14, 27 Normal 270
LinearColorKeyFrame 206 Normalize 50
LinearDoubleKeyFrame 205
LinearGradientBrush 146
O
LineGeometry 85
LineSegment 95
ListBox 136
Loaded 161
L’ordonnée 14
luminance 142 Offset 150
luminosité 141 OffsetX 52
OffsetY 52
M
Opacity 147
Oscillations 230
M11 52
M12 52
M21 52
M22 52
markup 266
P
ParallelTimeline 179
Path 21, 27, 85
matrice 41 PathFigure 85, 94
matrices 39 PathGeometry 85
Matrix 51 PathSegment 93
Copyright 2011 Patrice REY
Q
QuadraticBezierSegment 95
S
ScA 135
QuadraticEase 234 Scale 54
QuarticEase 235 ScaleAt 54
QuinticEase 237 ScalePrepend 57
ScaleTransform 16, 67, 71
R
ScaleX 21, 73
ScaleY 21, 73
ScB 135
ScG 135
ScR 135
ScRGB 135
R 135 segment 39
RadialGradientBrush 146, 152 Segments 94
RadioButton 102 Setter 247
RadiusX 152 Setters 251
RadiusY 152 ShaderEffect 166
ShadowDepth 167
290 Index
Shape 27 System.Windows.Media.Effects 165
SineEase 238 System.Windows.Shapes 27
Skew 54
T
SkewTransform 67, 78
Slider 19
Slider.Value 74
SolidColorBrush 135, 146
Spline 212
SplineColorKeyFrame 205
SplineDoubleKeyFrame 213 TargetName 202, 247
Springiness 230 TargetType 264
sRGB 135 teinte 141
StartPoint 87, 149 template 263
StaticResource 90 TemplateBinding 266
Stop 181 TextBlock 19
StopStoryboard 177 TileMode 162
Stopwatch 244 Timeline 178
Storyboard 83, 177 TimelineGroup 179
Storyboard.TargetProperty 178 TimeSpan 180
StreamGeometry 85, 100 To 174
Stretch.Fill 31 ToolTip 165
Stretch.Uniform 31 Transform 54, 147
Stretch.UniformToFill 31 transformation géométrique 40
String.Format 138 transformations 39
StrokeDashArray 30 transformations successives 47
StrokeEndLineCap 28 TransformCollection 81
StrokeLineJoin 33 TransformFromDevice 241
StrokeStartLineCap 28 TransformGroup 16, 67, 81
StrokeThickness 14 TransformToDevice 241
structure Matrix 51 Transitions 271
style 247 Translate 54
Style 250 TranslatePrepend 59
style implicite 249 TranslateTransform 16, 67
SweepDirectionFlag 102 TranslateTransform.Matrix 74
SystemColors 135 translation 43
System.Diagnostics 244 triangle de Maxwell 141
système 13
Copyright 2011 Patrice REY
Trigger 177
système de coordonnées 13
système de coordonnées 2D 13
U
System.Reflection 136
System.Windows 49, 178
System.Windows.Data 21
System.Windows.Media 51, 145
System.Windows.Media.Animation 178
System.Windows.Media.ColorConverter 135
X
UIElement 27
Unfocused 270
Union 90
UserControl.Resources 88
x:Key 250
V
Xor 90
x:Type 250
Valid 270
ValidationStates 270
Value 81, 247
ValueChanged 143
vecteur 39
Z
Zoomer 19
vecteurs 39
Vector 39, 49
Viewbox 169, 269
Visual 118, 146, 157
VisualBrush 146, 157
VisualBrush.Visual 157
VisualState 177, 270
VisualStateGroup 270
VisualStateManager 270
VisualTransition 271
VisualTree 264
VisualTreeHelper 118
W
Width 14
WindowBrush 135
WindowBrushKey 135
WindowColor 135
WindowColorKey 135
Guide
de
programmation
Initiation au jeu 2D avec Silverlight 4
(ISBN 978-2-8106-1168-3)
chapitre 1 : la vectorisation
chapitre 2 : création du projet
chapitre 3 : boucle de jeu et rendu
chapitre 4 : le déplacement des images
chapitre 5 : la détection des collisions
chapitre 6 : le score et la fin de partie
chapitre 7 : un écran d’accueil
chapitre 8 : la pause et l’arrêt de jeu
chapitre 9 : sonoriser le jeu
chapitre 10 : téléchargement du jeu
http://www.decitre.fr/livres/Initiation-au-jeu-avec-silverlight-4.aspx/9782810611683
Les structures de données
illustrées avec WPF et C#4
(ISBN 978-2-8106-1387-8)
chapitre 1 : la récursivité
chapitre 2 : les piles
chapitre 3 : les files
chapitre 4 : les listes chaînées
chapitre 5 : les arbres
chapitre 6 : les méthodes de tri
chapitre 7 : les recherches
chapitre 8 : les tables
http://www.decitre.fr/livres/Les-structures-de-donnees.aspx/9782810613878
La programmation graphique 2D de WPF 4
Edition
Edit
di ion numé
numériqu
numérique
n érique
www.reypatrice.fr
ISBN : 978-2-8106-1199-7
Éditeur :
Books on Demand GmbH, 41 €
12/14 rond point des
Champs Élysées,
75008 Paris,
France