IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Windows Phone 7.

Développez pour Windows Phone 7 en C# et Xaml. Cours complet

Publié le 19 octobre 2012


IV. XAML et interface utilisateur
IV-A. Définition du XAML
IV-B. Balisage
IV-C. Casse, espace, tabulation, commentaire
IV-D. Objet, attribut, propriété
IV-E. Élément racine
IV-F. Code-behind, évènements
IV-G. Extension de balisage
IV-H. Espace de noms
IV-I. Remarque importante
V. Silverlight pour WP
V-A. La classe 'Control'
V-A-1. Ajouter un contrôle
V-A-2. Particularités des contrôles
V-A-3. Évènements
V-A-4. Saisie tactile
V-A-5. Modèle de contenu
V-B. Les applications et pages
V-C. Conteneur, position, dimensions des contrôles
V-C-1. Hiérarchie des contrôles, arbre
V-C-2. Nom, position et dimensions d'un contrôle
V-C-3. Plan
V-D. Aspect des contrôles
V-D-1. Propriétés des contrôles
V-D-2. Contrôle contenant des contrôles
V-D-3. Aspect visuel des contrôles: template visuel, style
V-D-4. Transformation
V-D-5. Font
V-E. Couleur, remplissage de surface
V-E-1. Couleur
V-E-2. SolidColorBrush
V-E-3. LinearGradientBrush
V-E-4. RadialGradientBrush
V-E-5. ImageBrush
V-E-6. Masque d'opacité
V-E-7. Nombre de couleurs et dégradés
V-F. Ressources
V-F-1. Les dictionnaires de ressources
V-F-1-a. Ressources 'simples'
V-F-1-b. Les styles
V-F-1-c. Les modèles de contrôle : ControlTemplate
V-F-1-d. Les modèles de données : Data Template
V-F-2. Les fichiers de ressources
V-F-3. Ressources Windows Phone
V-G. Les liaisons de données ou Binding
V-G-1. Principes du Binding
V-G-2. Liaison entre contrôles
V-G-3. Liaison Collection-ListBox
V-G-4. Liaison collection d'objets, ListBox
V-G-5. Convertisseur
V-G-6. Binding avec tri, filtrage, image
V-G-7. Générateur de liaison
V-G-8. Erreur dans le binding


IV. XAML et interface utilisateur


IV-A. Définition du XAML

Windows Phone 7 utilise pour décrire l'interface une version spécifique de Silverlight. Le code source de silverlight est en XAML= "eXtensible Application Markup Language". Prononcez "Zammel". Il s'agit d'un langage XML permettant de définir des interfaces d'applications (WPF, silverlight). En d'autres termes, quand vous dessinez une interface utilisateur en silverlight, elle est enregistrée en XAML.

Le code XAML respecte la syntaxe XML ; il est enregistré dans un fichier ayant l'extension .xaml.



IV-B. Balisage

XAML est un langage 'balisé'.

Qu'est ce qu'une balise ? 'Élément Sémantique de base' des langages de balisage. En XML une balise est un mot clé, un élément. Elle est comprise entre crochets et possède un nom et parfois des attributs. En XAML l'élément est le nom d'un objet visuel (Buttom, TextBlock…)d'une propriété de cet objet visuel (Background,Content…), ou de tout élément intervenant dans l'UI (une Brush, un ListItem…) .

Toutes les balises doivent être ouvertes puis refermées . On retrouvera donc toujours une balise de début et une balise de fin. La balise de fin porte le même nom que la balise de début à l'exception du signe / qui le précède.

Exemple :

<Button>
</Button>
On peut combiner balise de début et de fin :

<Button />
Cela suffit à afficher un bouton.

Les balises peuvent être intriquées :

<Grid>
 <Button> 
 </Button>
</Grid>
La dernière balise ouverte doit être la première fermée. Ici on a mis un bouton dans une grille.



IV-C. Casse, espace, tabulation, commentaire

XAML est sensible à la casse : majuscules et minuscules ne sont pas équivalentes. Les espaces et tabulations sont ignorés.

Attention donc quand on tape du code XAML : 'grid' ou 'click' ne sont pas acceptés, il faut taper 'Grid' et 'Click'. Le nom des types et attributs commence par une majuscule.


Les commentaires sont ignorés. Voici un commentaire en XAML :

<!--Commentaire
    ici -->

IV-D. Objet, attribut, propriété

On a vu que pour créer un objet, il suffit d'écrire :

<Button>
</Button>

Un objet visuel a des propriétés : la couleur de fond, les dimensions d'un bouton, le texte dans un TextBlock sont les propriétés. Il y a différentes manières d'indiquer les valeurs d'une propriété d'un objet en XAML.

1- Les propriétés des objets visuels peuvent s'écrire sous forme d' attributs. Un attribut est le nom d'une propriété de la balise souvent associé à une valeur, il est mis dans la balise d'ouverture après le nom de la balise :

<Button Background="Blue" Foreground="Red" Content="C'est un bouton"/>
Ici dans un bouton, on a utilisé les attributs Background, Foreground Content, pour indiquer la couleur du fond, du texte et le texte à afficher dans le bouton. À un attribut on affecte une valeur qui est entre guillemets et est un simple texte. On remarque que la syntaxe est simple mais on ne peut mettre que des valeurs simples. Les attributs sont séparés par un espace.


2- Les propriétés des objets visuels peuvent aussi s'écrire sous forme d'objet balisé dans un format alternatif appelé "Property element syntax" (syntaxe d'élément propriété), qui étend la syntaxe de XML. Une propriété a aussi une balise de début et de fin, la syntaxe est celle de l'XML c'est à dire de la forme :

<TypeName.Property>
Voyons, par exemple, la propriété Background d'un bouton :

<Button.Background>
</Button.Background>
L'intérêt de cette syntaxe est qu'entre les deux balises, on peut mettre d'autres balises ou un élément complexe (comme une Brush, par exemple).

Voici l'exemple de notre bouton :

<Button>
<Button.Background>
<SolidColorBrush Color="Blue"/>
</Button.Background>
<Button.Foreground>
<SolidColorBrush Color="Red"/>
</Button.Foreground>
<Button.Content>
C'est un bouton
</Button.Content>
</Button>

3- Propriété par défaut.

Pour le texte affiché dans un bouton, on a vu qu'on pouvait l'écrire avec l'attribut content.

<Button Content="Cliquez ici !"/>
Et aussi sous forme d'objet.

<Button>
<Button.Content>
Cliquez ici
</Button.Content>
</Button>
Mais comme Content est la propriété par défaut des boutons, on peut l'écrire aussi comme cela :

<Button>Cliquez ici !</Button>
De même la propriété 'Text' d'un TextBlock peut s'écrire ainsi :

<TextBlock>
Hello!
</TextBlock>
On parle ici d'élément de contenu.

Remarque : on a accès dans un objet aux propriétés qui sont héritées de la classe de base. Par exemple, dans un bouton, on a accès à la propriété BackGround qui est une propriété héritée de 'controls'.


4- Propriété attachée.

On peut utiliser dans un objet une propriété 'attachée' appartenant au parent :

<DockPanel>
    <Button DockPanel.Dock="Left">Annuler</Button>
</DockPanel>
Dans le bouton on utilise DockPanel.Dock qui n'est pas une propriété des boutons mais une propriété du DockPanel, le parent.
Le bouton est collé sur le bord gauche du DockPanel grâce à la propriété 'attachée' Dock qui prend la valeur 'Left' de l'énumération Dock.


5- Propriété de liste.
Beaucoup de contrôles conteneurs peuvent contenir des contrôles enfants. Exemple avec un StackPanel (conteneur permettant d' empiler des contrôles) dans lequel on met plusieurs boutons ; on devrait normalement utiliser la balise 'StackPannel.Child'.

<StackPanel>
  <StackPanel.Children>
    <Button>First Button</Button>
    <Button>Second Button</Button>
  </StackPanel.Children>
</StackPanel>
En fait, comme c'est une liste, on peut omettre cette balise 'StackPanel.Child' car silverlight sait que dans un StackPanel il y a une collection de contrôles :

<StackPanel>
    <Button>First Button</Button>
    <Button>Second Button</Button>
</StackPanel>


IV-E. Élément racine

Il doit y avoir un élément racine (root) et un seul dans un fichier XAML, il contient tous les autres. En WP c'est une grille.

<!--LayoutRoot est la grille racine  tout le contenu de la page est placé-->
<Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

    <!--TitlePanel contient le nom de l'application et le titre de la page-->
    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
    <TextBlock x:Name="ApplicationTitle" Text="MON APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
    <TextBlock x:Name="PageTitle" Text="nom de la page" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>

        <!--ContentPanel - placez tout contenu supplémentaire ici-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
          
        </Grid>
    </Grid>

IV-F. Code-behind, évènements

L'interface est en XAML. Le code (dit code-behind) est en C#. Le code XAML et le code-behind sont identifiés en spécifiant un espace de noms et une classe.

Soit un logiciel nommé 'CalculeTout', par exemple.

En haut du fichier XAML qui décrit l'interface (fichier .xaml) :

<phone:PhoneApplicationPage 
x:Class="CalculeTout.MainPage"
La classe se nomme "CalculeTout.MainPage" (cela correspond à 'EspacedeNom.NomPage').
On remarque que WP a pris, pour le nom de l'espace de noms, le nom de l'application. Et la page de démarrage se nomme par défaut 'MainPage'.

En haut du fichier C# contenant le code (fichier .xaml.cs) :

namespace CalculeTout
{

    public partial class MainPage : PhoneApplicationPage
    {}
}
On a bien l'espace de noms qui est le même que celui de la page XAML (nom de l'application par défaut) et la page qui est en fait une classe dont le nom est 'MainPage'. C'est une classe partielle (partial), l'autre partie de la classe étant dans le code XAML.



Si dans la page on ajoute un Bouton et si on double-clique sur ce bouton, on se retrouve dans le code-behind en C# qui a été crée automatiquement dans le fichier .cs :

namespace CalculeTout
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructeur
        public MainPage()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Hello");
        }
    }
}
Il y a eu création d'une fonction 'button1_Click' qui contient le code lié à l'évènement. Elle sera exécutée quand l'utilisateur cliquera sur le bouton.


On aurait pu créer soi-même la gestion des évènements en nommant la fonction liée au 'Click' du bouton dans le code XAML :

<Button Name="Button1" Click="MyButtonClick">
Il faut dans ce cas ensuite créer une fonction MyButtonClick en C#.

 private void MyButtonClick(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Hello");
        }
sender est l'objet qui a déclenché l'évènement (le bouton ici).

e est de type RoutedEventArgs et contient les arguments de l'évènement.


On remarque que pour pallier la disparition des 'groupes de contrôles' et pour que le clic, sur plusieurs boutons, exécute la même fonction, on peut indiquer Click="MyButtonClick" pour plusieurs boutons.

<Button Name="Button1" Click="MyButtonClick">
<Button Name="Button2" Click="MyButtonClick">
 


IV-G. Extension de balisage

On a vu qu'un attribut avait une valeur, mais il peut aussi avoir comme valeur une extension.

Les principales extensions sont les Binding et les ressources.

Les caractères { et } indiquent une extension de balisage (markup extension).

Exemple d'une extension : dans un 'Border', on utilise comme Style une ressource (staticResource) nommée PageBackground.

<Border Style="{StaticResource PageBackground}">
Exemple d'une extension avec ici une liaison de données (Binding). Ici le texte du TextBlock prendra la valeur de Title.

<TextBlock Text="{Binding Title}" />

x:Key.

Définit une clé unique (un 'nom' unique ) pour chaque ressource dans un ResourceDictionnary ; pour utiliser cette ressource, on l'appellera par son nom.
Créons une ressource pour une grille, c'est un LinearGradientBrush (un dégradé) que l'on 'nomme' "FunkyBrush" (c'est son nom, une clé unique).

<Grid.Resources>
    <LinearGradientBrush x:Key="FunkyBrush">
      <GradientStop Color="Yellow" Offset="0" />
      <GradientStop Color="Green" Offset="1" />
    </LinearGradientBrush>
</Grid.Resources>
Ensuite on peut l'utiliser dans la grid :

<Button Background="{StaticResource FunkyBrush}">Click Me</Button>
Code complet :

<Grid>
  <Grid.Resources>
    <LinearGradientBrush x:Key="FunkyBrush">
      <GradientStop Color="Yellow" Offset="0" />
      <GradientStop Color="Green" Offset="1" />
    </LinearGradientBrush>
  </Grid.Resources>
  <Button Background="{StaticResource FunkyBrush}">Click Me</Button>
</Grid>

La dernière ligne peut aussi être écrite sans extension de balisage, sous la forme :

<Button Content="Click Me">
<Button.Foreground>
<StaticResource ResourceKey="FunkyBrush" />
</Button.Foreground>
</Button>

On ajoute que :

x:Name.

Identifie de manière unique les éléments définis en XAML comme les contrôles.


x:Class.

Définit l'espace de noms et le nom d'une classe.


IV-H. Espace de noms

Chapitre difficile ! À lire plus tard et plusieurs fois.

On a vu qu'au début du fichier XAML, on pouvait inclure un espace de noms grâce à 'xmlns:' (XML Name Space) et ainsi donner accès à des objets extérieurs.



La syntaxe est :

xmlns:<alias>="<namespace identifier>"
alias est maintenant le nom local de l'espace de noms 'namespace identifier'.

Pour utiliser un objet de l'espace de noms.

QName = <alias>:<local name>

Voyons les espaces de noms qui sont déjà présents.
Déclarations des espaces de noms silverlight et XAML
Ce sont les deux premières lignes du code XAML. Elle indiquent une adresse Web.

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

C'est l'espace de noms XAML.
:x est là pour créer l'alias x (remarquer le ':').
La ligne indique l'espace de noms 'xaml', dessous il se nommera 'x:',
on le retrouve dans x:Key, x:Name…


xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
C'est l'espace de noms silverlight (à noter que cet espace de noms contient les classes des objets visuels. Il contient les classes Button, ListBox…)
On remarque que la ligne commence par xmlns= sans ':' il n'y a donc pas d'alias et les classes de cet espace de noms peuvent être utilisées sans préfixe.


On pourra instancier un objet dont la classe est dans cet espace de noms :

<Button Name="ButtonOk"/>
On remarque qu'on utilise 'Name' car c'est l'espace de noms par défaut.

On va maintenant voir comment inclure d'autres espaces de noms dans le code XAML.
Il faudra utiliser 'clr-namespace' : espace de noms CLR (Common Language Runtime) déclaré dans l'assembly qui contient les types publics. On déclarera l'espace de noms et si nécessaire l'assembly (nom de la DLL) ; ici on n'utilise plus l'URL.

Exemple : espace de noms: 'MyNameSpace' contenant une classe 'SuperButton'.

xmlns:Mns="clr-namespace:MyNameSpace"
Remarquons qu'on a donné un nom, un alias (Mns) à l'espace de noms 'MyNameSpace' grâce à 'xmlns:Mns'.
On utilise les ':'. Maintenant quand on utilise 'Mns' on utilise en fait 'MyNameSpace'.
L'assembly peut être omis si l'espace de noms référencé est défini dans le même assembly que l"application.

Ensuite, toujours en XAML on peut utiliser un objet de cet espace de noms :

<Mns:SuperButtom x:Name="ButtonOk" />
Remarquons qu'on utilisera la syntaxe 'Espacedenom:Objet' avec 'Mnp:MyObjet'.
On utilise 'x:Name' car l'instance appartient à un autre espace de noms que celui par défaut.
Cela permettra d'utiliser le nom dans le code-behind.


Si l'espace de noms fait partie d'un autre assembly (d'une autre ddl), il faudra ajouter le nom de l'assembly.

Par exemple, on a un espace de noms 'CustomClasses' qui est dans un assembly nommé 'Custom' (la DLL custom.dll) ; on veut utiliser la classe 'MyControle' de cet espace de noms, on doit écrire :

xmlns:mycustom="clr-namespace:CustomClasses;assembly=Custom"
On remarque après 'assembly' il y a '='.

Puis pour utiliser 'MyControle'.

<mycustom:MyControle  x:Name="MyControle" />

Exemples : comment utiliser dans le code XAML une classe créée dans le code C#.

Je crée une classe 'MyButton' (qui hérite de Button) dans l'espace de noms 'MyControle'.

Voyons le code C# (il est dans le code C# de l'application) :

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using Microsoft.Phone.Shell;
using Microsoft.Phone.Controls;

namespace MyControle
{
    public class MyButton : Button
    {
        // Constructeur
        public MyButton()
        {
        }
    }
}	    
Pour utiliser MyButton dans le code XAML :

xmlns:MyControle="clr-namespace:MyControle"

<MyControle:MyButton  x:Name="ButtonOk">Ok</MyControle:MyButton>
L'interface a un comportement bizarre. Il faut compiler le projet ce qui compile le code correspondant à la classe ensuite pas de problème.



Autre exemple : si je veux utiliser des string dans un dictionnaire de ressources, il faut que j'importe l'espace de noms 'system' de mscorelib.dll :

xmlns:system="clr-namespace:System;assembly=mscorlib"
<!--l'alias de system est ici system-->

<!-- Ensuite, dans une ressource je pourrais déclarer une string nommée 'LeTitre'-->
<!--grâce à system:String-->
    <phone:PhoneApplicationPage.Resources>
        <system:String x:Key="LeTitre">Cours Wp</system:String>
    </phone:PhoneApplicationPage.Resources>

Ne pas oublier que  :
x:Class NameSpace.PageName, qui est la première ligne de la page XAML, permet d'indiquer l'espace de noms et le nom de la classe de la page et de faire ainsi le lien entre la classe XAML et la classe partielle de même nom contenant le code en C#.


x:Name identifie de manière unique des éléments objets afin d'accéder à l'objet instancié à partir du code-behind ou du code général.
x:Key est lui, utilisé dans les ressources.



IV-I. Remarque importante

Il est important de comprendre que tout le code écrit en XAML peut aussi être écrit en C#.

<StackPanel>
<Button Name="MonBouton"  Width = "300"  Height="100" >Ok</Button>
</StackPanel>
Ce code, dans la fenêtre XAML crée un bouton ; on peut aussi créer ce bouton dans le code C# :

StackPanel ContentPanel;
Button MyButton= new Button ();
MyButton.Width = 300;
MyButton.Height = 100;
MyButton.Content = "Ok";
ContentPanel.Children.Add(MyButton);
Ici on déclare un bouton, on donne ses dimensions et le texte, enfin on l'ajoute au StackPanel nommé ContentPanel.



V. Silverlight pour WP


Windows Phone 7.1 utilise silverlight 4 avec certaines particularités pour Windows Phone:
Il y a en plus dans WP 7.1 les éléments relatifs aux opérations de touché de l'écran.

On peut charger la Documentation silverlight 4 Offline (fichier .CHM) en français,
Documentation silverlight 4 Attention c'est silverlight et pas silverlight pour Windows Phone, mais c'est une bonne doc !


V-A. La classe 'Control'

Les contrôles permettent de créer une interface utilisateur.

Les contrôles silverlight dérivent de la classe Control qui fait partie de l'espace de noms System.Windows.Controls.
Voici les classes dont dérive Control :
System.Object
System.Windows.DependencyObject
System.Windows.UIElement
System.Windows.FrameworkElement
System.Windows.Controls.Control.

C'est la classe UIElement qui donne le pouvoir d'apparaître comme objet visuel et de recevoir des entrées.
La classe Control hérite des propriétés des classes parentes.
Aussi tous les contrôles ont des propriétés communes :
Les propriétés de dépendances.
Les propriétés de positionnement : Height, Width, Margin, HorizontalAlignment…
Les évènement de vie : Load, Unload, DataContext.
Le style.
Les ControlTemplate.
Les polices de caractères utilisées : FontFamily, FontSize…


V-A-1. Ajouter un contrôle

Pour mettre un contrôle dans une page, un bouton par exemple, il y a quatre manières :

- en mode 'Design', aller le chercher dans la boîte à outils et le déposer sur la page (drag and drop) ;

-l'ajouter à l'aide de code XAML ;

Il faut taper dans la fenêtre XAML :

<Button>Ok</Button>
Cela ajoute à la page un bouton sur lequel est affiché 'Ok'.

-l'ajouter dans le code C# :

Button MyButton= new Button ();
MyButton.Width = 300;
MyButton.Height = 100;
MyButton.Content = "Ok";
ContentPanel.Children.Add(MyButton);
On note qu'après avoir déclaré le bouton, on l'a ajouté aux enfants d'un conteneur nommé ContentPanel.


- utiliser 'Expression Blend' l'application graphique.



Il est possible ensuite de modifier ses propriétés.

Modifions la couleur de l'arrière plan du bouton, par exemple.

- En mode 'Design', dans la fenêtre de propriété en bas à droite.

-à l'aide de code XAML (ajout d'attribut 'simple') :

<Button Background="Blue" >Ok</Button>
ou de manière plus complexe par ajout d'une Brush sur le fond :

<Button FontSize="14" FontWeight="Bold">
   <Button.Background>
    <LinearGradientBrush StartPoint="0,0.5" 
                            EndPoint="1,0.5">
      <GradientStop Color="Green" Offset="0.0" />
      <GradientStop Color="White" Offset="0.9" />
    </LinearGradientBrush>
  </Button.Background>
  Ok
</Button>
-A l'aide de code C# :

MyButton.Background= new SolidColorBrush (Colors.Blue);
On crée une nouvelle instance de SolidColorBrush de couleur bleue.



V-A-2. Particularités des contrôles

Propriétés de dépendance :

Les propriétés des contrôles et leur valeur ne sont pas 'figées' (comme dans les Windows Forms où elles ont une valeur donnée) mais elles peuvent 'dépendre' d'autres choses. Les propriétés de dépendance permettent de calculer la valeur d'une propriété en fonction de la valeur d'autres entrées. Ces autres entrées peuvent être des propriétés système (des thèmes et des préférences utilisateur), des valeurs déterminées au dernier moment (liaison de données, animations), des ressources et des styles ou des valeurs issues de relations parent-enfant d'autres éléments.


Propriétés attachées :

Les propriétés attachées permettent à des éléments enfants de spécifier des valeurs pour une propriété définie dans un élément parent. Cela permet de faire en sorte que les éléments enfants informent l'élément parent sur la manière dont ils doivent être présentés dans l'interface.

La propriété DockPanel.Dock en est un exemple. Dans un Panel, il y a plusieurs enfants, plusieurs boutons, par exemple. La propriété DockPanel.Dock de chaque bouton (enfant) permet au Panel (le parent) de positionner les boutons dans le Panel.


Particularités des contrôles Silverlight:

Les contrôles qui ont une propriété 'Content' (les boutons, par exemple) peuvent contenir un Objet (du texte oui mais aussi une Grid, une Image, un StackPanel…), c'est extrêmement puissant.



V-A-3. Évènements

Créons un bouton avec le code XAML suivant :

<Button>Ok</Button>

Si je double-clique sur ce bouton dans le designer, je me retrouve dans le code-behind en C# qui a créé automatiquement la fonction évènement suivante :

 private void button_Click(object sender, RoutedEventArgs e)
        {
          
        }
Quand l'utilisateur du logiciel clique sur le bouton, c'est cette fonction qui sera exécutée.

De plus il y a eu un ajout dans le code XAML du bouton :

<Button Click="Button_Click>Ok</Button>
Click="Button_Click indique que lorsque l'évènement Click survient sur le bouton la fonction Button_Click est exécutée.


On aurait pu ajouter l'attribut Click="OnClick" en le tapant au clavier, cela aurait donné le code XAML suivant.

<Button Click="OnClick">Ok</Button>
Dans ce cas, en double-cliquant sur le bouton, la routine suivante aurait été créée :

private void OnClick(object sender, RoutedEventArgs e)
        {
          
        }

Il est aussi possible de taper 'Click="bouton_click"', puis de mettre le curseur sur bouton_click et de faire un clic droit. Cela ouvre un menu, cliquez sur 'Naviguer vers le gestionnaire d'évènements'.

Cela crée dans le code-behind la fonction évènement :

  private void bouton_click(object sender, RoutedEventArgs e)
        {

        }

sender est l'objet qui a déclenché l'évènement, je dis bien 'Object' (il faudra le convertir en Button pour utiliser la propriété Name par exemple).

e contient les arguments de l'évènement, remarquons que e est de type RoutedEventArgs (et pas EventArgs comme dans les Windows Forms).


Sur un Windows Phone l'utilisateur tape avec son doigt sur l'écran.
Quel évènement utiliser ? Bien sûr pour un bouton c'est Click. Sur une ListBox il y a les évènements KeyDown, KeyUp, MouseLeftButtonDown, SelectionChanged, MouseLeftButtonUp ; ce dernier semble bien fonctionner pour intercepter la 'tape' du doigt.


Évènement routé :

Quand survient un évènement sur un contrôle, un clic sur un bouton, par exemple, si cet évènement n'est pas traité au niveau du bouton, un parent comme le StackPanel qui contient le bouton peut traiter l'évènement. Cela est possible car l'évènement 'remonte' de parent en parent.

On peut ainsi dans le StackPanel 'parent' gérer l'évènement Click de tous les boutons enfants :

  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="ClickHandlerCommun">
    <Button Name="ButtonOui" Width="Auto" >Oui</Button>
    <Button Name="ButtonNon" Width="Auto" >Non</Button>
  </StackPanel>


Dans le code C# on peut créer un contrôle et le gestionnaire d'évènements routé correspondant :

 //Déclaration d'un bouton
            Button myButton = new Button();// 'création d'un bouton 
            
            // Modification des propriétés du bouton
            myButton.Name="Bouton1";
            myButton.Height = 133;        // modification des dimensions du bouton
            myButton.Width = 155;
            myButton.HorizontalAlignment = HorizontalAlignment.Stretch;
            myButton.Content = "Button";
            
            //Gestion de l'évènement Click
            myButton.Click += new  RoutedEventHandler(button1_Click);
            
            //Ajout du bouton au conteneur
            ContentPanel.Children.Add(myButton);// 'met le bouton dans le conteneur ContentPanel
myButton.Click += new RoutedEventHandler(button1_Click) permet de créer un RoutedEventHandler qui indique d'exécuter la fonction 'Button1_Click' quand l'utilisateur clique sur le button1.


On peut écrire plus simplement :

myButton.Click += button1_Click;


V-A-4. Saisie tactile

On utilise habituellement les évènements souris.

Un doigt qui touche un bouton déclenche un évènement Click.
Soit un bouton en Xaml :

<Button Click="Button_Click>Ok</Button>
Le Click exécute :

 private void Button_Click(object sender, RoutedEventArgs e)
        {

        }

Un doigt qui touche un objet visuel (UIElement) est converti en un évènement de souris ; évènement MouseLeftButtonDown lorsque vous placez votre doigt, évènement MouseLeftButtonUp lorsque vous levez votre doigt, MouseMove lorsque vous faites glisser votre doigt.
Il y a aussi MouseLeave (sortie de la zone) et MouseEnter (entrée dans la zone).
Les arguments de ces évènements sont : Sender l'objet d'où vient l'évènement et e de type MouseButtonEventArgs.

Exemple, gestion de l'évènement MouseLeftButtonUp sur un rectangle :

 <Rectangle Height="67" Name="rectangle1" Width="331" 
 Fill="Transparent"
 MouseLeftButtonUp="Rectangle_MouseLeftButtonUp"/>
    
Noter que pour que cela fonctionne, il faut remplir le rectangle ; ici on met une couleur transparente grâce à Fill.
La méthode suivante sera exécutée lors du 'levé' du doigt sur le rectangle.

 private void Rectangle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {

        }

Il existe aussi des évènements de bas niveau, des évènements de haut niveau sur le UIElement (Tap, DoubleTap …) et des évènements de gesture dans le toolkit (voir chapitre sur les gestures).


V-A-5. Modèle de contenu

Pour être complet, il y a quatre modèles de contenu :

- ContentControl :

sa propriété Content contient un objet. Exemple : Button ;

- HeaderContentControl :

sa propriété Content contient un objet. Sa propriété Header fournit un titre au contrôle. Exemples : GroupBox, TabItem ;

- ItemsControl :

sa propriété Items contient une collection d'objets de même type. Exemple : ListBox contient des ListBoxItem, il y a les objets conteneurs 'Panel' (comme les Grid, StackPanel…) qui contiennent une collection d'objets (Children) ;

- HeaderItemsControl :

sa propriété Header fournit un titre au contrôle. Sa propriété Items contient une collection d'objets.



V-B. Les applications et pages

Une application Windows Phone contient une Frame dans laquelle se trouve des Pages.

Chaque page est dans un fichier .xaml.
La page correspond au contenu d'un écran. Un appareil Windows Phone 7 possède un écran de 480 × 800 pixels.

Quand on crée un nouveau projet nommé MyProjet, Visual Studio crée une page nommé MainPage.xaml (la page de démarrage) qui contient :

<phone:PhoneApplicationPage 
    x:Class="MyProjet.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <!--LayoutRoot est la grille racine  tout le contenu de la page est placé-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contient le nom de l'application et le titre de la page-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock x:Name="ApplicationTitle" Text="MON APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
        <TextBlock x:Name="PageTitle" Text="nom de la page" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - placez tout contenu supplémentaire ici-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
          
        </Grid>
    </Grid>
 
    <!--Exemple de code illustrant l'utilisation d'ApplicationBar-->
    <!--<phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Bouton 1"/>
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Bouton 2"/>
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="ÉlémentMenu 1"/>
                <shell:ApplicationBarMenuItem Text="ÉlémentMenu 2"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>-->

</phone:PhoneApplicationPage>
Dans la balise PhoneApplicationPage :
x:Class="MyProjet.MainPage indique l'espace de noms (le nom du projet par défaut) et la classe de la page.
En effet une page se présente comme une classe (dans le code XAML) plus le code C# correspondant qui se trouve dans une classe partielle.

On a ensuite la déclaration des espaces de noms (xml name space) utilisés dans la page :
L'espace de nom silverlight :
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
L'espace de nom XAML :
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
L'espace de noms contenant les contrôles :
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
Et d'autres :
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"

On remarque que la première ligne commence par xmlns= sans ':', il n'y a donc pas d'alias et les classes de cet espace de noms peuvent être utilisées sans préfixe. Dans les autres lignes il y a un alias (:x par exemple) ce qui oblige à utiliser l'alias ( x:Name par exemple).

Le "d" (de "designer") et le "mc" (de "markup compatibility") dans la déclaration des espaces de noms sont utilisés par les programmes comme Expression Blend et le designer de Visual Studio. DesignerWidth et DesignerHeight sont ignorés durant la compilation.

Puis on indique la police à utiliser dans la page, sa taille et le Foreground (couleur des caractères) :
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"


On indique aussi l'orientation supportée et l'orientation en cours :
SupportedOrientations="Portrait" Orientation="Portrait".
shell:SystemTray.IsVisible="True" indique d'afficher la barre système.

On a ensuite une grille qui est l'élément racine.
Cette grille contient des StackPanel affichant le titre de la page puis le contenu de l'application.

Il doit y avoir un élément racine (root) et un seul dans un fichier XAML, il contient tous les autres : c'est la grille 'LayoutRoot'.



Il y a aussi création d'un fichier MainPage.xaml.cs qui contient le code en C#.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;

namespace MyProjet
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructeur
        public MainPage()
        {
            InitializeComponent();
        }
    }
}
L'espace de noms est celui du projet (MyProjet). Il y a une 'partial class' nommée MainPage.
La page est donc composée d'une classe en XAML complétée d'une partie C# : une classe partielle.
L'interface graphique écrite en XAML sera générée à la compilation et fusionnera avec le reste de la classe partielle définie dans le code-behind. Nous avons donc une seule et même classe définie dans deux fichiers différents (le fichier généré à partir du XAML étant caché de l'utilisateur et se nommant MainPage.Xaml.g.i.cs).


L'explorateur de solution en haut à droite permet de voir de quoi se compose le projet 'MyProjet'.

Properties donne accès aux propriétés du projet.

Références indique les DLL chargées dans le projet.

On voit aussi :
le code XAML de l'application : App.xaml et en developpant, App.xaml.cs ;
les icônes et images du projet : ApplicationIcon.png, Background.png, SplashScreenImage.jpg ;
la page (MainPage ici) composée de l'interface (MainPage.xaml) et du code C# (MainPage.xaml.cs).


Voyons le détail du fichier App.xaml, le fichier 'Application' dans lequel on peut mettre les ressources de l'application (balise Application.resources).

<Application 
    x:Class="MyProjet.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"       
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone">

    <!--Ressources d'applications-->
    <Application.Resources>
    </Application.Resources>

    <Application.ApplicationLifetimeObjects>
        <!--Objet requis qui gère les événements de durée de vie pour l'application-->
        <shell:PhoneApplicationService 
            Launching="Application_Launching" Closing="Application_Closing" 
            Activated="Application_Activated" Deactivated="Application_Deactivated"/>
    </Application.ApplicationLifetimeObjects>

</Application>


Voyons le détail du fichier App.xaml.cs (pour le voir, cliquer sur le petit triangle à droite de 'App.xaml'), le fichier comporte, par défaut, des commentaires qui expliquent bien la fonction de chaque ligne.

namespace CalculeTout
{
    public partial class App : Application
    {
      
      
        /// <summary>
        /// Permet d'accéder facilement au frame racine de l'application téléphonique.
        /// </summary>
        /// <returns>Frame racine de l'application téléphonique.</returns>
        public PhoneApplicationFrame RootFrame { get; private set; }

        /// <summary>
        /// Constructeur pour l'objet Application.
        /// </summary>
        public App()
        {
            // Gestionnaire global pour les exceptions non interceptées. 
            UnhandledException += Application_UnhandledException;

            // Initialisation Silverlight standard
            InitializeComponent();

            // Initialisation spécifique au téléphone
            InitializePhoneApplication();

            // Affichez des informations de profilage graphique lors du débogage.
            if (System.Diagnostics.Debugger.IsAttached)
            {
                // Affichez les compteurs de fréquence des trames actuels.
                Application.Current.Host.Settings.EnableFrameRateCounter = true;

                // Affichez les zones de l'application qui sont redessinées dans chaque frame.
                //Application.Current.Host.Settings.EnableRedrawRegions = true;

        // Activez le mode de visualisation d'analyse hors production, 
        // qui montre les zones d'une page sur lesquelles une accélération GPU est produite avec une superposition colorée.
        //Application.Current.Host.Settings.EnableCacheVisualization = true;

        // Désactivez la détection d'inactivité de l'application en définissant la propriété UserIdleDetectionMode de l'objet
            // PhoneApplicationService de l'application sur Désactivé.
            // Attention :- À utiliser uniquement en mode de débogage. Les applications qui désactivent 
			//la détection d'inactivité de l'utilisateur continueront de s'exécuter
            // et seront alimentées par la batterie lorsque l'utilisateur ne se sert pas du téléphone.
            PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled;
            }

        }

        // Code à exécuter lorsque l'application démarre (par exemple, à partir de Démarrer)
        // Ce code ne s'exécute pas lorsque l'application est réactivée
        private void Application_Launching(object sender, LaunchingEventArgs e)
        {
           
        }

        // Code à exécuter lorsque l'application est activée (affichée au premier plan)
        // Ce code ne s'exécute pas lorsque l'application est démarrée pour la première fois
        private void Application_Activated(object sender, ActivatedEventArgs e)
        {
           
        }

        // Code à exécuter lorsque l'application est désactivée (envoyée à l'arrière-plan)
        // Ce code ne s'exécute pas lors de la fermeture de l'application
        private void Application_Deactivated(object sender, DeactivatedEventArgs e)
        {
            // Assurez-vous que l'état de l'application requis est persistant ici.
           
        }

        // Code à exécuter lors de la fermeture de l'application (par exemple, lorsque l'utilisateur clique sur Précédent)
        // Ce code ne s'exécute pas lorsque l'application est désactivée
        private void Application_Closing(object sender, ClosingEventArgs e)

        {
           
        }

        // Code à exécuter en cas d'échec d'une navigation
        private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            if (System.Diagnostics.Debugger.IsAttached)
            {
                // Échec d'une navigation ; arrêt dans le débogueur
                System.Diagnostics.Debugger.Break();
            }
        }

        // Code à exécuter sur les exceptions non gérées
        private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
        {
            if (System.Diagnostics.Debugger.IsAttached)
            {
                // Une exception non gérée s'est produite ; arrêt dans le débogueur
                System.Diagnostics.Debugger.Break();
            }
        }

        #region Initialisation de l'application téléphonique

        // Éviter l'initialisation double
        private bool phoneApplicationInitialized = false;

        // Ne pas ajouter de code supplémentaire à cette méthode
        private void InitializePhoneApplication()
        {
            if (phoneApplicationInitialized)
                return;

            // Créez le frame, mais ne le définissez pas encore comme RootVisual ; cela permet à l'écran de
            // démarrage de rester actif jusqu'à ce que l'application soit prête pour le rendu.
            RootFrame = new PhoneApplicationFrame();
            RootFrame.Navigated += CompleteInitializePhoneApplication;

            // Gérer les erreurs de navigation
            RootFrame.NavigationFailed += RootFrame_NavigationFailed;

            // Garantir de ne pas retenter l'initialisation
            phoneApplicationInitialized = true;
        }

        // Ne pas ajouter de code supplémentaire à cette méthode
        private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
        {
            // Définir le Visual racine pour permettre à l'application d'effectuer le rendu
            if (RootVisual != RootFrame)
                RootVisual = RootFrame;

            // Supprimer ce gestionnaire, puisqu'il est devenu inutile
            RootFrame.Navigated -= CompleteInitializePhoneApplication;
        }

        #endregion
    }
}

info En résumé : une page est donc une classe, elle est composée d'une partie XAML et d'une partie C#, tout cela est dans l'espace de noms ayant le nom de l'application.

Comment ouvrir une page nommée 'PageConvert.xaml' dans le code C# ?

 NavigationService.Navigate(new Uri("/PageConvert.xaml", UriKind.Relative));


V-C. Conteneur, position, dimensions des contrôles

Le positionnement des contrôles est fondamental, il faut bien le comprendre.

Comparaisons avec les WindowsForms.

En Windows Forms on pouvait mettre autant de contrôles que l'on voulait dans une fenêtre et on utilisait simplement les coordonnées du coin supérieur gauche d'un contrôle pour définir sa position. En Windows Forms, en cas de changement d'écran ou de modification des dimensions d'une fenêtre, si on voulait un réagencement, il faudrait tout gérer soit même.

En silverlight il faut utiliser la notion de conteneur et de position dans ces conteneurs. Le positionnement est relatif. Il y a capacité d'adaptation aux modifications d'affichage et de disposition des fenêtres. Silverlight gère la négociation entre les contrôles pour déterminer la disposition. Un contrôle informe son parent de l'emplacement et de la taille dont il a besoin ; deuxièmement, le parent informe le contrôle de l'espace dont il peut disposer. Cette disposition dynamique fait qu'un contrôle est positionné sur une page en fonction de son contenu, de son conteneur de disposition parent.

La disposition silverlight est basée sur les grilles (Grid), l'empilement (Stack) et l'ancrage des contrôles.



V-C-1. Hiérarchie des contrôles, arbre

Contrôles conteneurs et non conteneurs.

Certains contrôles (dits 'non conteneurs') ne peuvent contenir qu'un contrôle. Essayer de mettre deux TextBlock dans un bouton avec le designer ou une image et un texte dans un bouton : impossible !

Certains contrôles (dits 'conteneurs') peuvent contenir plusieurs contrôles. Une Grid, par exemple, qui peut contenir un élément dans chacune de ses cellules ; un StackPanel peut contenir en empilement de contrôles.

Pour un bouton par exemple, en C# :

MyButton.Content = "Ok"; 
affiche le texte 'Ok' dans le bouton.

En XAML :

<Button Content="Ok"/>

Content.

Dans un contrôle non conteneur, la propriété 'Content' ne peut contenir qu'un objet et un seul.
Pour un bouton par exemple, on a deux manières d'écrire le texte du bouton :

<Button Content="Ok"/>
ou :

<Button> Ok </Button>
car Content est la propriété par défaut.


Content peut contenir un texte mais pas seulement ; en fait il peut contenir un Objet (un seul) : du texte mais aussi un objet conteneur comme une Grid, un Canvas, un StackPanel… qui lui peut contenir plusieurs contrôles. Cela revient à mettre autant de contrôles que l'on veut dans un contrôle.

Les conteneurs comme une Grid par exemple, ont une propriété nommé Children qui permet d'ajouter plusieurs contrôles.

 MyGrid.Children.Add (Button1); 
À titre d'exemple, on peut mettre une Grid dans un bouton et mettre dans les cellules de cette Grid deux textes, une image, une vidéo…


Voyons maintenant un exemple en XAML d'un conteneur, un StackPanel contenant un TextBlock et trois boutons :

<StackPanel >
<TextBlock>Faire un choix:</TextBlock>
<Button >Option 1</Button>
<Button >Option 2</Button>
<Button >Option 3</Button>
</StackPanel>

Pour être complet certains contrôles ont un contenu spécifique : un TextBlock par exemple, qui ne peut afficher que du texte, a une propriété 'text' permettant d'inclure du texte (et uniquement du texte).


Graphes d'objets.

Ainsi il y a des objets dans d'autres et donc une hiérarchie des contrôles qui peut être représentée par un graphe.


Cela a son importance car on a vu qu'un évènement (un clic, par exemple) qui survient sur un contrôle (l'image en bas à droite) peut être routé, remonter et être exploité plus haut (au niveau de la grille, par exemple).



La classe VisualTreeHelper permet de trouver le parent ou les enfants d'un contrôle :

// GetParent permet de récupérer le parent de button1, un DependencyObjet
DependencyObject tb = VisualTreeHelper.GetParent (button1) as DependencyObject;

// Combien button1 a d'enfants?
int nbEnfant =VisualTreeHelper. GetChildrenCount( bouton1);


//Récupérer le premier enfant (index 0) de bouton1
TextBlock tb =VisualTreeHelper. GetChild( bouton1,0) as TextBlock;

// comme on sait que GetChild retourne un TextBlock on cast le dependencyObjet en TextBlock
//grâce à "as TextBlock"
À noter que GetChild retourne parfois des choses bizarres ! bogue ?



V-C-2. Nom, position et dimensions d'un contrôle

Ajoutons un bouton à la page, déplaçons le à la souris :

Cela affiche le code XAML suivant :

 <Button Content="Button" Height="95" HorizontalAlignment="Left" Margin="49,128,0,0" 
 Name="button1" VerticalAlignment="Top" Width="345" />
Name indique le nom du contrôle.
Content indique le texte affiché dans le bouton.
Height et Width indiquent les dimensions du bouton.
Margin indique les marges autour du bouton.
HorizontalAlignment indique sur quoi est ancré le bouton.

Voyons cela en détails :

Dimensions d'un contrôle.

Les dimensions sont, par défaut, en pixels (px) ou "device independente unit".


Noter que les valeurs des dimensions et coordonnées sont des 'Double' (nombre réel en virgule flottante double précision).

 <Button Name="button1" Content="Button" Height="80"  Width="150" />          

En C# :

Button1.Width="50";
Button1.Height="30;"

On peut utiliser une unité différente :
px (valeur par défaut) représente les device-independente units ou pixels (1/96th inch per unit ;)
in est l'abréviation du mot anglais « inches » (pouces) ; 1 in = 96 px ;
cm correspond à centimètres ; 1 cm = (96/2,54) px ;
pt correspond à points ; 1 pt = (96/72) px.

Exemple :

 <Button Name="button1" Content="Button" Height="4cm"  Width="1cm" />          
Largeur et hauteur du bouton en centimètres.


 <Button Name="button1" Content="Button" FontSize="36" />          
Hauteur de la police : 36 pixels soit 36x72/96 = 24 points.



Width et Height peuvent aussi prendre la valeur Auto. L'élément remplit alors la largeur ou la hauteur disponible dans le conteneur (cela semble fonctionner uniquement s'il n'y a pas de HorizontalAlignment ou de VerticalAlignment ni de Margin).

On peut spécifier :
MinWidth et MaxWidth (0 et infinity par défaut),
MinHeight et MaxHeigth.
MinWidth et MinHeight servent à indiquer des dimensions minimales afin qu'un texte, par exemple, ne soit pas tronqué.


Enfin ActualWidth et ActualHeigth indiquent les dimensions actuelles réelles, si des contraintes (relation avec les autres contrôles, disposition…) imposent des dimensions différentes de celles qu'on désirait. La disposition peut parfois rejeter les dimensions proposées.


Alignement :

VerticalAlignement et HorizontalAlignment définissent sur quoi est aligné le contrôle. Le contrôle y sera 'ancré', si on modifie les dimensions du conteneur, le contrôle suivra.

Cela remplace la propriété 'Anchor' des Windows Forms.

Rappelons que si on modifie la taille du conteneur, le contrôle reste à égale distance du bord sur lequel il est aligné (ancré).

Valeur possible pour VerticalAlignement : Top, Bottom, Center, Stretch (étire le contrôle et le remplit verticalement).

Valeur possible pour HorizontalAlignment : Left, Right, Center, Stretch (étire le contrôle et le remplit horizontalement).

Attention, si on renseigne Width et Height Stretch ne fonctionne pas !


Margin définit les distances au conteneur, les marges.

Margin="30,64,128,0" indique les marges gauche, supérieure, droite, inférieure.

Margin="30" indique que toutes les marges sont à 30.


Dans la fenêtre des propriétés d'un contrôle, la flèche à droite de la propriété 'Margin' ouvre l'éditeur de marges :


Paddding définit l'épaisseur d'une marge dans le contrôle. (valable dans certains contrôles seulement).

idea En pratique on positionne et on dimensionne le contrôle à la souris ce qui modifie en conséquence les propriétés Width, Height et Margin.
En résumé :
Silverlight commence par regarder la propriété "Width". Si elle est définie, c'est cette valeur qui est utilisée pour la largeur du composant, même si "HorizontalAlignment=Strech". Si aucune valeur n'est spécifiée ou qu'elle vaut "Auto", Silverlight regarde la valeur de "HorizontalAlignment". Si la valeur est "Strech", la largeur sera tout l'espace disponible. Sinon, la propriété "Width" étant sur "Auto", la largeur s'adaptera au contenu.


idea Bien comprendre qu'on ne travaille plus en coordonnées absolues de position du contrôle (Left, Top c'est fini…) mais plutôt en accrochant les contrôles (avec une marge autour) dans un conteneur (Grid, StackPanel).

Dans ce chapitre, on voit le positionnement d'un contrôle dans un conteneur, pour être complet, il faut aussi voir le chapitre sur les conteneurs et les règles de positionnement dynamique. Dans une grille par exemple, dans chaque cellule on pourra positionner un contrôle comme indiqué ici avec une 'margin' autour.


Exemple de création de contrôle entièrement en C# :
On peut avoir besoin au cours du déroulement de l'application de créer de toute pièce un bouton.
Ici on a mis le code dans le constructeur de la page.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;

namespace courscsharp
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructeur
        public MainPage()
        {
            InitializeComponent();
            
            //Déclaration d'un bouton
            Button myButton = new Button();// 'création d'un bouton 
            
            // Modification des propriétés du bouton
            myButton.Name="Bouton1";
            myButton.Height = 133;        // modification des dimensions du bouton
            myButton.Width = 155;
            myButton.HorizontalAlignment = HorizontalAlignment.Stretch;
            myButton.Content = "Button";  // texte affiché
            myButton.Margin = new Thickness(0, 0, 0, 370);
            
            //Gestion de l'évènement Click
            myButton.Click += new  RoutedEventHandler(button1_Click);
            
            //Ajout du bouton au conteneur
            ContentPanel.Children.Add(myButton);
			// 'met le bouton dans le conteneur ContentPanel
        }
        
        private void button1_Click(object sender, RoutedEventArgs e)
        //routine exécutée lors de l'évènement Click
        {
            MessageBox.Show("Ok");
        }
    }
On remarque que si on crée un contrôle dans le code C#, il est nécessaire d'ajouter ce contrôle à un conteneur déjà présent sinon le contrôle n'apparaît pas.
On voit aussi que Margin (comme Padding) accepte un objet Thickness (épaisseur). On en crée un avant de l'affecter à la propriété Margin.



V-C-3. Plan

Les objets peuvent être au premier plan ou au second plan : l'objet de premier plan apparaîtra au dessus des autres et les cachera s'il n'est pas transparent.

Dans le designer, cliquer sur un objet visuel, clic droit puis 'Ordre' puis 'Mettre au premier plan'.

En fait, il n'y a pas de propriété indiquant le plan, silverlight affiche les objets qui se trouvent dans un conteneur dans l'ordre du code XAML ; le fait de mettre un objet au premier plan met le code XAML de l'objet à la fin du conteneur :

 <!--ContentPanel - placez tout contenu supplémentaire ici-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,-18">
 <ScrollViewer Height="193" VerticalScrollBarVisibility="Auto" HorizontalAlignment="Left" Margin="2,463,0,0" 
              Name="scrollViewer1" VerticalAlignment="Top" Width="448">
    <TextBlock Name="TextLong" TextWrapping="Wrap" Width="417" Height="525" />
  </ScrollViewer>
  <TextBlock HorizontalAlignment="Center" Margin="15,354,24,244" Name="resultat2" Style="{StaticResource TextResultat}" 
              Text="" TextAlignment="Center" Width="417" />
  <Button Content="-" Height="81" Click="FaireMoins" HorizontalAlignment="Left" Margin="315,0,0,540" Name="ButtonMoins" 
              Style="{StaticResource MyButton}"
       VerticalAlignment="Bottom" Width="116" />
  <TextBlock Name="resultat" HorizontalAlignment="Center" Margin="22,180,17,392" Text="" Width="417" 
        Style="{StaticResource TextResultat}" TextAlignment="Center" />
  <Image Name="Image" HorizontalAlignment="Left" Margin="245,134,0,244" Stretch="Fill" Width="186" />
</Grid>
Ici l'image nommée 'Image' sera au premier plan.



V-D. Aspect des contrôles

Quand on dépose un bouton dans un formulaire, il a un aspect 'standard', on a souvent besoin de modifier sa couleur ou sa forme, son aspect visuel ; c'est ce que nous allons voir. Dans ce chapitre on travaille sur UN contrôle. Dans le chapitre sur les ressources, on verra comment créer des modèles de contrôles (avec les Styles et les Templates) pour les appliquer à un, plusieurs ou tous les contrôles.



V-D-1. Propriétés des contrôles

Ici on garde l'aspect général du contôle, on modifie simplement ses propriétés (Couleur, texte, font…).

On veut avoir ce beau bouton (bof !), ci-dessous l'exemple :


Avec le designer, on prend un bouton dans la boîte à outils, on le pose dans un conteneur (une grid, par exemple).

Pour définir les dimensions, la couleur et le texte dans le bouton, on va modifier les propriétés dans la fenêtre de propriétés en bas à droite :


  • Height et Width permettent de modifier les dimensions ;
  • Foreground et Background donnent la couleur du texte et du fond ;
  • BorderBrush et BorderThickness indique la couleur et l'épaisseur des bords ;
  • FontFamily indique le nom de la police de caractères ;
  • FontSize indique la hauteur de la police ;
  • FontStyle permet de choisir entre Normal, Italic, Oblique ;
  • FontWeight permet de choisir entre Normal, Bold, Heavy… ;
  • Name donne le nom du bouton en haut de la fenêtre ;
  • Content indique le texte à afficher.

En bas de l'écran dans la fenêtre XAML, apparaît le code XAML correspondant au bouton.

<Button 
Height=" 100" Width="200" 
Foreground="White" Background="Red" 
BorderBrush="Green" BorderThickness="100" 
FontFamily="Algerian" FontSize="24" FontStyle="Italic" FontWeight="Bold"
Name="mybutton">Beau Bouton</Button> 
Dans une fenêtre vide, on aurait pu coller le code XAML entre les balises Grid et voir apparaître le bouton.

On aurait pu créer le bouton avec du code C# :

            //Déclaration d'un bouton
            Button myButton = new Button();// 'création d'un bouton 
            // Modification des propriétés du bouton
            myButton.Name="Bouton1";
            myButton.Height = 133;        // modification des dimensions du bouton
            myButton.Width = 155;
            myButton.HorizontalAlignment = HorizontalAlignment.Stretch;
            myButton.Content = "Button";          
            myButton.Background = new SolidColorBrush(Colors.Red);
            myButton.Foreground = new SolidColorBrush(Colors.White);
            myButton.BorderBrush = new SolidColorBrush(Colors.Green);
            myButton.BorderThickness = new Thickness(100);
            myButton.FontFamily = new FontFamily("Algerian");
            myButton.FontSize = 24;
            myButton.FontStyle = FontStyles.Italic;
            myButton.FontWeight = FontWeights.Bold;
            //Gestion de l'évènement Click
            myButton.Click += new  RoutedEventHandler(button1_Click);
            //Ajout du bouton au conteneur
            ContentPanel.Children.Add(myButton);
			// 'met le bouton dans le conteneur ContentPanel
On a à notre disposition une classe Colors qui contient les couleurs :

On ne peut pas affecter directement une couleur à une propriété définissant une couleur : myButton.Background = Colors.Red; ne fonctionne pas.

Il faut instancier et utiliser une SolidColorBrush :

myButton.Background = new SolidColorBrush(Colors.Red);

Il en est de même avec FontFamily, FontStyle, FontWeight et Thickness (épaisseur). Il faut instancier une FontFamily, un FontStyle, un FontWeight ou un Thickness pour donner une valeur à la propriété correspondante.


L'énumération Brushes que l'on peut utiliser en WPF ne fonctionne pas ici :

myButton.Background = Brushes.Red; //ne fonctionne pas



Au lieu de mettre une couleur unie dans le fond d'un contrôle, on peut y mettre une 'LinearGradientBrush' ce qui produit un bel aspect de Background dégradé.

Cela donne :


En XAML :

 <Button Content="Inverse le Calcul" Height="69" HorizontalAlignment="Left" Margin="32,63,0,0" 
 Name="button1" VerticalAlignment="Top" Width="390" Click="button1_Click" >
                <Button.Background>
                <LinearGradientBrush >
                    <GradientStop Offset="0" Color="Blue" />
                    <GradientStop Offset="1" Color="SkyBlue" />
                </LinearGradientBrush>
                </Button.Background>              
            </Button>


Un contrôle peut être visible ou non : (Visible ou Collapsed qui correspond à une taille de zéro et n'occupe pas d'espace dans le layout.)

<Button Visibility="Collapsed" Content="Calcul" Height="69" Margin="32,63,0,0" 
En C# :

BtCalculer.Visibility = Visibility.Visible;
BtCalculer2.Visibility = Visibility.Collapsed;

On peut ajouter l'attribut Opacity dont la valeur va de 0 à 1.

<Image Source="C:/test.JPG" Opacity="0.5"/>
Avec la valeur 0.5 l'image est semi-transparente.


On peut aussi modifier les propriétés d'un contrôle ou d'un type de contrôle (tous les boutons de l'application) à l'aide des Ressources des Styles ; voir le chapitre 'Ressources'. Enfin on peut utiliser les data templates et contrôles templates (voir plus bas).



V-D-2. Contrôle contenant des contrôles

On a vu que la propriété Content d'un bouton pouvait contenir un objet mais un seul. On peut y mettre du texte, mais comment mettre un texte et une image dans un bouton ?

Il faut mettre un StackPanel (ou une Grid) dans le bouton (puisque celui-ci ne peut contenir qu'un seul objet), dans ce StackPanel (qui lui est un conteneur qui peut contenir x contrôles empilés) mettre un TextBlock et une Image. Dans le designer c'est difficile de mettre un StackPanel dans un Button, il se met dessus et pas dedans (c'est galère !), donc tapez ou collez le code XAML du StackPanel dans le bouton. De plus l'image doit être dans les ressources : passer par le menu 'Projet' puis 'Ajouter un élément existant', indiqué 'resources' dans les 'options de génération' en bas. Voir le chapitre sur les ressources.

 <Button  Height="161" HorizontalAlignment="Left" Margin="32,63,0,0" Name="button1" 
 VerticalAlignment="Top" Width="390" Click="button1_Click" >
        <StackPanel   Name="StackPanel">
        <TextBlock HorizontalAlignment="Center"  TextAlignment="right">
                         Ok
        </TextBlock>
    <Image   HorizontalAlignment="Stretch" Source="wpfsilverlight.jpg" Height="67" Width="188">
                </Image>
        </StackPanel>
 </Button>
Cela donne :


On se rend compte de la puissance de silverlight : on peut mettre autant de contrôles que l'on veut dans un contrôle en utilisant des conteneurs. Dans notre bouton on aurait pu mettre deux images, deux textes…



V-D-3. Aspect visuel des contrôles: template visuel, style

Ici on modifie l'aspect profond des contrôles.

Chaque contrôle possède un template visuel ou Control Template qui permet de définir l'aspect visuel du contrôle, le template indique comment il sera dessiné (forme, arrière plan, bords, coins, contenu…).

Ce template est entre les balises 'Button.Template' pour un bouton. On peut bien sûr modifier ce template, ce qui modifie l'aspect du bouton. On peut ainsi obtenir des boutons ronds, elliptiques…


Dans le template du bouton, ici, on va définir la forme qui est un rectangle mais aussi la forme des coins (radius).

On va donc faire un bouton Cyan avec des coins ronds (grâce à Rectangle RadiusX="39" RadiusY="39").

<Button Name="Button2" Margin="32,93,60,329" FontSize="40">
                <Button.Template>
                    <ControlTemplate>
                        <Grid>
                            <Rectangle RadiusX="39" RadiusY="39" Fill= "Cyan">
                            </Rectangle>
                            <ContentPresenter HorizontalAlignment="Center"
                             VerticalAlignment="Center" Content="BoutonTemplate"/>                          
                        </Grid>
                    </ControlTemplate>
                </Button.Template>
</Button>
Cela donne :


On voit qu'à partir du moment ou on utilise le ControlTemplate, il faut tout refaire, la forme du contrôle (Rectangle), mais aussi l'aspect du contenu ce qui explique l'usage du ContentPresenteur qui permet de gérer le contenu. On aurait pu ajouter l'aspect du bouton s'il est 'Enabled', 'Focused' ou 'cliqué' (voir le détail d'un template dans le chapitre sur les ressources).
Pour les listes on a un ItemsPresenter.

Si au lieu de mettre 'Rectangle Radius…' on met 'Ellipse', le bouton est en forme d'ellipse.

<Button Name="Button2" Margin="32,93,60,329" FontSize="40">
                <Button.Template>
                    <ControlTemplate>
                        <Grid>
                            <Ellipse Fill= "Cyan">
                            </Ellipse>
                            <ContentPresenter HorizontalAlignment="Center"
                             VerticalAlignment="Center" Content="BoutonTemplate"/>                          
                        </Grid>
                    </ControlTemplate>
                </Button.Template>
</Button>

Cette exemple fonctionne mais est incomplet car comme dans le template, on n'a pas défini l'aspect du bouton en cas de clic ou quand il a le focus, le bouton ne change donc jamais d'aspect même quand on clique dessus !


On pourrait créer un Style pour un bouton. En fait c'est beaucoup plus simple d'utiliser une propriété (les Styles sont plutôt utilisés dans les ressources).


Dans ce chapitre, on a modifié UN contrôle directement, il est possible de modifier un contrôle avec un style ou plusieurs contrôles de même type. Voir le chapitre sur les ressources qui parle des modèles de contrôles et donne un exemple complet. Enfin, pour être complet, il est possible de modifier l'aspect des données dans un contrôle grâce aux Data Template (modèle de données).




V-D-4. Transformation

Pour effectuer une transformation 2D sur un UIElement, on utilise la propriété RenderTransform.
On peut modifier l'échelle, la rotation, l'oblicité, le déplacement.

Échelle :
on peut effectuer une transformation horizontale ou verticale grâce à un ScaleTransform.
Pour cela on définit ScaleX et ScaleY.

Dans un bouton, on dessine une loupe (avec un Path), puis on va l'agrandir grâce à RenderTransform.
ScaleX="3" ScaleY="3" indiquent d'agrandir de trois fois.

<Button   Name="btnStart" Click="btnStart_Click" Width="260" Margin="98,284,98,239">
    <Button.Content>
        <StackPanel Orientation="Horizontal">
        <Path Fill="Blue" Data="F1 M 2.339,6.489 C 1.193,5.343 1.193,3.485 2.339,2.339 C 3.485,1.192 5.344,1.193 6.489,2.339 
                C 7.635,3.485 7.635,5.343 6.489,6.489 C 5.344,7.636 3.485,7.636 2.339,6.489 Z M 11.711,10.209 L 8.185,6.684 
                C 9.207,4.986 9.000,2.757 7.535,1.293 C 5.812,-0.431 3.017,-0.431 1.293,1.293 
                C -0.431,3.017 -0.431,5.812 1.293,7.536 C 2.757,8.999 4.988,9.206 6.685,8.185 
                L 10.209,11.710 L 11.711,10.209 Z" Margin="0,0,5,0" Height="36" Width="17">
                            <Path.RenderTransform>
                                <ScaleTransform ScaleX="3" ScaleY="3" />
                            </Path.RenderTransform>
        </Path>
        <TextBlock Foreground="White" Text="Chercher" Margin="20 0 0 0" />
        </StackPanel>
    </Button.Content>
</Button><br/>
					


Si ScaleX ou ScaleY sont négatifs, il y a inversion de l'élément.
Attention pour que l'élément reste au même endroit, il faut modifier l'origine de la transformation dans l'élément (origine est par défaut sur le coin supérieur gauche ce qui déplacerait l'élément en haut à gauche).
Exemple avec un bouton.

<Button Content="Click"  RenderTransformOrigin="0.5, 0.5">
                <Button.RenderTransform >
                    <ScaleTransform ScaleX="-1" ScaleY="1" />
                </Button.RenderTransform>
</Button>


Rotation :
On peut effectuer une rotation grâce à un RotateTransform.
Pour cela on définit Angle et si nécessaire CenterX et CenterY.
Exemple : texte vertical.

<TextBlock Text="Texte vertical" FontSize="32" Foreground="Teal" Margin="74,3,183,547">
  <TextBlock.RenderTransform>
    <RotateTransform Angle="90" />
  </TextBlock.RenderTransform>
</TextBlock>

Inclinaison :
On peut effectuer une inclinaison grâce à un SkewTransform.
Pour cela on définit AngleX AngleY et si nécessaire CenterX et CenterY.
Exemple : texte penché.

<TextBlock Text="Texte italique" FontSize="32" Foreground="Teal" Margin="74,3,183,547">
  <TextBlock.RenderTransform>
     <SkewTransform AngleX="45" />
  </TextBlock.RenderTransform>
</TextBlock>

Déplacement :
TranslateTransform permet de déplacer un élément ; on renseigne ici X et Y qui indiquent le déplacement en pixels.
Ici on va déplacer le texte quand on pose le doigt dessus ; il revient à sa place quand on lève le doigt.

<TextBlock  MouseLeftButtonDown="Bouge" MouseLeftButtonUp="Bouge2"
                        Text="Texte qui bouge" FontSize="32" Foreground="Teal" Margin="74,3,114,547">
  <TextBlock.RenderTransform>
      <TranslateTransform x:Name="myTransform" />
  </TextBlock.RenderTransform>
</TextBlock>

private void Bouge(object sender, MouseButtonEventArgs e)
        {
            myTransform.X = myTransform.X + 15;
            myTransform.Y = myTransform.Y + 15;
        }

private void Bouge2(object sender, MouseButtonEventArgs e)
        {
            myTransform.X = myTransform.X - 15;
            myTransform.Y = myTransform.Y - 15;
        }

On peut aussi créer des effets visuels.

<TextBlock Text="Translate Transform"
Foreground="{StaticResource PhoneForegroundBrush}"
FontSize="48"
HorizontalAlignment="Center"
VerticalAlignment="Center" />

<TextBlock Text="Translate Transform"
Foreground="{StaticResource PhoneBackgroundBrush}"
FontSize="48"
HorizontalAlignment="Center"
VerticalAlignment="Center" >
<TextBlock.RenderTransform>
<TranslateTransform X="2" Y="2" />
</TextBlock.RenderTransform>
</TextBlock>


V-D-5. Font

Les 'fonts' correspondent aux polices de caractères.

Quand on crée une page, les polices standards (ressources Windows Phone) sont utilisées pour la page :

<phone:PhoneApplicationPage 
    x:Class="MainPage"
    
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
PhoneFontFamilyNormal correspond à la police Métro 'Segoe WP'. PhoneFontSizeNormal correspond à 20 pixels.


Pour un contrôle comme un TextBlock, on peut modifier la police.

<TextBlock Text="15:03" FontFamily="Georgia"/>
Quand on tape FontFamilly=" la liste des polices disponibles s'affiche.


Pour ajouter une autre police : charger une police gratuite et redistribuable (fichier .ttf), la mettre dans un répertoire /fonts du projet. L'ajouter au projet et dans les propriétés de la police mettre 'Option de génération' à 'contenu' et 'Copier dans le répertoire de sortie' à 'Copier si plus récent'.
Il semble que si on la met avec 'Option de génération' à Resources, on la voit dans la liste des polices (à vérifier).
Pour l'utiliser :

<TextBlock Text="15:03" FontFamily="fonts/DigitalDream.ttf#Digital Dream" FontSize="60"/>
Avant # il y a le nom du fichier '.ttf'.
Après # il y a le nom de la police (ouvrir le fichier .ttf en cliquant dessus, il peut y avoir plusieurs polices dedans, voir le nom en haut).


Il faut trouver des polices redistribuables (licence GNU par exemple) et pas des polices pour usage personnel :
voir ici http://www.dafont.com/fr/top.php?l[]=10.


On peut spécifier la hauteur de la police avec FontSize (en pixels) :

<TextBlock Text="15:03"  FontSize="36"/>
En fait, la hauteur occupée est de 33 % supérieur à la hauteur indiquée par FontSize car il y a quelques pixels blancs au dessus et au dessous.
Comme Point=3/4 *Pixels, une FontSize de 36 correspond à 24 Points.



V-E. Couleur, remplissage de surface


V-E-1. Couleur

Dans le code XAML, les couleurs sont désignées directement :

<Button Background="Red"/> 
En fait il y a une conversion implicite du texte "Red" en System.Windows.Media.Brushes.Red.

On aurait pu écrire :

<Button>A Button
  <Button.Background>
    <SolidColorBrush Color="Red" />
  </Button.Background>
</Button>
ou en donnant le code couleur du rouge en hexadécimal :

<Button>A Button
  <Button.Background>
    <SolidColorBrush Color="#FFFF0000" />
  </Button.Background>
</Button>  
Les couleurs sont codées sur 32 bits : composante alpha (transparence) sur 8 bits et R (rouge), B (bleu), G (vert) chacun sur 8 bits.


Quand dans le code XAML on tape Color=" une liste des couleurs s'ouvre ; il suffit de choisir une couleur. Là il y a une multitude de couleurs.

Voici les couleurs disponibles et leur valeur hexadécimale :


Bien sûr on peut agir sur la composante alpha pour rendre une couleur plus ou moins transparente, il y a aussi une couleur transparente :

<StackPanel Background="Transparent" />


En C# 
On peut utiliser l'énumération Color de System.Windows.Media pour déclarer une variable qui contient une couleur.
La classe Colors permet d'avoir la valeur de 15 couleurs différentes :

Color myColor = Colors.Green;
Les couleurs sont codées sur 32 bits : composante alpha (transparence) et RBG.

On peut utiliser FromArgb pour indiquer les paramètres alpha, R, B, G.

myColor= Color.FromArgb(255, 0, 0, 255);
MessageBox.Show(myColor.ToString());//Affiche #FF0000FF

Il y a 15 couleurs dans la classe 'Colors', aussi pour utiliser dans le code C# les autres couleurs de la palette ci dessus, il faut utiliser la valeur hexadécimale et la découper.
Pour obtenir la couleur 'Silver' de code hexadécimal #FFC0C0C0.

col = Color.FromArgb(0xFF, 0xC0, 0xC0, 0xC0);


L'outil pinceau

Dans la fenêtre 'Propriétés', pour donner une couleur à la propriété (Background, par exemple) d'un contrôle on peut taper le nom de la couleur, on peut aussi ouvrir l'outil pinceau en cliquant sur la flèche à droite :

On peut choisir en haut une des icônes : null, couleur unie, dégradé, image.
Les curseurs permettent de doser la composante R, G, B et la composante alpha.
La valeur de la couleur est retournée en hexadécimal : #DF6C5F4D par exemple.


On peut aussi utiliser les couleurs Windows Phone standards (couleurs styles ou Brush) qui sont dans les ressources. Leurs noms commencent par 'Phone' :

< Button Foreground="{StaticResource PhoneForegroundBrush}"" />
Ces ressources utilisent le thème en cours (couleur de fond et couleur d'accentuation) que l'utilisateur à sélectionné dans les paramètres (fond clair ou foncé).
Voir chapitre sur les thèmes.

Sélecteur de ressource
Dans la fenêtre 'Propriétés', pour donner une valeur ressource à la propriété (Background par exemple) d'un contrôle on peut ouvrir la fenêtre du sélecteur de ressource en cliquant sur le carré à côté du nom de la propriété :

Dans le menu, choisir 'Appliquer la ressource'.

Double cliquer sur la ressource désirée.



Pour remplir une surface d'une forme (Rectangle, par exemple) avec une couleur on utilise Fill.

<Rectangle
    Width="200"
    Height="100"
    Fill="Blue"
    Stroke="Black"
    StrokeThickness="4" />

En C#

SolidColorBrush blueBrush = new SolidColorBrush();
blueBrush.Color = Colors.Blue;

blueRectangle.Fill = blueBrush;

LayoutRoot.Children.Add(blueRectangle);


Pour indiquer une couleur d'avant ou d'arrière plan pour un élément coloré on utilise Foreground, Background.

<Button Background="Red"/> 


On a vu qu'en C# il faut impérativement (même pour une couleur unie) utiliser une Brush (une brosse !).
Voyons les Brush en détails.


V-E-2. SolidColorBrush

C'est simple, c'est une couleur unie.

On peut 'remplir' peindre un objet, le fond d'un bouton (Background), un rectangle et même du texte avec un pinceau, une brosse (Brush).

En XAML c'est simple de remplir avec une Brush de couleur unie.

<Rectangle Width="50" Height="50" Fill="Blue" />
En C# c'est plus compliqué :

Button2.Background = new SolidColorBrush(Colors.Cyan);
On crée une instance de SolidColorBrush ( grâce à new) avec comme paramètre la couleur désirée.


Exemple 1 : on veut choisir la couleur du Background d'un bouton.

On peut le définir en mode design dans la fenêtre de propriétés.

En XAML :

<Button Background="Red"/> 
ou en hexadécimal :

<Button Background="#FF0000FF"/> 
ou en C# :

myButton.Background = new SolidColorBrush(Colors.Red);
On ne peut pas affecter directement la couleur : (myButton.Background = Colors.Red ne fonctionne pas) on est obligé d'instancier une nouvelle SolidColorBrush et de lui donner la valeur Red qui appartient à la collection Colors.


Exemple 2 : créer un rectangle rouge.

 Rectangle Rect = new Rectangle();
Rect.Width = 75;
Rect.Height = 75; 
//Creation d'une SolidColorBrush  
SolidColorBrush  myBrush =  new SolidColorBrush(Colors.Red); 
Rect.Fill = myBrush;
ContentPanel.Children.Add ( Rect);
Remarquer qu'on a utilisé la méthode Fill pour remplir le rectangle avec la Brush.

En XAML :

<Rectangle Width="75" Height="75">  
<Rectangle.Fill> 
    <SolidColorBrush Color="Red" /> 
</Rectangle.Fill> 
</Rectangle> 


V-E-3. LinearGradientBrush

Peinture avec dégradé linéaire.
C'est une couleur qui se transforme progressivement en une autre puis éventuellement en une autre encore.

Il y a un system de coordonnées sur la surface à remplir : (0,0) est au coin supérieur gauche, (1,1) au coin inférieur droit.

'0.5, 0.5 ' correspond au centre.


StartPoint indique les coordonnées du début du gradient, EndPoint les coordonnées de la fin du gradient, GradientStop indique la position relative de la couleur sur la ligne qui rejoint le point de début au point de la fin.


Exemple 1 : sur une Grid, on positionne trois couleurs dans la diagonale.

On ne peut pas le faire dans la fenêtre designer avec la souris, le plus simple est de coller le code XAML dans la fenêtre XAML ou de l'écrire en C#.


Pour suivre la diagonale StartPoint="0,0" EndPoint="1,1". le rouge sera à 0 % de la diagonale, le blanc à 50 % le bleu à 100 %.

En XAML :

<Grid >  
<Grid.Background> 
 <LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> 
 <GradientStop Color="Red" Offset="0" /> 
 <GradientStop Color="White" Offset="0.5" />  
 <GradientStop Color="Blue" Offset="1" /> 
 </LinearGradientBrush> 
 </Grid.Background> 
 </Grid> 
En C# :

  Rectangle Rect = new Rectangle();
            Rect.Width = 175;
            Rect.Height = 175;


            LinearGradientBrush myBrush = new LinearGradientBrush();
            myBrush.StartPoint = new Point(0, 0);
            myBrush.EndPoint = new Point(1, 1);
            GradientStop g1 = new GradientStop();
            g1.Color = Colors.Green;
            g1.Offset = 0.0;
            myBrush.GradientStops.Add(g1);
            GradientStop g2 = new GradientStop();
            g2.Color = Colors.White;
            g2.Offset = 0.5;
            myBrush.GradientStops.Add(g2);
            GradientStop g3 = new GradientStop();
            g3.Color = Colors.Blue;
            g3.Offset = 1.0;
            myBrush.GradientStops.Add(g3);
           
            Rect.Fill = myBrush;
            ContentPanel.Children.Add(Rect);
La syntaxe suivante pour le GradientStop, bien que correcte n'est pas acceptée :

 myBrush.GradientStops.Add(new GradientStop(Colors.White, 0.5d));


Exemple 2 : plein de couleurs dans un rectangle.

<Rectangle Width="400" Height="200">
                <Rectangle.Fill>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
                        <GradientStop Color="Yellow" Offset="0.0" />
                        <GradientStop Color="Red" Offset="0.25" />
                        <GradientStop Color="Blue" Offset="0.75" />
                        <GradientStop Color="LimeGreen" Offset="1.0" />
                    </LinearGradientBrush>
                </Rectangle.Fill>
</Rectangle>

Exemple 3 : sur un bouton avec un dégradé de gris.


Pour que le gradient s'applique de haut en bas StartPoint="0,0" EndPoint="0,1" le blanc sera à 0 % de la verticale, le gris à 100 % de la verticale.

En XAML :

<Button  Content="Ok">
<Button.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="White" Offset="0" />
<GradientStop Color="LightGray" Offset="1" />
</LinearGradientBrush>
</Button.Background>
</Button>
Remarque : on aurait pu mettre un GradientStop en dehors de la zone (à 2 par exemple pour le LightGray) ce qui permet d'estomper la couleur grise et de faire un bouton plus clair.


Exemple 4 : texte d'un bouton avec dégradé.

<Button Content="Linear" >
        <Button.Foreground>
                    <LinearGradientBrush>
                        <GradientStop Offset="0" Color="Red" />
                        <GradientStop Offset="1" Color="Yellow"/>
                    </LinearGradientBrush>
        </Button.Foreground>
</Button>
Ici on a utilisé un LinearGradientBrush pour le Foreground.



V-E-4. RadialGradientBrush

Peinture avec dégradé radial.
Ici on applique deux couleurs ou plus dans un cercle qui occupe la totalité du conteneur.

Visualisons ce cercle et le system de coordonnées :


Le GradientOrigin donne le centre du gradient. Ci-dessous le centre du gradient est à 0.50, 0.50 (c'est sa valeur par défaut) c'est à dire au centre du cercle. Les couleurs seront donc concentriques.


Les GradientStop indiquent la position des couleurs par rapport au centre : le blanc est à l'offset 0 : disque de blanc au centre ; le bleu est à l'offset 0.5, cela donne un cercle de bleu autour du blanc ; le vert est à l'offset 1 (100 %): le cercle vert est autour du cercle bleu.

En XAML

<RadialGradientBrush GradientOrigin="0.50,0.50">
<GradientStop Color="white" Offset="0" />
<GradientStop Color="LightBlue" Offset="0.5" />
<GradientStop Color="LightGreen" Offset="1" />
</RadialGradientBrush>

Pour l'exemple suivant le centre du gradient est à 0.50, 1 excentré en bas du cercle.

On a ajouté artificiellement, pour mieux comprendre, le cercle gris qui occupe la totalité du conteneur et le centre du gradient symbolisé par un point noir.


L'offset du blanc est 0.75 : le blanc monte haut.

En XAML

<RadialGradientBrush GradientOrigin="0.50,1">
<GradientStop Color="white" Offset="0.75" />
<GradientStop Color="LightBlue" Offset="1.0" />
</RadialGradientBrush>

Ici l'offset du blanc est 0.50 : le blanc monte moins haut.


En XAML :

<RadialGradientBrush GradientOrigin="0.50,1">
<GradientStop Color="white" Offset="0.50" />
<GradientStop Color="LightBlue" Offset="1.0" />
</RadialGradientBrush>

Exemple sur une Grid. Centre du gradient excentré en haut à droite (0.75, 0.25) et il y a trois couleurs.


En XAML :

<Grid > 
<Grid.Background >
<RadialGradientBrush GradientOrigin="0.75,0.25">
<GradientStop Color="Yellow" Offset="0.0" />
<GradientStop Color="Orange" Offset="0.5" />
<GradientStop Color="Red" Offset="1.0" />
</RadialGradientBrush>
</Grid.Background>
</Grid > 

On peut avoir une ellipse au lieu d'un cercle en définissant RadiusX et RadiusY.

 <RadialGradientBrush 
          Center="0.5,0.5" RadiusX="0.5" RadiusY="0.5"/>

Un exemple complet en C# :

            //Instancier un Radialgradientbrush
            RadialGradientBrush MyBrush = new RadialGradientBrush();

			//Mettre le GradientOrigin au centre.
			MyBrush.GradientOrigin = new Point(0.5, 0.5);

			// Mettre le centre du gradient au centre.
			MyBrush.Center = new Point(0.5, 0.5);

			// Mettre les radius pour que le gradient soit contre les bords.
			MyBrush.RadiusX = 0.5;
			MyBrush.RadiusY = 0.5;

			// Créer les gradients stops.
            GradientStop g1 = new GradientStop();
            g1.Color = Colors.Yellow;
            g1.Offset = 0.0;
			MyBrush.GradientStops.Add(g1);
            GradientStop g2  = new GradientStop();
            g2.Color = Colors.Blue;
            g2.Offset = 0.75;
			MyBrush.GradientStops.Add(g2);
             GradientStop g3 = new GradientStop();
            g3.Color = Colors.Green;
            g3.Offset = 1.0;
            MyBrush.GradientStops.Add(g3);
			
		
			//Créer un rectangle et le peindre avec la brush
			Rectangle aRectangle = new Rectangle();
			aRectangle.Width = 200;
			aRectangle.Height = 100;
			aRectangle.Fill = MyBrush;

            ContentPanel.Children.Add(aRectangle);

Autre exemple, une Sphère : on remplit une ellipse avec un RadialGradientBrush :

<Grid >
            <Ellipse Height="200" Width="200" Margin="12,0,9,0">
                    <Ellipse.Fill>
                        <RadialGradientBrush Center="0.4 0.4"
                                            GradientOrigin="0.4 0.4">
                            <GradientStop Offset="0" Color="White" />
                            <GradientStop Offset="1" Color="YellowGreen"  />
                        </RadialGradientBrush>
                    </Ellipse.Fill>
            </Ellipse>
</Grid>

Dernier exemple : utiliser une RadialGradientBrush comme Foreground d'un texte.

<TextBlock Text="GRADIENT BRUSH"
FontFamily="Arial"
FontSize="48" Margin="39,477,-39,-477">
<TextBlock.Foreground>
<RadialGradientBrush>
<RadialGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Offset="0" Color="Transparent" />
<GradientStop Offset="1" Color="White" />
</GradientStopCollection>
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</TextBlock.Foreground>
</TextBlock>


V-E-5. ImageBrush

On peut mettre une image dans un rectangle, par exemple.

En XAML :

<Rectangle Width="75" Height="75">  
<Rectangle.Fill>  
<ImageBrush ImageSource="c:\graphe.bmp" />  
</Rectangle.Fill>  
</Rectangle> 

Si je donne à la propriété Foreground le nom d'une ImageBrush, le texte va être dessiné avec cette image :

<TextBlock FontFamily="Verdana" FontSize="88"
 FontStyle="Italic" FontWeight="Bold">
    Philippe
  <TextBlock.Foreground>
    <ImageBrush ImageSource="C:\P1020907.jpg"/>
  </TextBlock.Foreground>
</TextBlock>

Il n'y a pas de VirtualBrush en WP7.



V-E-6. Masque d'opacité

Dans les UIElements, une image par exemple, on peut mettre un masque d'opacité (propriété OpacityMask), il permet de modifier la transparence d'une image en fonction d'un masque qui peut être un dégradé ou une image.
Seule la composante alpha du masque est prise en compte. L'image hérite de la transparence du masque.

Ici sur l'image de cet oiseau on applique un masque qui est une RadialGradientBrush de plus en plus transparente vers l'exterieur.

<Image Source="oiseau.jpg">
                <Image.OpacityMask>
                    <RadialGradientBrush>
                        <GradientStop Offset="0" Color="White" />
                        <GradientStop Offset="0.8" Color="White" />
                        <GradientStop Offset="1" Color="Transparent" />
                    </RadialGradientBrush>
                </Image.OpacityMask>
</Image>

Effet miroir :
Ici on affiche 2 fois la même image. On inverse la seconde par rapport à l'axe des Y grâce à RenderTransform et on lui applique un masque qui est un LinearGradientBrush.

<Image Source="oiseau.jpg" Stretch="None"
VerticalAlignment="Top" />
            
<Image Source="oiseau.jpg" Stretch="None"
VerticalAlignment="Top"
RenderTransformOrigin="0.5 1">
         <Image.RenderTransform>
                <ScaleTransform ScaleY="-1" />
         </Image.RenderTransform>
         <Image.OpacityMask>
              <LinearGradientBrush StartPoint="0, 0" EndPoint="0, 1">
                   <GradientStop Offset="0" Color="#00000000" />
                   <GradientStop Offset="0.5" Color="#40000000" />
              </LinearGradientBrush>
         </Image.OpacityMask>
</Image>


V-E-7. Nombre de couleurs et dégradés

Les téléphones Windows Phone 7 ont des écrans 32 bpp (bits par pixel) ou des écrans 16 bpp (bits par pixel), l'émulateur est en 16 bpp pour aider les développeurs à concevoir des applications qui fonctionnent quel que soit le matériel.
Aussi dans l'émulateur les dégradés ne sont pas beaux et présentent des bandes colorées
(pour être sûr d'avoir un beau dégradé, certains proposent d'afficher l'image d'un dégradé qu'on aura flouté dans PaintNet ou PhotoShop).


V-F. Ressources

Les ressources sont un ensemble d'éléments :
- Couleurs, Brush, Style, ControlTemplate (aspect du contrôle), DataTemplate (affichage des données) ressources dites 'internes' ;
- Images, icônes, textes, sons (ressources contenues dans des fichiers externes).

Nous allons donc voir

1- Les ressources internes.

2- Les fichiers de ressources externes.

3- Les ressources fournies par WP.


V-F-1. Les dictionnaires de ressources

On a vu antérieurement comment modifier certaines propriétés d'UN contrôle. Mais il est possible de créer des modèles pour ensuite les appliquer à un ou plusieurs contrôles.
Ici on va voir comment créer des styles, des modèles de contrôles, des modèles de données pour les appliquer à un, plusieurs ou tous les contrôles ou données .


V-F-1-a. Ressources 'simples'
Plutôt que d'indiquer X fois quelle couleur ou Brush utiliser dans plusieurs contrôles d'une fenêtre, il est plus simple de définir une ressource contenant la couleur ou la Brush puis X fois, indiquer pour chaque contrôle quelle ressource utiliser.

Les ressources sont dans un dictionnaire de ressources. Dans les ressources de l'application, d'une page ou d'un objet, entre les balises de début et de fin de ressources.

<Grid.Resources>

</Grid.Resources>
Remarquez : Resources avec un 's' et non Ressources.

Ici dans ces ressources simples, la ressource est une Brush, une couleur un texte…

Chaque ressource doit avoir une clé unique. Il faut affecter la clé unique via l'attribut x:Key.

En principe, la clé est une chaîne de caractères.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.Resources>
  <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
</Grid.Resources>
Ici la clé unique de la SolidColorBrush est "MyBrush", c'est son 'nom', son nom d'index.
Elle est dans les ressources de la grille.

Ensuite dans le code XAML de l'UI on utilise cette ressource grâce à {StaticResource MyBrush}.
Seul le contrôle utilisant la ressource la prendra en compte !
Ici dans la grille où se trouve la ressource, on a un bouton et une ellipse. On peut donc utiliser la ressource dans le bouton et l'ellipse.

<Button Background="{StaticResource MyBrush}"/>


<Ellipse Fill="{StaticResource MyBrush}"/>
En C# :

//ContentPanel.Resources contient les ressources de la grille.
//Comme la ressource "MyBrush" est un objet, on la caste en Brush.
button1.Background = (Brush)ContentPanel.Resources["MyBrush"];

// Si la ressource avait été dans App.xaml
button1.Background = (Brush) Application.Current.Resources["MyBrush"];
 

Exemple complet

Dans une Grid, on crée une ressource de type LinearGradientBrush nommée BrushBizarre, ensuite on applique cette Brush au fond d'un bouton.

<Grid> 
  <Grid.Resources> 
    <LinearGradientBrush x:Key="BrushBizarre">
      <GradientStop Color="Yellow" Offset="0" /> 
      <GradientStop Color="Green" Offset="1" /> 
    </LinearGradientBrush> 
  </Grid.Resources> 


  <Button Background="{StaticResource BrushBizarre}">Click Me</Button> 
</Grid> 

Où mettre les ressources ?

-Dans un objet. Dans une Grid comme dans l'exemple précédent, syntaxe : Grid.Resources ; dans ce cas la Grid mais aussi tous les objets contenus dans la Grid peuvent utiliser la ressource.

<Grid.Resources>
  <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
</Grid.Resources>

En C# comment utiliser la ressource ?

//ContentPanel.Resources contient les ressources de la grille nommée ConcentPanel.
//Comme la ressource "MyBrush" est un objet, on la caste en Brush.
button1.Background = (Brush)ContentPanel.Resources["MyBrush"];
-Dans une Page, ici on crée une ressource string dans la page.

xmlns:system="clr-namespace:System;assembly=mscorlib"
  
    <phone:PhoneApplicationPage.Resources>
        <system:String x:Key="LeTitre">Cours Wp</system:String>
    </phone:PhoneApplicationPage.Resources>
On remarque que pour utiliser une string comme ressource, il faut au préalable importer l'espace de noms system.

Comment l'utiliser dans la page ?

<TextBlock x:Name="ApplicationTitle" Text="{StaticResource LeTitre}"/>

En C# :

TextBlock1.Text = (string) this.Resources["LeTitre"];

-Dans le fichier Application.

C'est le fichier App.xaml.

  <Application.Resources>
		 <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
  </Application.Resources>
Dans ce cas la ressource est utilisable dans toute l'application.

On peut l'utiliser dans un TextBlock :

 <TextBlock x:Name="PageTitle"  Foreground="{ StaticResource MyBrush}" 
 Text="nom de la page" />        
Comment récupérer la brush en C# ?

SolidColorBrush sb = Application.Current.Resources["MyBrush"] as SolidColorBrush;

- Dans un fichier de ressources indépendant.

Menu 'Projet', puis 'Ajouter un nouvel élément' puis 'Fichier texte'. Nommer le 'Dictionnary1' puis Ok.

Y mettre une SolidColorBrush.

  <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
  </ResourceDictionary>
Pour que cela fonctionne, il faut ajouter dans le fichier App.xaml la référence au dictionnaire.

  <Application x:Class="Application"

 

  <Application.Resources>
	<ResourceDictionary.MergeDictionaries>
   		<ResourceDictionary Source="Dictionary1.xaml"/>
	</ResourceDictionary.MergeDictionaries>
  </Application.Resources>

  </Application>


L'emplacement où est déclaré la ressource affecte l'emplacement où la ressource peut être utilisée.

idea Si vous déclarez la ressource dans votre fichier App.xaml avec la balise 'Application.Resources', elle pourra être utilisée n'importe où dans votre application.
Si vous déclarez la ressource dans une Grid grâce à 'Grid.Resources', elle pourra être utilisée uniquement dans la Grid et dans les objets enfants qui sont dans cette grille.


Il n'existe pas de ressources 'Dynamiques' en WP.



V-F-1-b. Les styles
Le style est une ressource qui est applicable à un objet ou un 'Type' d'objet. On peut créer un style pour les Buttons, les List…
Il correspond à l'identification de plusieurs propriétés par un nom, que l'on peut appliquer ensuite facilement à plusieurs contrôles.
Il peut être placé dans les ressources d'un objet, d'une page ou d'une application.

Ici on va créer un style pour les TextBlock.

TargetType="TextBlock" indique quel type cela concerne.

x:Key= , indique le nom du style.

Setter va servir à définir une Property à laquelle on va donner une Value. Dans ce style, grâce à 'Setter Property=' on affecte des valeurs aux propriétés des TextBlock.

<Application.Resources>
<Style TargetType="TextBlock" x:Name ="MyTB">
<Setter Property="FontSize" Value="18"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontFamily" Value="Trebuchet MS"/>
</Style>
</Application.Resources>
Ensuite, dans un TextBlock on peut utiliser le style.

Il faut utiliser l'attribut Style= dans le TextBlock sur lequel on veut appliquer le style.

 <TextBlock Style="{StaticResource MyTB}" >Titre</TextBlock>

Depuis WP 7.1 (Mango), si on omet x:Name dans un style, cela permet d'appliquer le style automatiquement à tous les objets correspondant au TargetType.

Si on enlève x:Name :

<Application.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="18"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontFamily" Value="Trebuchet MS"/>
</Style>
</Application.Resources>

Le style sera appliqué dans le textblock qui suit et tous les textblocks.

 <TextBlock >Titre</TextBlock>

On vient d'utiliser un Setter pour modifier une propriété, mais il est possible dans un style de mettre un DataTemplate (modèle de donnée), un ControlTemplate (modèle de contrôle).
Voyons un exemple avec un ControlTemplate (lire le chapitre suivant avant).
Ici on crée un style contenant un ControlTemplate donnant des boutons ronds et rouges.

 <!--Ressources d'applications-->
    <Application.Resources>
        <Style TargetType="Button" x:Name="MyStyle">


            <Setter Property="FontSize" Value="18"/>
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="FontFamily" Value="Trebuchet MS"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <Ellipse Width="100" Height="100" Fill="Red"></Ellipse>
    <ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center">
                            </ContentPresenter>
                        </Grid>
                        
                    </ControlTemplate>
                </Setter.Value>
            </Setter>

        </Style>
      
    </Application.Resources>
Pour simplifier, le template n'est pas complet ; en particulier, le bouton ne change pas d'aspect quand on clique.

Et pour l'utiliser sur un bouton :

 <Button Style="{StaticResource MyStyle}" Content="Ok" Height="153" 
 HorizontalAlignment="Left" Margin="56,48,0,0" Name="button1" VerticalAlignment="Top" Width="337" />


En C# pour appliquer le style à un contrôle :

Button1.Style= (Style)(this.Resources["MyStyle"]);

On peut dans un style récupérer un style existant et l'étendre. On utilise BaseOn pour récupérer un style.

<Style x:Key="MybaseStyle">
  <Setter Property="FontSize" Value="24" />
  <Setter Property="Background" Value="Orange" />
</Style>
 
 
<Style x:Key="boldStyle" BasedOn="{StaticResource MybaseStyle}">
  <Setter Property="FontWeight" Value="Bold" />
</Style>



V-F-1-c. Les modèles de contrôle : ControlTemplate
On a vu plus haut qu'on pouvait mettre un template (modèle, gabarit) dans un contrôle :

<Button>
  <Button.Template>
  <ControlTemplate>
  
  <ControlTemplate>
  </Button.Template>
</Button>
Mais on peut aussi créer des Modèles de contrôle (ControlTemplate) dans les ressources permettant de modifier tous les contrôles d'un même type.

On spécifie la structure visuelle et les aspects comportementaux d'un type de contrôle. Là, on ne modifie pas une simple propriété du contrôle, mais son aspect profond.

Si on écrit un ControlTemplate, on va définir son aspect visuel (fond, forme…), il faut aussi obligatoirement redéfinir l'aspect de son contenu (ControlPresenter) et si nécessaire les modifications liées aux évènements (VisualStateManager).


Comment avoir des boutons en forme d'ellipse ?

Dans les ressources de l'application (dans App.xaml), on va créer un style pour les boutons, on va sur le template et son ControlTemplate.
- Pour la forme du bouton, on va mettre dans une grille une ellipse (la remplir avec une Brush).
- Pour le texte on ajoute un ContentPresenter.
- Pour modifier la couleur quand le bouton est 'Pressed', on utilise un VisualStateManager contenant un Storyboard qui modifie la Brush de l'ellipse. Le bouton devient blanc quand on clique dessus.

 <!--Ressources d'applications-->
    <Application.Resources>
        <Style TargetType="Button" x:Key="MyButton">
         <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                       
                            <Grid>
                            <Ellipse>
                                <Ellipse.Fill>
                                    <SolidColorBrush x:Name="EllipseBrush" Color="Blue"/>
                                </Ellipse.Fill>
                            </Ellipse>

                            <ContentPresenter 
                             Margin="2"
                             HorizontalAlignment="Center"
                             VerticalAlignment="Center"/>

                                <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                     <VisualStateGroup.Transitions>
                                        <VisualTransition From="Normal" To="Pressed" GeneratedDuration="0:0:0.5"/>
                                    </VisualStateGroup.Transitions> 

                                    <VisualState x:Name="Normal" />

                                    <VisualState x:Name="Pressed">
                                        <Storyboard>
                                            <ColorAnimation Storyboard.TargetName="EllipseBrush" 
                                             Storyboard.TargetProperty="Color" To="White" />
                                        </Storyboard>
                                    </VisualState>
                                    
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>

                               
                           
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Application.Resources>
Pour un contrôle liste on aurait utilisé un ItemsPresenter.


Pour appliquer le style a un bouton :

<Button Style="{StaticResource MyButton}" Content="Button" Height="66" Name="button1"  Width="289" />




V-F-1-d. Les modèles de données : Data Template
Enfin pour être complet il est possible de modifier l'aspect des données affichées dans un contrôle grâce au Data Template (modèle de données) ; voir le chapitre sur les liaisons de données pour le détail.


Utilisation de Template dans une ListBox.

Grâce à l'ItemTemplate on peut afficher les items d'une ListBox comme on veut.
Ici on veut afficher dans une ListBox une collection.
Pour chaque Item on affiche une petite sphère, la propriété 'Nom' en gros et la propriété 'TextBref' dessous en plus petit.

On suppose que la collection a déjà été créée (voir exemple en fin de chapitre sur les collections) et qu'on effectue une liaison (Binding) de la collection sur la ListBox, ce qui remplit automatiquement la ListBox avec la collection.

Dans l'ItemTemplate on met un StackPanel dans lequel on met une ellipse puis deux TextBlock qui sont en liaison avec les propriétés 'Titre' et 'TextBref'.

<ListBox Name="ListBoxConversion"  Margin="10,10,23,-9" ItemsSource="{Binding}" 
Height="524" Width="400"  MouseLeftButtonUp="ListBoxConversion_Appel"  >
 <ListBox.ItemTemplate>
  <DataTemplate>
    <StackPanel Orientation="Horizontal" Margin="0,0,0,17"  >
    <!--La petite sphère-->
        <Ellipse Height="30" Width="30"  Margin="12,0,9,0">
        <Ellipse.Fill>
        <RadialGradientBrush Center="0.4 0.4"
        GradientOrigin="0.4 0.4">
        <GradientStop Offset="0" Color="White" />
        <GradientStop Offset="1" Color="Blue"  />
        </RadialGradientBrush>
        </Ellipse.Fill>
    	</Ellipse>
        <StackPanel Width="311">
    <!--Les 2 lignes Nom et TextBref-->
<TextBlock Foreground="Azure" Text="{Binding Titre}" TextWrapping="Wrap" 
Style="{StaticResource PhoneTextLargeStyle}"   />
<TextBlock Text="{Binding TextBref}" TextWrapping="Wrap" Margin="12,-6,12,0" 
Style="{StaticResource PhoneTextSubtleStyle}" />
        </StackPanel>
    </StackPanel>
  </DataTemplate>
 </ListBox.ItemTemplate>
</ListBox>


Comment afficher une liste d'images ?
Mesphotos étant une liste de noms d'images, la propriété 'NomImage' contient le nom de la photo ; on montre ici le principe simplement.

<ListBox ItemsSource="{Binding Source={StaticResource MesPhotos}}"/>
Ici on affiche la liste des noms d'images.
Comment afficher les images elle mêmes ? en créant un DataTemplate.
Il affiche pour chaque élément une ligne contenant le nom de l'image puis l'image elle-même.

<ListBox ItemsSource="{Binding Source={StaticResource MesPhotos}}">
<DataTemplate>
 <StackPanel Orientation ="Horizontale">
   <TextBlock Text= "{Binding NomImage}">
   <Image Source="{Binding NomImage}"/>
  </StackPanel>
</DataTemplate>
</ListBox>
Ici le Data Template est dans le contrôle, on aurait pu créer un DataTemplate dans les ressources.



V-F-2. Les fichiers de ressources

On peut ajouter dans le projet des fichiers image, son, icône…
Exemple de fichier Image.

J'ai une image nommée 'xvide.jpg', je veux la mettre dans les ressources pour la charger dans un contrôle image.

Mettre le fichier image dans le répertoire des sources (dans C:\Documents and Settings\phil\Mes documents\Visual Studio 2010\Projects\MyApplication\MyApplication).

Mettons à jour les fichiers de l'explorateur de solution en cliquant sur le bouton en haut (voir ci-dessous).
Le fichier apparaît dans l'explorateur de solution mais en grisé.

Clic droit sur le fichier puis sur 'Inclure dans le projet'.
Le fichier devient une 'resource' (voir en bas dans les propriétés, 'Action de génération=Resource').


On peut aussi passer par : Menu Projet=>Ajouter un élément existant.

Si on a créé un répertoire nommé image, pointer C:\Documents and Settings\phil\Mes documents\Visual Studio 2010\Projects\MyApplication\MyApplication\image\xvide.jpg, puis cliquer sur 'Ajouter'.

Le fichier apparaît dans l'explorateur de solution (en haut à droite).

Là aussi dans la fenêtre des propriétés du fichier 'Action de génération' prend la valeur 'Resource' par défaut.


Mettre la ressource dans l'image.

<Image Margin="34,36,48,46" Name="Image" Stretch="Fill" Source="/NomDuProjet;component/xvide.jpg" /> 
Notez bien : pas de chemin.

L'image xvide.jpg apparaît dans le contrôle image.

Dans le code C# :

Image1.Source = New BitmapImage(New Uri("/NomDuProjet;component/xvide.jpg", UriKind.Relative)); 
Lors de la génération du projet, l'image sera dans l'exécutable.


On peut passer par les ressources de l'application.

-Charger xvide.jpg dans le projet comme ci-dessus.

-Créer une ressource nommée "someImage" dans Application.xaml

<Application.Resources>
<ImageSource x:Key= "someImage">xvide.jpg</ImageSource>
</Application.Resources>
-Mettre la ressource dans une image

<Image Margin="34,36,3,8" Name="Image1" Stretch="Fill" Source="{StaticResource someImage}"/> 

Bien sûr, on peut utiliser des icônes.
On peut aussi donner l'adresse Internet d'une image.


Revenons sur les valeurs d' 'Option de génération' dans les propriétés d'un fichier :

Si on déroule la liste, on a les valeurs :
- Aucun : Le fichier n'est pas inclus dans le groupe de sorties de projet et n'est pas compilé au cours du processus de génération. Fichier texte contenant de la documentation par exemple ;
- Compiler : Le fichier est compilé dans la sortie de projet. Cette valeur est utilisée par défaut pour les fichiers de code (.cs) ;
- Page : Le fichier est compilé dans la sortie de projet. Cette valeur est utilisée par défaut pour les fichiers .xaml ;
- ApplicationDefinition : Cette valeur est utilisée par défaut pour App.xaml ;
Contenu : Le fichier n'est pas compilé, mais il est inclus dans le groupe de sorties Contenu. Valeur par défaut d'un fichier .htm ou d'autres types de fichier Web, par exemple ;
Resources : Cette action de génération va incorporer le fichier dans l'assembly du projet (correspond aux ressources incorporées).
Ressource incorporée : semble ne pas pouvoir être utilisé en silverlight.



Notez que dans l'explorateur de solutions , si on ajoute une image (menu 'Projet', 'Ajouter un elément existant' ), l'option 'Action de génération' prend la valeur 'Resource'.

On peut, quand on met une image dans le projet, en faire un 'Contenu' ou une 'Resource'.
Avec 'Contenu', le fichier image est dans le fichier d'installation XAP mais pas dans la DLL. Cela serait plus performant.
Noter la syntaxe de la valeur de 'Source':

<Image Name="Image" Source="xvide.jpg" /> 	
Avec 'Resource', le fichier image est incorporé dans le fichier DLL qui est dans le XAP.
Noter la syntaxe de la valeur de 'Source':

<Image Name="Image" Source="/NomDuProjet;component/xvide.jpg" /> 	


V-F-3. Ressources Windows Phone

WP fournit un dictionnaire de ressources dont on peut utiliser les valeurs dans une application. Ce sont des valeurs 'standards' proposées par Microsoft.
Elles sont en rapport avec le thème en cours (couleur de fond : claire ou foncée, couleur d'accentuation).
Le nom de ces ressources commence par 'Phone'.

Il y a des PhoneBrush dans ces ressources.
Par exemple pour un fond de page on va utiliser le fond de page standard : PhoneForegroundBrush.

Foreground="{StaticResource PhoneForegroundBrush}"

Il y a aussi des PhoneColor , PhoneFonts, les tailles pour les caractères (PhoneFontSizeMedium = 17),
mais aussi les épaisseurs pour les propriétés Thickness, Padding…
enfin les styles de texte et la visibilité ou l'opacité du thème.


Voici un exemple de l'utilisation d'un style dans un TextBox :

<TextBlock x:Name="PageTitle" Text="MonTitre"  Style="{StaticResource PhoneTextTitle1Style}" />

Voici où trouver toutes les ressources et leur valeur :



D'ailleurs, automatiquement, quand on crée une page, la police 'normale' est utilisée pour la page, ainsi que la couleur du fond :

<phone:PhoneApplicationPage 
    x:Class="MainPage"
    
    FontFamily="{StaticResource PhoneFontFamilyNormal}"  // c'est la police Métro: 'Segoe WP'
    FontSize="{StaticResource PhoneFontSizeNormal}"      // 15 pts, 20 pixels
    Foreground="{StaticResource PhoneForegroundBrush}"

Pour afficher le nom de l'application et le titre on utilise PhoneTextNormalStyle et PhoneTextTitle1Style :

<!--TitlePanel contient le nom de l'application et le titre de la page-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="MON APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="nom de la page" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

C'est la ressource 'PhoneFontFamilyNormal' (correspondant à la police Métro 'Segoe WP') qui est donc utilisée pour le texte de la page.


Comment utiliser les couleurs du thème actuel ?

Dans les paramètres du Windows Phone on peut choisir :
L'arrière plan (clair ou sombre) ;
La couleur d'accentuation (11 couleurs proposées).


Ces couleurs se retrouvent dans les ressources Windows Phones : la couleur de fond est PhoneBackgrountColor ;
la couleur d'accentuation est PhoneAccentColor.
Il y a les Brush correspondantes :

Dans le code XAML, quand on utilise des ressources comme couleur, ce sont les 'Brush' qu'il faut utiliser et pas les couleurs.

PhoneAccentBrush : Brush d'accentuation définie par le thème.

PhoneForegroundBrush : Brush utilisée pour les éléments de premier plan tels que le texte ou les icônes.

PhoneBackgroundBrush : Brush utilisée pour les éléments d'arrière-plan.

PhoneContrastBackgroundBrush : Brush de fond utilisée pour mettre en contraste (fond des menus contextuels par exemple).

PhoneContrastForegroundBrush : Brush de premier plan utilisée pour les éléments à mettre en contraste. Par exemple les textes des menus contextuels.

PhoneDisabledBrush : Brush des éléments désactivés.

PhoneSubtleBrush : Brush partiellement transparente qui permet de mettre en retrait des éléments visuels moins importants.

PhoneBorderBrush : Brush de bordure des contrôles TextBox, CheckBox, PasswordBox, et RadioButton.

PhoneSemitransparentBrush : Brush presque transparente permettant de faire ressortir des éléments de couleurs similaires (par exemple du texte blanc sur une image claire).


On peut aussi utiliser les styles :
PhoneTextNormalStyle qui contraste avec la couleur du fond pour le rendre lisible, PhoneTextTitle1Style.

Voir le chapitre sur les thèmes.


Sélecteur de ressource : dans la fenêtre 'Propriétés', pour donner la valeur d'une ressource à la propriété (BackGround par exemple) d'un contrôle on peut ouvrir le sélecteur de ressource en cliquant sur le carré à coté du nom de la propriété.

Dans le menu, choisir 'Appliquer la ressource'.

Double cliquer sur la ressource désirée.


Typographie :

il est conseillé d'utiliser la typographie recommandée.



V-G. Les liaisons de données ou Binding


V-G-1. Principes du Binding

Binding veut dire liaison.

"Les liaisons de données sont des processus qui établissent une connexion entre l'interface utilisateur de l'application et la logique métier." En d'autres termes, elles permettent d'établir une connexion entre un contrôle et une sources de données. Cela permet, par exemple, d'afficher automatiquement le contenu d'une base de données, d'une collection… dans une ListBox…

Il faut donc un objet visuel, la cible (ListBox, TextBox…) ayant une propriété de dépendance et faire une liaison avec la source de liaison qui est la propriété d'un objet (collection, tableau, base de données…).
La liaison peut être unidirectionnelle (OnWay= en lecture seule de la source) ou bidirectionnelle (TwoWay), ce qui permet dans ce dernier cas de mettre à jour la source quand on fait une modification dans l'UI.


Binding entre objets :

Pour qu'une propriété d'un objet (dit 'cible') soit liée à une source, il faut lui affecter un objet Binding (Text="{Binding…) puis indiquer l'objet source avec ElementName et la propriété source avec Path :

<TextBox Text="{Binding Path=Text, ElementName=txtsource}"  />
Ici la propriété Text du TextBox est liée à la propriété Text d'un autre contrôle. Si on tape un texte dans le contrôle txtsource, il apparaitra dans la propriété Text de notre TextBox.


DataContext

La propriété DataSource des contrôles WindowsForms n'existe plus ; les contrôles silverlight ont un DataContext. Il peut être renseigné en XAML ou dans le code #. Le DataContext indique donc la source mais ce qui est fondamental c'est que les contrôles enfants vont hériter du DataContext :
Si un DockPanel a un DataContext, les Buttons qui sont dedans hériterons de ce DataContext, il suffira pour chaque contrôle enfant d'indiquer une partie de la source (une propriété, un champ…).
Autrement dit, si vous avez une source possédant plusieurs propriétés, la source est spécifiée en utilisant DataContext du contrôle (pratique pour un groupe d'éléments donc). Chaque propriété à afficher sera indiquée dans les éléments enfants.

Si on a dans le code C# de la page une collection d'objets ayant les propriétés Nom, Prenom, Rue, Ville, on renseigne le DataContext dans le code C# puis on peut utiliser le binding comme cela :

<StackPanel DataContext="{Binding}">
    <TextBox Text="{Binding Nom}"/>
    <TextBox Text="{Binding Prenom}"/>
    <TextBox Text="{Binding Rue}"/>
    <TextBox Text="{Binding Ville}"/>
</StackPanel>
Ici on crée une liaison du StackPanel avec une collection. Les TextBox héritent du DataContext du StackPanel. Cela permet ensuite de lier la propriété Text de chaque TextBox à différentes propriétés des objets de la collection.


Vous pouvez indiquer le DataContext dans le code C# :

DockPanel1.DataContext=  MyCollection;
Dans ce cas en XAML, il faut simplement indiquer qu'il y a un Binding avec l'expression "{Binding}" :

<ListBox Margin="14,16,8,2" Name="ListBox1"  ItemsSource="{Binding }" />

Il est possible d'indiquer comme DataContext la classe elle-même.

public MainWindow()
			InitializeComponent();
			DataContext = this;
		}

warning On insiste sur le fait que si vous souhaitez créer une liaison avec un objet source (comme une collection, par exemple) qui a déjà été instancié dans le code C#, vous devez définir la propriété DataContext dans le code C# (et pas en XAML).

Si on n'utilise pas le DataContext, on peut aussi utiliser la propriété Source du Binding :

<Button  Background="{Binding Source={StaticResource myDataSource}/>

Si on utilise un contrôle de type ItemsControl tel qu'un ListBox, ListView ou un TreeView pour afficher une collection de données, il faut utiliser ItemsSource pour indiquer la source :

 <ListBox Name="ListBox1"  ItemsSource="{Binding Source={StaticResource ListData}}" />

Mode indique le mode de mise a jour (OneWay, TwoWay, OneTime).

<TextBox Name="txtcible" Margin="21,0,25,21"
             Text="{Binding Path=Text, ElementName=txtsource,  Mode=TwoWay}"  />

UpdateSourceTrigger détermine le moment des mises à jour de la liaison.
La valeur est par défaut 'Default' et le binding se fera dans ce cas lors de la sortie du contrôle source.
L'autre valeur est 'Explicit'.



V-G-2. Liaison entre contrôles

Pour qu'une propriété d'un objet (dit 'cible') soit liée à une source, il faut lui affecter un objet Binding ( Text="{Binding…) puis indiquer l'objet source avec ElementName et la propriété source avec Path.


A - Créons deux TextBox ; on va créer une liaison entre les deux propriétés Text, quand on tape un texte dans la première, il apparaît dans la seconde :

<TextBox Name="txtsource"  Text="" />
<TextBox Name="txtcible" Text="{Binding Path=Text, ElementName=txtsource}"  />
Dans la cible, la propriété Text= 'Binding' entre accolades permet d'indiquer qu'il y a une liaison, 'ElementName' indique la source de la liaison (la source est désignée par son nom dans le XAML). Path la propriété de la source. Il y a une ',' entre chaque élément à partir du second.

La syntaxe suivante est aussi acceptée.

<TextBox Name="txtcible" 
             Text="{Binding Text, ElementName=txtsource}"  />

On remarque que la mise à jour est effectuée (dans le cas d'un TextBox) quand on quitte le TextBox source. Il y a bien une propriété UpdateSourceTrigger qui détermine le moment de la mise à jour, elle peut avoir une des deux valeurs 'Default' et 'Explicit' (pas de valeur PropertyChanged).

Par défaut le Mode est égal à OneWay (la liaison se fait uniquement dans le sens source=>cible).

On peut donner la valeur TwoWay au mode, ce qui permet de faire en plus la liaison cible=>source : quand on tape dans la cible, la source est modifiée (cela est visible dans notre exemple ci-dessous quand on quitte la cible) :

<TextBox Name="txtsource"  Text="" />
<TextBox Name="txtcible" 
             Text="{Binding Path=Text, ElementName=txtsource,  Mode=TwoWay}"  />
Pour être complet il existe aussi les modes OneTime (mise à jour une seule fois au départ).


B - Créons deux ListBox ; quand on ajoute des éléments dans la première, ils apparaissent dans la seconde :

<Grid>
<ListBox  Name="listBox1" SelectionMode="Multiple" />
<ListBox  Name="listBox2" ItemsSource="{Binding ElementName=listBox1, Path=Items, Mode=OneWay}" />
</Grid>

C - Créons maintenant un Slider (curseur) et un rectangle ; quand on bouge le curseur du Slider cela modifie la hauteur du rectangle, il faut donc comme source la propriété Value du Slider et comme cible la propriété Height du rectangle :

<StackPanel>
    <Slider Name="Slider1" Value="5" Maximum="200" Minimum ="0" Margin="0,294,0,172" />
    <Rectangle Fill="Blue" Stroke="Red"  StrokeThickness="3" Width="97" Height="{Binding ElementName=Slider1, Path=Value}"/>
</StackPanel>


D - Créons une ListBox, on ajoute des éléments à cette ListBox avec du code C# ; quand on clique sur un item il s'affiche dans le TextBlock en bas :

<ListBox Height="203" HorizontalAlignment="Left" Margin="27,24,0,0" Name="listBox1" VerticalAlignment="Top" Width="373">
</ListBox>
<TextBlock Text="{Binding ElementName=listBox1, Path=SelectedItem}" Margin="6,296,-6,266" />
           
À noter que si on met des items dans la ListBox en utilisant le code XAML ci dessous, cela ne marche pas ; le binding affiche le type (il faut ajouter des éléments à la ListBox avec du code C#).

<ListItem Content="Paris" /> 


E -Créons un TextBox et un TextBlock avec liaison ; comment mettre à jour quand on veut ?

UpdateSourceTrigger détermine le moment des mises à jour de la liaison.
La valeur est par défaut 'Default' et le binding se fera dans ce cas lors de la sortie du contrôle source.
L'autre valeur est 'Explicit'.
Ici la mise à jour se fait quand on la provoque explicitement.
Exemple : on a un TextBox dans lequel on va taper du texte ; on a un TextBlock dans lequel le texte va s'afficher par binding. On va indiquer une mise à jour explicite.

 <TextBox x:Name="Mytxt" />
               
<TextBlock Name="Mytblock" Text="{Binding Text, ElementName=Mytxt, 
                     UpdateSourceTrigger=Explicit}" Height="50" />

Pour que la mise à jour intervienne lors du changement du texte dans la TextBox, il faut créer un EventHandler sur l'évènement TextChanged et dans la routine exécutée par ce dernier évènement effectuer la mise à jour du binding (propriété Update d'un BindingExpression).

using System.Windows.Data;
//.......


public partial class Window1 : Window
	 {
	 public Window1()
	 	 {
	 InitializeComponent();
	 
	 MyTextBox.TextChanged += new TextChangedEventHandler(OnTextChanged);
	 }
	 
	 private void OnTextChanged(object sender, TextChangedEventArgs e)
	 {
	  BindingExpression bex = Mytblock.GetBindingExpression(TextBlock.TextProperty);
            bex.UpdateSource();
	 }
	 }
Maintenant quand on tape du texte dans le TextBox, il s'affiche dans le TextBlock immédiatement.



En résumé : dans le Binding, ElementName indique le contrôle source. La propriété Path spécifie la propriété de l'objet.


Path peut aussi avoir une valeur comme Text.Height ou Text[1] (deuxième caractère).


V-G-3. Liaison Collection-ListBox

Pour une ListBox, c'est ItemsSource qui indique la source. Voici le code XAML de la ListBox : ItemsSource ="{Binding}" indique que la source est une liaison mais n'indique pas la source.

<ListBox Name="listbox1" ItemsSource ="{Binding}">            
</ListBox>

On crée une collection nommée 'noms' de type List(Of), on la remplit avec des noms ; comment faire une liaison List() ListBox et afficher automatiquement les noms dans la ListBox ?

Voici le code C# :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;

namespace testbinding
{
    public partial class MainPage : PhoneApplicationPage
    {
        public List<string> noms { get; set; }
        // Constructeur
        public MainPage()
        {
            InitializeComponent();
            noms = new List <string>();

            noms.Add("Paul");
            noms.Add("Pierre");
            noms.Add("Paule");
            listbox1.DataContext = noms; 
        }
    }
}
ListBox1.DataContext = noms indique quelle est la source de la liaison.
La liaison est ici par défaut : OneWay.


Cela marche aussi avec un tableau.


Le problème est que si ensuite j'ajoute un nom à la liste (noms.Add("Toto");), 'Toto' n'apparaît pas dans la ListBox.
Il n'y a pas de 'mise à jour'. On verra comment résoudre cela.


info Si vous souhaitez créer une liaison avec un objet source (comme une collection, par exemple) qui a déjà été instancié dans le code C#, vous devez définir la propriété DataContext dans le code C#.

Si l'utilisateur clique sur un élément de la liste, il est possible de l'utiliser dans un autre contrôle :

 <TextBlock Text="{Binding ElementName=listBox1, Path=SelectedItem}" Margin="6,296,-6,266" />
Ici dans le TextBlock, le nom qui a été sélectionné dans la ListBox sera affiché car on utilise comme Path le SelectedItem.



V-G-4. Liaison collection d'objets, ListBox

On veut une collection contenant des objets nommés 'ElementDeListe' ayant les propriétés 'Titre', 'Formule', 'Mode' et les afficher par liaison dans une liste.

On va créer une classe 'ElementDeListe' qui a trois propriétés: 'Titre', 'Formule' et 'Mode'.
On va aussi créer une collection nommée 'ListMath' qui hérite de la Classe List(Of). On y mettra des ElementDeListe.

Dans Class1.cs on crée une classe nommée 'ElementDeListe'.

using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;

    namespace listboxbinding // Espace de noms = nom du programme
    {
        public class ElementDeListe  // la classe se nomme 'ElementDeListe'
        {
            private string _Titre;  // Variable privée
            
            public string Titre     // Variable public avec son get et son set
            {
                get
                {
                    return _Titre;
                }
                set
                {
                    if (value != _Titre)
                    {
                        _Titre = value;
                       
                    }
                }
            }
            private string _Formule;

            public string Formule
            {
                get
                {
                    return _Formule;
                }
                set
                {
                    if (value != _Formule)
                    {
                        _Formule = value;

                    }
                }
            }

            private string _Mode;

            public string Mode
            {
                get
                {
                    return _Mode;
                }
                set
                {
                    if (value != _Mode)
                    {
                        _Mode = value;

                    }
                }
            }
          
            public ElementDeListe(string t, string n, string m) { _Titre = t; _Formule = n; _Mode = m; }  
        }
  
  }

Dans MainPage.xaml.cs on déclare une collection nommée 'ListMath' de type List, on y met quelques éléments.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;

namespace listboxbinding
{
    public partial class MainPage : PhoneApplicationPage
    {
        public List<ElementDeListe> ListeMath { get; set; } //la collection sera public
        // Constructeur
        public MainPage()
        {
            InitializeComponent();

            ListeMath = new List<ElementDeListe>();

            //On va créer de nouveaux ElementDeliste en utilisant le constructeur à 3 paramètres.
            ListeMath.Add(new ElementDeListe("Francs-Euros", "C", "C"));
            ListeMath.Add(new ElementDeListe("Celsius-Fahrenheit", "Celsius", "C"));
            ListeMath.Add(new ElementDeListe("Celsius-Kelvin", "Celsius", "C"));

            listBox1.DataContext = ListeMath;// en prime on ajoute un binding (une liaison) sur une listbox
        }
    }
}
En fin de code on lie la collection à la ListBox grâce à sa propriété DataContext.


Dans MainPage.xaml on crée une ListBox nommée 'listBox1', on indique que la source des items est une liaison.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <ListBox ItemsSource="{ Binding}" 
    Height="193" HorizontalAlignment="Left" Margin="40,32,0,0" Name="listBox1" VerticalAlignment="Top" Width="369" />
</Grid>
Cela donne :

La ListBox affiche les types !

Pour afficher Titre et Formule, on va ajouter un ItemTemplate contenant un StackPanel horizontal qui contient trois TextBlocks. Le premier et le troisième sont liés aux propriétés Titre et Formule.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <ListBox ItemsSource="{ Binding}" 
    Height="193" HorizontalAlignment="Left" Margin="40,32,0,0" Name="listBox1" VerticalAlignment="Top" Width="369" >
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Titre}" />
                            <TextBlock Text=", " />
                            <TextBlock Text="{Binding Formule}" />
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>

Le problème, là aussi, est que si ensuite j'ajoute un objet à la liste ( ListeMath.Add(new ElementDeListe("Ares-m2", "Ares", "C"));) ou si je modifie une propriété d'un objet de la liste, cela ne modifie pas la ListBox.
Il n'y a pas de 'mise à jour'.


On peut forcer la mise à jour de deux manières :

1 -En utilisant une ObservableCollection(Of)

Il faut utiliser une ObservableCollection générique qui possède l' interface 'INotifyCollectionChanged' qui entraîne, en cas d'ajout ou de suppression d'élément de la collection, une mise à jour de la ListBox.


Remarquons que pour utiliser une ObservableCollection il faut ajouter : 'System.Collections.ObjectModel'

using System.Collections.ObjectModel;
Ensuite on utilise l'ObservableCollection :

namespace listboxbinding
{
    public partial class MainPage : PhoneApplicationPage
    {
        public ObservableCollection<ElementDeListe> ListeMath { get; set; } //la collection sera public
        // Constructeur
        public MainPage()
        {
            InitializeComponent();

            ListeMath = new ObservableCollection<ElementDeListe>();
Maintenant cela marche : si on ajoute un élément à ListeMath, il apparaît immédiatement dans la ListBox.

C'est que l'observable collection a en natif une interface : INotifyCollectionChanged.


2 -En ajoutant des interfaces à la classe

En effet, avec une List rien n'indique à la liaison collection-listbox que la collection a changé et qu'il faut mettre à jour.


Ajout de INotifyPropertyChanged (après le nom de la classe et les ':').

Permet, lorsqu'on modifie une propriété d'un objet présent dans la List (on change un titre, par exemple), que la modification soit dans la ListBox.

using System.ComponentModel;

namespace listboxbinding  // Espace de noms = nom du programme
{
    public class ElementDeListe : INotifyPropertyChanged // la classe se nomme 'ElementDeListe'
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private string _Titre;  // Variable privée

        public string Titre     // Variable public avec son get et son set
        {
            get
            {
                return _Titre;
                
            }
            set
            {
                if (value != _Titre)
                {
                    _Titre = value;
                    EnvoiePropertyChanged("Titre");

                }
            }
        }

       
        private string _Formule;

        public string Formule
        {
            get
            {
                return _Formule;
               
            }
            set
            {
                if (value != _Formule)
                {
                    _Formule = value;
                    EnvoiePropertyChanged("Formule");

                }
            }
        }

        private string _Mode;

        public string Mode
        {
            get
            {
                return _Mode;
                
            }
            set
            {
                if (value != _Mode)
                {
                    _Mode = value;
                    EnvoiePropertyChanged("Mode");

                }


            }
        }
    


      
        private void EnvoiePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
               
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
       
        public ElementDeListe(string t, string n, string m) { _Titre = t; _Formule = n; _Mode = m; }
    }
}
  
  

Ajout de INotifyCollectionChanged.

Permet, si on modifie la collection (ajout d'un élément par exemple) de voir apparaitre cet élément dans la ListBox.
(Code contenant uniquement la partie INotifyCollectionchanged ; code non testé.)

// Extrait d'un article de Nathanael Marchand sur developpez.com. 
using System.Collections.Specialized;

public interface INotifyCollectionChanged
	{
		event NotifyCollectionChangedEventHandler CollectionChanged;
	}
	  
public delegate void NotifyCollectionChangedEventHandler(object sender, NotifyCollectionChangedEventArgs e);

public class NotifyCollectionChangedEventArgs : EventArgs
	{
 public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action);
 public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem);
 public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem, int index);
 public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems);
 public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems, int startingIndex);
 public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object newItem, object oldItem);
 public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object newItem, object oldItem, int index);
 public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList newItems, IList oldItems);
 public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList newItems, IList oldItems, int startingIndex);
 public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem, int index, int oldIndex);
 public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems, int index, int oldIndex);

 public NotifyCollectionChangedAction Action { get; }
 public IList NewItems { get; }
 public IList OldItems { get; }
 public int NewStartingIndex { get; }
 public int OldStartingIndex { get; }
	}
	
public enum NotifyCollectionChangedAction
	{
		Add,
		Remove,
		Replace,
		Move,
		Reset,
	}

idea On voit qu'il est bien plus simple d'utiliser une ObservableCollection pour que l'ajout ou la suppression d'élément soit pris en compte.
Par contre pour la modification d'élément déjà dans la liste , il faut implémenter INotifyPropertyChanged.


V-G-5. Convertisseur

On peut aussi utiliser un converter (en anglais "converter", en français "convertisseur") qui, entre la source et la cible va effectuer une conversion.

Ici l'exemple montre un binding avec comme cible un TextBlock, le converter enlève simplement les blancs avant et après.

<StackPanel>
  <StackPanel.Resources>
    <local:StringTrimmingConverter x:Key="trimmingConverter" />
  <StackPanel.Resources>
  <TextBlock Text="{Binding Path=Text, Converter={StaticResource trimmingConverter}}" />
</StackPanel>
Le converter est écrit en C#, il doit implémenter impérativement Convert et ConvertBack.

using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Globalization;

public class StringTrimmingConverter : IValueConverter
{

	public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
	{
		return value.ToString().Trim();
	}

	public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
	{
		return value;
	}

}


V-G-6. Binding avec tri, filtrage, image

Nous allons créer une page contenant une ListBox affichant une liste de personnes.
L'application se nomme 'binding avancé' et la page 'MainPage'.
Créons une classe 'Personne' avec les propriétés Prenom, Nom, Adresse, Sexe, Image et une classe 'LesMembres' contenant une liste de personnes (ce code est dans un module de classe class1.cs).

using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;

namespace bindingavancé
{
   
    public class Personne
    {
        public String Prenom { get; set; }
        public String Nom { get; set; }
        public String Addresse { get; set; }
        public String Sexe { get; set; }
        public String Image { get; set; }
        public Personne(String prenom, String nom, String addresse, string sexe, string image)
        {
            this.Prenom = prenom;
            this.Nom = nom;
            this.Addresse = addresse;
            this.Sexe = sexe;
            this.Image = image;
        }

    }
     public class LesMembres : ObservableCollection<Personne>
     {
        
    }

}

Dans le code-behind C# de la page, on instancie 'Membres' de type 'LesMembres' et on ajoute quatre 'Personne'.

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Collections.ObjectModel;

namespace bindingavancé
{
    public partial class MainPage : PhoneApplicationPage
    {
        public LesMembres Membres;// instancialisation d'un objet Membres
        // Constructeur
        public MainPage()
        {
            InitializeComponent();
        Membres= new LesMembres();  //initialisation de l'objet Membre
        // Ajout de 4 'personne' dans l'objet Membres
        Membres.Add(new Personne("Philippe", "Lasserre",
                "12 Muloland road", "M", "image1.jpg"));
        Membres.Add(new Personne("Odile", "Dupont",
                "31 avenue rien", "F", "image1.jpg"));
        Membres.Add(new Personne("Pierre", "Durand",
                "58 rue partout", "M", "image1.jpg"));
        Membres.Add(new Personne("Agathe", "Dubout",
                "1 place de l'église", "F", "image1.jpg"));
                
        //Création d'une liaison entre la collection et la ListBox1
        ListBox1.DataContext = Membres;
}
        }
    }

Dans le code XAML de la page, dans la grille principale, on ajoute une ListBox nommée 'ListBox1'.
Dans le ListBox on indique que l'ItemSource est une liaison (dans le code c# ci dessus le DataContext crée une liaison entre cette ListBox1 et Membres).

<ListBox Name="ListBox1" ItemsSource="{Binding }" Margin="36,60,38,353" Grid.Row="1">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Path=Nom}" />
                        <TextBlock Text=", " />
                        <TextBlock Text="{Binding Path=Prenom}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
On note qu'on a ajouté un ItemTemplate qui hérite de la liaison et affiche les propriétés Nom et Prenom.

Cela donne :



Nous allons aller plus loin.
Nous avons cette collection de Personne. Comment trier les personnes ? Comment n'afficher dans la ListBox que certaines personnes en fonction de critères ? Comment afficher des images ?
Pour cela ajoutons en haut le MainPage (pour avoir accès à Linq) et aux méthodes d'extension :

using System.Linq;

Maintenant on veut trier les personnes par le nom.
Nous allons utiliser OrderBy de Linq.
OrderBy effectue un tri.
Voilà comment modifier le DataContext :

ListBox1.DataContext = Membres.OrderBy(e => e.Nom);



Maintenant on veut filtrer les personnes par le Sexe.
On ne veut mettre dans la ListBox que les personnes de sexe 'F'. Nous allons utiliser Where de Linq.
Where précise les conditions à appliquer, c'est le 'filtre'.
Il faut ajouter une fonction TestSexe qui retourne true si Sexe="F" et modifier le DataContext qui filtrera en utilisant cette fonction :

namespace bindingavancé
{
    public partial class MainPage : PhoneApplicationPage
    {
        public LesMembres Membres;
        // Constructeur
        public MainPage()
        {
            InitializeComponent();
        Membres= new LesMembres();  
        Membres.Add(new Personne("Philippe", "Lasserre",
                "12 Muloland road", "M", "image1.jpg"));
        Membres.Add(new Personne("Odile", "Dupont",
                "31 avenue rien", "F", "image1.jpg"));
        Membres.Add(new Personne("Pierre", "Durand",
                "58 rue partout", "M", "image1.jpg"));
        Membres.Add(new Personne("Agathe", "Dubout",
                "1 place de l'église", "F", "image1.jpg"));
                
//DataContext utilisant la clause Where
        ListBox1.DataContext = Membres.Where(e=>TestSexe(e));
}
       
 public bool TestSexe(Personne p)
 //fonction ayant un paramètre de type Personne
 //retournant true si Sexe="F"
    {
        if (p.Sexe=="F")
        {
            return true;
        }
        else
        {
            return false;
        }
}   
    }
       

}


Petit bonus
Si l'utilisateur clique sur un élément de la liste, il est possible de voir dans un contrôle Image sous la ListBox une image correspondant à la "Personne" sélectionnée (le nom de l'image est dans la propriété 'Image') :

<Image 
Stretch="None"
Source="{Binding ElementName=listBox1,
Path=SelectedItem.Image}" /
Ici on a fait un binding sur le SelectedItem de la ListBox1. Sa propriété Image sert de 'Source', ce qui affiche l'image.




Maintenant on veut afficher une image différente suivant le sexe.
Nous allons utiliser Select de Linq.
Select précise les éléments à extraire. Il faut ajouter en haut du fichier Class1.cs et du fichier MainPage :

using System.Windows.Media;
using System.Windows.Media.Imaging;

Il faut créer une classe supplémentaire dans class1.cs.

  public class PersonneImage
     {
         public BitmapImage Image { get; set; }
         public String Prenom { get; set; }
         public String Nom { get; set; }
     }

Il faut créer (avec Paint) deux petites images, carrerose.jpg (de couleur rose) et carrebleue.jpg (de couleur bleue) et les mettre dans le répertoire du projet ; il faut ensuite les ajouter au projet (menu 'projet' puis 'Ajouter un élément existant'. Enfin il faut, dans les propriétés des images, indiquer 'contenu' dans 'option de génération'.


Dans MainPage, il faut ajouter une fonction 'giveImage' qui reçoit en paramètre 'F' ou 'M' et qui retourne un BitmapImage contenant l'image du carré rose ou bleue en fonction du sexe donc :


 private BitmapImage giveImage(string Sexe)
 {
     if (Sexe == "F")
     { return new BitmapImage(new Uri("carrerose.jpg", UriKind.Relative)); }
     else
     { return new BitmapImage(new Uri("carrebleue.jpg", UriKind.Relative)); }
 }
 
 ListBox1.DataContext = Membres.Select(e => new PersonneImage { Nom = e.Nom, Prenom = e.Prenom, Image = giveImage(e.Sexe) });

Il faut maintenant modifier le DataContext avec la clause Select qui retourne un objet de type PersonneImage avec le nom le prénom et un BitmapImage en fonction du sexe (pour cela on appelle la fonction giveImage).
 
 ListBox1.DataContext = Membres.Select(e => new PersonneImage { Nom = e.Nom, Prenom = e.Prenom, Image = giveImage(e.Sexe) });

Enfin il faut modifier, dans le code XAML, le DataTemplate de la ListBox en ajoutant un contrôle Image dont la propriété Source sera en liaison avec la propriété Image de l'objet PersonneImage :

<ListBox Name="ListBox1" ItemsSource="{Binding }" Margin="36,60,38,353" Grid.Row="1">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Image Source="{Binding Image}" Width="20" Height="20"/>
                        <TextBlock Text="{Binding Path=Nom}" />
                        <TextBlock Text=", " />
                        <TextBlock Text="{Binding Path=Prenom}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
Génial !


On peut mettre plusieurs clauses :

 ListBox1.DataContext = Membres.Select(...).Where (...).OrderBy(...);


V-G-7. Générateur de liaison

Dans la fenêtre 'Propriétés' d'un contrôle, pour créer une liaison avec la propriété d'un contrôle on peut ouvrir la fenêtre du générateur de liaison en cliquant sur le carré à côté du nom de la propriété :

Dans le menu, choisir 'Appliquer la liaison de données'.

Choisir l'objet de liaison désiré.



V-G-8. Erreur dans le binding

Si, dans le code Xaml, nous orthographions mal le nom d'une propriété dans l'expression du binding ('nome' au lieu de 'nom' par exemple), il n'y a pas d'erreur à la compilation mais la liaison ne se fait pas.
Heureusement, il y a un message d'erreur dans la fenêtre de sortie (Ctrl+W, O) :


 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2011 Lasserre Philippe. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.