Windows Phone 7.

Lasserre Philippe

L'objet de ce cours est de créer des applications pour un téléphone portable Windows Phone 7.5 (OS 7.1).
Il n'y a pas de prérequis.

Autres contributions du même auteur :
Cours complet sur Visual Basic.Net.
Cours sur Chart le contrôle graphique en VB.Net ou en C#.
Pour voir et télécharger LDF: logiciel de compta en shareware.
Télécharger un dictionnaire médical gratuit pour Word.

philippe@lasserrelyon.fr

Commentez cet article*: Commentez

- Titre : Windows Phone 7.

- Auteur : Lasserre Philippe

- Parution : 19 novembre 2012

- Mise à jour : 20 février 2013

- Public visé :

- Licence : 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 et 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.

Sommaire

- I. Définition, plan du cours

- I-A. Définitions XAML, Silverlight, Wpf, C#

- I-B. Documentation

- I-C. Ce qu'il faut comprendre, plan du cours

- II. Visual C# Express= l'IDE

- III. Le C# et Framework.NET

- III-A. Premier programme

- III-B. Objet, Classe, Espace de noms.

- III-C. Variable, déclaration, initialisation, affectation, constante

- III-D. Strings, Char, StringBuilder

- III-E. Variables numériques

- III-F. Autres types de variable : booléen, objet

- III-G. Nullable

- III-H. Tableau

- III-I. Conversion de variable, caractère décimal

- III-J. Type Valeur et Type Référence

- III-K. Bool, if, et , ou, switch, goto

- III-L. Boucle

- III-M. Les génériques

- III-N. Les collections

- III-N-1. Liste générique

- III-N-2. Dictionary

- III-N-3. LinkedList

- III-N-4. Queue

- III-N-5. Stack

- III-N-6. BitArray

- III-N-7. ObservableCollection

- III-N-8. Généralisation sur la notion de collection

- III-O. Les énumérations

- III-P. Les structures

- III-Q. Les fonctions, procédures, méthodes

- III-R. Erreurs et interception des erreurs

- III-S. Linq

- III-T. Date et heure

- III-U. Nombres aléatoires

- III-V. Culture, globalisation

- III-W. Afficher

- III-X. Le Framework.NET

- III-Y. Création de classe

- III-Z. Exercices en C#

- 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

- VI. Les différents contrôles

- VI-A. Page

- VI-B. Pivot et Panorama

- VI-C. Les Conteneurs

- VI-C-1. Les Grid

- VI-C-2. Les StackPanel

- VI-C-3. Les Canvas

- VI-C-4. Les ScrollViewer

- VI-C-5. WrapPanel

- VI-C-6. Positionnement dynamique : les grandes règles

- VI-D. Les Boutons

- VI-D-1. Les Button

- VI-D-2. HyperLinkButton

- VI-E. Les contrôles contenant du texte

- VI-E-1. Les TextBlock

- VI-E-2. Les TextBox

- VI-E-3. Les RichTextBox

- VI-E-4. Les PasswordBox

- VI-F. Les cases à cocher et RadioButton

- VI-F-1. Case à cocher

- VI-F-2. RadioButton

- VI-G. Les ListBox

- VI-H. Les Slider

- VI-I. Les MessageBox

- VI-J. Image, Vidéo, Son

- VI-K. Formes

- VI-L. Barre System en haut

- VI-M. Application Barre

- VI-N. AdControl pour la publicité

- VI-O. Le Toolkit

- VI-P. MessageBox

- VII. Grandes fonctions WP

- VII-A. Données

- VII-A-1. État de l'application

- VII-A-2. Isolated Storage

- VII-A-3. LINQ to SQL, données sur le Web

- VII-B. Activation, désactivation, Tombstone.

- VII-C. Le pattern Model-View-ViewModel

- VII-D. Orientation

- VII-E. Lanceur d'applications internes

- VII-F. Les thèmes

- VII-G. Vibreur

- VII-H. Localisation

- VII-I. Accéléromètre

- VII-J. Internet, WebBrowser, WebClient

- VII-K. Radio FM

- VII-L. Minuterie

- VII-M. Applications en tâche de fond : alarme, rappel

- VII-N. Informations diverses

- VIII. Comment faire

- VIII-A. SplashScreen

- VIII-B. Image de fond de page, icône

- VIII-C. Positionnement dynamique des contrôles

- VIII-D. Navigation entre pages, transfert de paramètres

- VIII-E. Validation de caractère dans un TextBox

- VIII-F. Créer ses propres contrôles

- VIII-G. Gérer le BackButton

- VIII-H. Saisie tactile, gesture

- VIII-H-1. Événements souris

- VIII-H-2. Événements de bas niveau

- VIII-H-3. Événements de haut niveau

- VIII-H-4. Gestures du toolkit

- VIII-I. Perdre du temps

- VIII-J. Sortir de l'application

- VIII-K. Application démonstration ou payée

- VIII-L. Mes trucs

- IX. Compilation, test, distribution

- IX-A. Compilation

- IX-B. Test sur un Windows Phone

- IX-C. La distribution

I - Définition, plan du cours

L'objet de ce cours est de créer une application pour un téléphone portable Windows Phone 7 et 7.1.
Il n'y a pas de prérequis.
Ce cours est en français !
J'explique les choses à ma manière.
Comme d'habitude ce cours sera complété avec l'expérience de l'auteur et les compléments ou corrections proposés par les lecteurs.
L'auteur ne répondra pas aux questions spécifiques ; il y a les forums pour cela mais il pourra rajouter des compléments au cours.
Merci de me signaler les erreurs, fautes d'orthographe et merci de m'adresser vos critiques.

I-A - Définitions XAML, Silverlight, Wpf, C#

Dans Windows Phone 7, on peut utiliser deux manières de dessiner l'interface utilisateur:
- avec Silverlight qui utilise un moteur de rendu vectoriel et des accélérations matériels pour afficher. Cela permet d'afficher de la 2D, de la 3D, des animations, des documents, des dessins vectoriels ou Bitmap, de l'audio, de la vidéo ;
-avec XNA pour faire des jeux (non étudié dans ce cours).

L'interface Silverlight est écrite en code XAML.

Que signifie XAML ?

XAML= "eXtensible Application Markup Language", en français: "langage à balises d'applications extensibles" est un langage utilisant la syntaxe XML et permettant de définir des interfaces d'applications. En d'autres termes, quand vous dessinez une interface utilisateur en Silverlight (ou en WPF), elle est enregistrée en XAML.

Silverlight est une variante allégée de WPF (Windows Présentation Foundation est utilisé sur les ordinateurs). Ce qui fait que Silverlight est portable et utilisé par les navigateurs Web et Windows Phone.

Par contre le code d'exécution du programme est soit en C#, soit en Visual Basic.

Le cours actuel utilise C#.

Pour développer une application Windows Phone 7, il faut Visual Studio version pro (payante) qui permet de développer en C# ou en Visual Basic.

On peut aussi développer sur Visual Studio 2010 Express (gratuit) mais uniquement en C# actuellement.

Le présent cours utilise Visual Studio 2010 for Windows Phone Express (C#) gratuit et téléchargeable ici: http://msdn.microsoft.com/fr-fr/express/aa975050 et Windows Phone 7.1 (Mango) la seconde version de l'OS de WindowsPhone.

I-B - Documentation

Où trouvez de la documentation ?

Documentation officielle en anglais sur MSDN :http://msdn.microsoft.com/fr-fr/library/ff402535%28v=vs.92%29.aspx

Silverlight en anglais (attention, c'est Silverlight 4, Silverlight pour Windows Phone est légèrement différent) :Silverlight

(http://msdn.microsoft.com/en-us/library/cc838158%28v=VS.95%29.aspx)

Windows Phone (Microsoft) en anglais : Windows Phone

(http://msdn.microsoft.com/en-us/library/ff402535%28v=VS.92%29.aspx)

On peut charger Windows Phone Documentation en anglais, sous forme d'un fichier d'aide chm : Windows Phone Documentation (fichier CHM en anglais)

Pour débuter Quick Start sur App Hub en anglais :http://create.msdn.com/en-us/education/quickstarts

App Hub :App Hub

Guideline pour l'aspect des applications en anglais :
http://msdn.microsoft.com/en-us/library/hh202915%28v=VS.92%29.aspx

Exemple de code expliqué en anglais :http://msdn.microsoft.com/en-us/library/ff431744.aspx

Un livre sur Windows Phone en pdf en anglais :http://www.charlespetzold.com/phone/index.html

31 days of Windows Phone 7 :http://www.jeffblankenburg.com/2010/09/30/31-days-of-windows-phone-7/

31 days of Mango :http://jeffblankenburg.com/31daysofmango/

Le recent Dev Center en anglais : http://msdn.microsoft.com/library/windowsphone/develop

Windows Phone sur MSDN ; site en français donnant accès au « Coach Windows Phone », à un tutoriel pour publier, à des toolskits :http://msdn.microsoft.com/fr-fr/windowsphone/default

Les cours sur développez.com en français : https://windowsphone.developpez.com/cours/

Le blog MSDN de Jean-Francois Lavidalle's blog ; une multitude de tutoriels en français(Louis-Guillaume Morand y participe) ; fait partie de MSDN :
http://blogs.msdn.com/b/jflavidalle/archive/2011/09/06/developpement-windows-phone-partie-1.aspx

I-C - Ce qu'il faut comprendre, plan du cours

Avant tout, il faut connaitre le langage C# et le Framework.Net pour écrire le code d'exécution. Le XAML sera utilisé pour écrire du Silverlight qui décrit l'interface utilisateur.

Dans ce cours vous découvrirez :

- l'IDE de Visual Studio Express ;

- un micro cours de C# ;

- un chapitre sur XAML ;

- les grands principes Silverlight ;

- les différents contrôles ;

- les grandes fonctions (IsolatedStorage,Tombstone) ;

- comment faire: SplashScreen ;

- compilation distribution.

On insiste sur le fait qu'en Windows Phone il y a séparation de la présentation et de la logique.
Cela signifie que les concepteurs peuvent travailler sur l'apparence d'une application et le comportement de l'apparence en utilisant uniquement XAML pendant que les développeurs travaillent sur la programmation logique à l'aide de C#.

On verra que le comportement de la présentation (disposition des contrôles, affichage de données) est totalement pris en compte par la présentation.

On peut aussi utiliser Expression Blend, un logiciel très puissant sur le plan graphique, pour dessiner l'interface. Pour le moment, ce cours ne l'utilise pas, bien qu'il soit exécuté très simplement à partir de Visual Studio.

II - Visual C# Express= l'IDE

C'est l'Integrated Development Environment (IDE) : environnement de développement intégré de Visual Studio Express 2010 for Windows Phone de Microsoft.
Il permet de dessiner l'interface (les fenêtres, les boutons, les listes, les images…) et d'écrire le code C#.
Chez nous, on peut aussi dire EDI (Environnement de Développement Intégré).

Lancez le logiciel, vous voyez la page d'ouverture :

Cliquez sur « Nouveau Projet » en haut à gauche, cela ouvre la fenêtre suivante :

Choisissez à gauche Silverlight pour Windows Phone, au milieu Application Windows Phone Visual C# donnez un nom (évitez de donner comme nom un mot clé du genre « char » ou « string ») ; en bas, puis cliquez sur « Ok ».

Ensuite vous pouvez choisir la version de Windows Phone 7.0 ou 7.1 (Mango) en déroulant la liste :

Gardez la version 7.1.

L'espace de travail s'ouvre :

On est dans l'onglet MainPage.xaml. Il y a le designer à gauche qui permet de dessiner l'interface que verra l'utilisateur. Le designer génère le code XAML correspondant, on le voit à droite, il décrit l'interface en XAML. On peut aussi écrire directement du XAML à droite, ce qui modifie l'interface dans le designer.

Le logiciel a créée une page vide. Nous pouvons ajouter un bouton par exemple, pour cela on ouvre la boite à outils à gauche :

On clique sur le bouton dans les outils puis sur la page dans le designer, on appuie (bouton gauche de la souris), on déplace puis on lâche (Drag and Drop). Le bouton apparaît.

On note que le code XAML correspondant au bouton a été ajouté dans la fenêtre de droite.

En bas à droite, on a la fenêtre de propriétés du bouton. Les contrôles font partie de l'espace de noms System.Windows.Controls.

On remarque qu'il n'y a pas de propriété Text pour le texte du bouton, mais une propriété « Content ». On y reviendra.

Dans cette fenêtre des propriétés, on peut modifier rapidement, le nom du bouton, les propriétés de ce bouton : Content, Fontsize, Background…

À droite du nom des propriétés, dans la première colonne, figure un marqueur de propriété. Ce marqueur de propriété indique s'il y a une liaison de données ou une ressource appliquée à la propriété. Lorsque vous cliquez sur le marqueur de propriété, vous pouvez ouvrir le générateur de liaisons de données ou le sélecteur de ressource.

Une autre manière, moins rapide, de modifier les propriétés de ce bouton est de taper du code XAML dans la fenêtre de droite :

<Button Content="Start" Height="68" HorizontalAlignment="Left" Margin="160,176,0,0" Name="button1" VerticalAlignment="Top"
Width="226" Click="bouton_click" />

Il faut savoir parler l'XAML !

Si on double-clique sur un bouton, par exemple, on se retrouve dans la fonction évènement correspondante (ici l'évènement "button1_Click"), celle qui contient du code C# qui sera exécuté quand l'utilisateur de l'application clique sur le bouton.

La fonction évènement et le code C# sont dans l'onglet MainPage.xaml.cs.

Souvent on a besoin de gérer d'autres évènements :
quand l'utilisateur frappe une touche cela déclenche les évènements KeyDown puis KeyUp (pas de keyPress !)

Pour ajouter la gestion de l'évènement KeyUp, cliquer sur le bouton. Dans la fenêtre des propriétés cliquer sur l'onglet « évènement ».

puis dans la liste double-cliquez sur « KeyUp » vous voyez apparaitre :

<TextBox  Name="textBox1" Text="TextBox1"  KeyUp="textBox1_KeyUp" />

Et dans le code C# :

private void textBox1_KeyUp(object sender, KeyEventArgs e)
{

}

On note que la fonction a un paramètre nommé sender (c'est l'objet déclencheur) et un paramètre e de type KeyEventArgs (arguments, paramètres de l'évènement).

Quand on a le curseur sur un objet visuel (comme un bouton), si on clique droit, on a un menu permettant d'aller dans le code XAML du bouton (le button dans le code XAML est sur fond gris) ou dans le code C# :

On remarque en haut à droite la fenêtre Explorateur de solutions qui contient les différents éléments du projet :

On voit :
-les propriétés du projet ;
-les références (DLL du projet) ;
-le code XAML de l'application : App.xaml ;
-les icônes et images du projet :Application Icon.png, Background.png, SplashScreenImage.jpg ;
-la page (MainPage ici) ou les pages composées de l'interface (MainPage.xaml) et du code C# (MainPage.xaml.cs).

En bas il y a la liste des erreurs qui sont dans le code et la ligne où se trouve l'erreur (s'il y en a !) :

Dans les fenêtres, il y a différentes icônes permettant de modifier la taille du texte du design ou des volets :

On peut cliquer sur le titre de l'onglet d'une fenêtre et en maintenant enfoncé le bouton gauche de la souris, déplacer la fenêtre :

Quand on déplace, une croix devient visible ; il suffit de lâcher le curseur par exemple sur le haut de la croix pour que la fenêtre soit ancrée en haut.

Par le menu Projet, puis Propriétés de on affiche les propriétés du projet :

Pour déboguer, il faut choisir l'émulateur en haut au milieu :

si on clique sur le bouton d'exécution (ou F5) on compile l'application, on charge l'émulateur WP7 et on exécute l'application dans l'émulateur.

La souris permet de simuler le doigt sur l'écran. Il est en 16 bits par point soit 65635 couleurs.

À sa gauche il y a une barre d'outils qui apparait quand le curseur de la souris s'approche.
Les boutons de haut en bas :

- fermer l'émulateur.

- réduire l'émulateur.

- faire pivoter l'émulateur pour passer en mode paysage.

- faire pivoter mais dans l'autre sens.

- ajuster l'émulateur à la taille de l'écran.

- définir le zoom de l'émulateur.

- autres.

- accéléromètre.

- localisation.

- copie d'écran.

Sur la droite de l'écran, il y a des compteurs (voir le chapitre sur la compilation et les tests).

Dans le constructeur de la classe App de votre application (App.xaml.cs), vous pouvez faire disparaitre ces compteurs.

Application.Current.Host.Settings.EnableFrameRateCounter = false;

On peut mettre dans le code un point d'arrêt en double-cliquant dans la marge à gauche de la ligne ,un rond apparait :
Si on exécute l'application, elle s'arrêtera si elle rencontre un point d'arrêt et surlignera la ligne en jaune.

Si on met le curseur sur une variable ou un objet du code C#, sa valeur s'affiche dans un petit rectangle (il faut parfois dérouler les détails en cliquant sur les « + »).

On peut ensuite faire exécuter le programme pas à pas (F11) sans sauter dans les routines (F10), on peut même remonter (Maj+F11) :

On voit qu'on peut aussi ouvrir des fenêtres :

Liste des variables locales et leurs valeurs :

On peut ouvrir une fenêtre Espion, saisir le nom d'une variable ou d'un objet et voir sa valeur.

On peut poursuivre l'exécution par F5, arrêter complètement le débogage par Maj-F5.

Dans Visual Studio 2010, pour modifier l'interface, on peut ouvrir le projet dans Expression Blend 4. C'est un logiciel permettant de dessiner des interfaces complexes et très élaborées. Il faut pour cela passer par le menu « Afficher » puis « Ouvrir dans Expression Blend..».

Où se trouvent les sources des applications ?
Pour mon application qui se nomme CalculTout : dans « C:\Users\Philippe\Documents\Visual Studio 2010\Projects\CalculTout ».
Il y a les fichiers CalculTout.sln et CalculTout.suo.

Il y a dessous un répertoire nommé aussi CalculTout qui contient les fichiers « .xaml » et « .xaml.cs » et les répertoires Bin et Obj qui contiennent chacun les répertoires Debug et Release.

Conclusion : le XAML permet de décrire l'apparence de l'application, tandis que les évènements sont gérés par un langage .NET classique comme le C# (étudié ici) ou le Visual Basic.

III - Le C# et Framework.NET

Le C# (prononcer « si sharp ») est un langage de programmation orienté objet et à typage fort, créé par la société Microsoft (Anders Hejlsberg). On l'associe à une boîte à outils que l'on appelle le Framework .NET (prononcez "dotte nette") qui offre une multitude de possibilités : classe d'entiers, de chaîne de caractères… classe d'objets visuels (bouton, textbox, image…), appel à une base de données…

Pourquoi le # (dièse) ? parce que C# est une note au dessus de C !

III-A - Premier programme

Ouvrir l'IDE, créer une nouvelle application (voir chapitre précédent).

Où mettre le code C# à tester ?
Ouvrir MainPage.xaml.cs et placer votre code à tester après « public MainPage() » (c'est le constructeur de la page) sous InitializeComponent. Il sera exécuté dès le démarrage 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 MonApplication
{
public partial class MainPage : PhoneApplicationPage
{
// Constructeur
public MainPage()
{
InitializeComponent();

//Mettre votre code ici.
//Il sera exécuté au lancement de l'application
}
}
}

On veut afficher « Bonjour ».(Pas très original !)

Voici le code qui fait cela :

string texte;      //On déclare (on crée) une variable nommée texte
texte= "Bonjour";  //On affecte un texte à la variable texte (on met "Bonjour" dans texte)
MessageBox.Show(texte); //On affiche le texte dans une messageBox

Taper sur F5.
Cela donne :

Une instruction est une commande élémentaire lue et exécutée par un processeur.

On remarque dans le code C# :
L'exécution se fait séquentiellement, instruction par instruction de haut en bas.
Chaque instruction se termine par « ; ».
Il y a distinction entre majuscules et minuscules.
Les commentaires (parties non exécutées) commencent par //.
On peut aussi utiliser /* commentaires */ qui permet d'écrire des commentaires sur plusieurs lignes.

On peut mettre plusieurs instructions sur une même ligne (séparées par des « ; »).
On peut aussi écrire une instruction sur plusieurs lignes (sans rien ajouter).
Un bloc d'instructions est placé entre accolades {} et peut contenir des blocs imbriqués.

/* Ceci est encore
1 commentaire sur 2 lignes */
{
int i = 3;                     //1 instruction sur une ligne
string nom;  nom= "Philippe";  //2 instructions sur cette ligne
string prenom = "Marie"+        //1 instruction sur 2 lignes
"Chantale";
}

Le code se compose de :
Mots clés C# : for, if = …
d'objets et Classes faisant partie du Framework .NET et de leurs propriétés et méthodes : MessageBox, Math.Pow, String…

On verra aussi que les mots clés C# commencent par une minuscule (if , for…).
Par contre les noms de classes ou propriétés du Framework .NET commencent par une majuscule (Math.Pow, String.Equals…)

Ici on a affiché un texte dans une MessageBox ; on peut aussi afficher un texte dans un contrôle TextBlock ou un contrôle TextBox.
Il faut ajouter un TextBlock par exemple dans l'interface, ensuite pour afficher dedans :

textBlock1.Text = "Bonjour";

Pour faire des tests en mode Debug, on peut aussi afficher sur la sortie (fenêtre de l'IDE ouverte par CTR+W, O) en écrivant :

System.Diagnostics.Debug.WriteLine("Bonjour");

On doit être en mode Debug et non Release.

III-B - Objet, Classe, Espace de noms.

On utilise des objets dans les programmes. Des objets visuels comme les boutons, TextBox… ou des objets comme les collections, des objets mathématiques…

Il existe des types d'objet qui définissent les caractéristiques communes des objets. Ces types se nomment les Classes (il y a d'autres types…).

Exemple :
le bouton qui est sur mon écran fait partie de la classe Button. Ainsi il a toutes les caractéristiques de la classe Button.
On rappelle que se sont ces classes que l'on utilise pour instancier (créer) un objet, une instance.
En C# :

Button MyButton  = new Button();

La ligne déclare un bouton nommé MyButton.

On remarque que :

MyButton est un objet une variable par référence : dans la mémoire de l'ordinateur, à l'endroit de la variable il y a l'adresse où est stockée la valeur de la variable.
Il faut déclarer une référence puis instancier l'objet avec new :

// en deux lignes
Button MyButton;           //on déclare, myButton existe mais ne contient rien
MyButton  = new Button(); //on instancie

//ou

// en une ligne
Button MyButton  = new Button();

On le redit : on déclare une référence (avec la première ligne) puis on instancie l'objet avec new (seconde ligne). Parfois on le fait dans deux endroits différents.

Par opposition un int (un type structure numérique entier), permet de créer une variable par valeur, (dans la mémoire de l'ordinateur, à l'endroit où est stockée la variable, il y a directement la valeur de la variable) pas besoin du mot new pour instancier.

On a à notre disposition de multiples classes toutes faites (Button, ListBox, PhoneApplicationPage, int, double…) pour créer des applications. En plus on peut créer de toutes pièces une classe dont on a besoin.

Libération de mémoire : l'objet est libérable quand il cesse d'exister (quand on sort de la fonction où on l'a déclaré et qu'il n'existe plus aucune référence vers lui dans un autre endroit).
Il peut aussi être libérable en lui affectant la valeur null. Mais il existe toujours.
En pratique, on ne s'occupe pas de libération de la mémoire qui s'exécute automatiquement.
Le ramasse miettes (Garbage Collector) libérera ensuite réellement la mémoire quand il en aura besoin.

Un objet a des propriétés. La syntaxe est NomObjet.NomPropriété :

MyButton.Content = "Bonjour";

Ici on a utilisé la propriété Content du bouton.

Un objet a aussi des méthodes :

MyButton.Focus()

Cette ligne déclenche une méthode qui donne le focus au bouton.(c'est le bouton qui devient actif).

Une méthode peut avoir un ou des paramètres :

MessageBox.Show("texte");

Ici la méthode Show a un paramètre ; c'est le texte qui sera affiché dans la MessageBox.

Pour une méthode qui a des paramètres, il peut y avoir surcharge : on peut avoir une même méthode (même nom) avec le nombre ou le type de paramètres différent :
Exemple : la méthodes .Show de MessageBox qui affiche une boite de message accepte 2 signatures différentes ; il y a surcharge de la méthode :

MessageBox.Show("texte");
MessageBox.Show("texte", "titre", MessageBoxButton.OK);

Dans une classe il peut y avoir des fonctions :

public void Affiche()
{
}

Un objet peut avoir des évènements.
Pour un bouton par exemple, quand l'utilisateur de l'application clique sur le celui-ci, l'évènement Button.Click se produit.
L'application devra gérer les évènements qui se produisent.

Les classes sont regroupées en bibliothèques sous la dénomination d'Espace de noms (NameSpace). Un framework est un ensemble d'espace de noms et de classes.
On a à notre disposition le Framework.NET qui contient des milliers de classes.

Les espaces de noms sont le moyen de regrouper logiquement des classes afin de mieux s'y retrouver. Cela permet aussi d'utiliser des classes ayant le même nom mais venant d'espaces de noms différents.

Pour pouvoir utiliser une classe il faut inclure l'espace de noms auquel appartient cet objet.
En C#, on le fait grâce à using qui sera placé en haut du code, avant toutes autres instructions :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;

Ici, en haut du code, on ajoute les espaces de noms donnant accès aux variables (System), aux contrôles Silverlight (System.Windows.Controls en bas).

using permet d'inclure un espace de noms qui permet d'avoir accès à des classes. Ces classes permettront d'instancier (de créer) des objets.

Dans le code XAML on peut aussi inclure des espaces de noms grâce à xmlns (xml name space) :

xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"

Enfin pour utiliser un espace de noms, il faut que l'assembly (la DLL ou librairie dynamique sous forme d'un fichier .dll) contenant cet espace de noms soit incluse dans les références du projet.
Pour utiliser les contrôles (Button, ListBox… qui sont dans l'espace de noms System.Windows.Controls) la référence « System.Window » doit donc être dans les références du projet (ce qui est le cas par défaut) ; voir les références dans la fenêtre de l'explorateur de solution.

On peut ajouter une référence (un assembly sous forme d'une DLL) : pour cela, clic droit sur Références dans l'explorateur de solution puis sur Ajouter une référence.

Les classes sont regroupées "physiquement" par assembly (DLL) et "logiquement" par namespace (les namespaces et assemblies peuvent se recouper : des classes d'un même namespace peuvent se retrouver dans différents assemblies, et des classes d'un même assembly dans différents namespaces).

On verra qu'une page d'une application Windows Phone est une Classe. Voici les premières lignes de la page d'une application.

namespace CalculeTout
{
public partial class MainPage : PhoneApplicationPage
{

Il y a un espace de noms (qui a le nom de l'application) et une classe partielle (qui a le nom de la page).

Au passage, une classe peut être découpée et composée à différents endroits de plusieurs classes partielles.

Notion de propriété d'instance et de propriété static.

Une propriété d'un objet peut être accessible sur un instance : un objet qui a été crée à partir de la classe ; on appelle cela un membre d'instance.

string myString= "qsd"; // on déclare une instance de la classe string nommée 'myString'.
myString= myString.Trim(" "); //on utilise .Trim sur l'instance myString

La propriété est celle de l'objet et non du type.
La syntaxe est NomDeLinstance.NomPropriété.

Par contre certaines propriétés ou méthodes sont des propriétés ou des méthodes de la classe, elles sont static :

bool c= String.Compare (a, b); // on utilise .Compare de la classe String

La propriété est celle du type String et non de l'objet.
La syntaxe est NomDeLaClasse.NomPropriété.

De la même manière il y a des classes static, des méthodes static.

Les classes statiques et les membres de classe sont utilisés pour créer des données et des fonctions auxquelles il est possible d'accéder sans créer d'instance de la classe.

Autre exemple : Si je veux créer un compteur d'instance, je vais ajouter une propriété static qui va s'incrémenter pour chaque instanciation.

Héritage : une classe peut hériter d'UNE autre classe.
La classe héritée possède tous les attributs de la classe mère (ou classe de base) dont elle hérite :

Par exemple, la classe Button hérite successivement des classes :
Object
DependencyObject
UIElement
FrameworkElement
TypesBases
Control

Quand on crée une classe, on peut indiquer de quelle classe elle hérite : par exemple on a dans le code C# une page nommée MainPage, elle hérite de la classe PhoneApplicationPage, elle a donc tous les attributs de la classe de base PhoneApplicationPage qui est une classe du framework. On note bien la syntaxe : après « Class MainPage », « : PhoneApplicationPage » indique la classe mère.

public partial class MainPage : PhoneApplicationPage

Dans la classe dérivée qu'on a créée, on peut ajouter des champs et des méthodes, on peut aussi appeler les méthodes de la classe de base.

On peut aussi redéfinir une méthode qui remplacera celle de la classe de base (à condition qu'elle soit virtuelle).

Interface :

Il y a deux notions :
- l'interface au sens général du terme, indique "ce que le type expose publiquement", ce que je vois de l'objet, c'est son interface (le nom des propriétés, méthodes…) ; exemple : la méthode Clear fait partie de l'interface d'une ListBox. Par contre le code qui effectue la méthode (celui qui efface physiquement toutes les lignes de la ListBox), ce code se nomme implémentation, lui n'est ni visible ni accessible pour les classes du framework.
- l'interface au sens spécifique de .NET : elle définit un contrat à implémenter (relation "peut faire" ou "a un").

Le nom d'une interface commence par « I ». Ainsi l'interface IEnumerable. Une classe, List générique par exemple, implémente les interfaces ICollection générique (définit des méthodes pour manipuler des collections génériques) et IList (méthodes pour les collections d'objets accessibles séparément par index).

Quand vous créerez une classe, vous écrirez vous même l'implémentation et vous pourrez ajouter à votre classe une interface.

Visibilité :

Quand un objet est créé, il est visible et utilisable, uniquement dans certaines parties de l'application.
Par exemple je peux voir et modifier la couleur d'un bouton créé dans le code XAML uniquement dans le code de la page où il est situé ; dans une autre page il n'est pas accessible.
Pour les variables on parle de portée : la variable peut être locale (private) ou de portée générale ('public') visible partout.

On utilise souvent le mot clé this qui désigne l'objet sur lequel opère la méthode, l'objet où on est. Le mot clé this fait référence à l'instance actuelle de la classe.

public Employee(string name, string alias)
{
this.name = name;
this.alias = alias;
}

Une fonction est une suite d'instructions regroupées sous un nom qui accepte des paramètres et qui retourne un résultat. Pour l'utiliser on l'appelle par son nom en ajoutant les paramètres, elle retourne le résultat.
On ajoute void si elle ne retourne pas de résultats. On devrait parler de procédure dans ce cas.
En programmation objet on utilise le terme général de méthode.
Voici une fonction qui a deux paramètres (x et y) et qui retourne un int (entier) :

int Multiplier(int x, int y)
{
return x*y;
}

III-C - Variable, déclaration, initialisation, affectation, constante

Variable :

C'est le même sens qu'en mathématique.

Une variable sert à stocker un nombre, du texte (chaîne de caractères), une date, un objet…

Une variable a un nom et un type qui indique ce que l'on peut y mettre. Elle a aussi une portée : où et quand sera-t-elle visible et utilisable…?

Le nom d'une variable ne doit contenir que des caractères alphanumériques (abc […] ABC123 […] 0éè…) et « _ », il doit commencer par une lettre et le nom ne doit pas être le nom d'un type, d'une classe, ou d'un mot-clé du langage.

C# différencie majuscules et minuscules, il accepte les caractères accentués.

état, _item, Pos1 sont acceptés
1pos, etat-1, string sont refusés.

Nommage :
La camel casing correspond aux majuscules en chameau : la première lettre de chaque mot est en majuscule.

Pour les variables locales et paramètres de méthode, on utilise lowerCamelCase (première lettre en minuscule, puis première lettre de chaque mot en majuscule).
Exemple : nombreDePoint.

Pour les noms de types et leurs membres publics, on utilise PascalCase (ou UpperCamelCase, chaque "mot" commence par une majuscule).
Exemple : NombreDePoint.

Pour les membres privés, il n'y a pas de convention, mais on utilise habituellement la suivante :
- méthodes et propriétés : PascalCase, comme pour les membres publics.
- variables de classe (champs): lowerCamelCase, comme pour les variables locales, éventuellement précédées d'un préfixe comme "_".
Exemple : _Nom .

On peut aussi utiliser la notation hongroise : on ajoute une lettre, un préfixe indiquant le type :
Exemple : iIndex, sNom. Son utilisation est maintenant déconseillée.

On nomme souvent les variables booléennes (contenant true ou false) avec un nom commençant par « Is ».
Exemple : IsOpen.

Type de variable :
Les variables peuvent être de type numérique, chaîne de caractères, objet…

Exemple.
Si myString est une variable de type chaîne de caractères (ou String), je peux y mettre une chaîne de caractères (« TOTO » par exemple).

Déclaration :

Avant d'utiliser une variable, il faut la déclarer, la créer et lui allouer un espace de stockage, on dit aussi instancier = créer une instance.
La syntaxe est : type nom;

Déclarer une variable de type int (numérique de type entier) et nommée number :

int number;

Ainsi la variable number a été créée, elle ne peut contenir qu'un entier.

On peut déclarer plusieurs variables en même temps :

double number1, number2, number3, result;

Ici on a déclaré quatre variables de type double.

Initialisation :
Il faut donner initialement une valeur à la variable qui vient d'être créée :

int number;
number= 12;

On peut déclarer et initialiser en même temps :

int number = 12;

Autre exemple, créer un double et l'initialiser avec la valeur 12 (double) :

double conv = 12d;

Il est toujours préférable d'initialiser rapidement une variable. Le compilateur n'est pas content si on tente de lire une variable qui n'a pas été initialisée.

Si on utilise new cela initialise la variable en appellant le constructeur par défaut et donne la valeur par défaut du type.

int j = new int();  //j=0

On peut aussi déclarer et instancier un objet visuel ou non (ce n'est pas vraiment une variable mais plutôt un objet, mais bon ! c'est pour expliquer).

Button MyButton;              //déclaration
MyButton = new Button();   //instanciation

//ou en 1 lignes

Button MyButton = new Button();

Ici on a instancié un bouton que l'on nomme MyButton.
On remarque qu'on a mis new.

Autre exemple :

BitmapImage MyBitMap= new BitmapImage();

Ici on a instancié une BitmapImage que l'on a nommée MyBitMap.

Modificateur d'accès :

Une variable peut être accessible de partout (public) ou uniquement dans la classe ou la fonction où elle à été déclarée (private).

{ public partial class MainPage : PhoneApplicationPage

public int EntierPublic;  //Variable publique, on la verra partout, même dans une autre classe
private int EntierPrivé;  //Variable privée, on la verra uniquement dans la classe MainPage

private void SaveButton_Click(object sender, RoutedEventArgs e)
{

int Entier privé;// toute variable déclarée dans une fonction est privée à cette fonction
// On ne peut pas mettre private ou public
}

On remarque qu'une classe peut être aussi public ou privée, il en est de même pour une fonction.

Affectation :

C'est l'instruction la plus utilisée en programmation.

On peut aussi utiliser le terme « Assignation » à la place de l'affectation.

'Variable = Expression;' est une affectation, c'est le signe = qui indique l'affectation.

L'affectation transfère le résultat de l'expression située à droite du signe égal dans la variable (ou la propriété) à gauche du signe égal.

A= B;

Affecte la valeur de la variable B à la variable A, la valeur de B est mise dans A.

Exemple concret :

int number;
number= 12;

On affecte à la variable de gauche (number), la valeur située à droite (12).

S'il y a une expression à droite, elle est évaluée, calculée et affectée à la variable de gauche :

number= num + 12;

Si num est égal à 1, après l'affectation ci-dessus number sera égal à 13.

L'expression de droite peut être complexe : C*12-X+2.

Remarque : regardons l'expression suivante :

number= number +12;

Cela est valide. Le signe = n'indique pas une égalité mais une affectation de l'expression à droite qui est évaluée dans la variable de gauche. Si number avait la valeur 10, après cette affectation number aura la valeur 22.

L'affection marche pour les objets, leurs propriétés…

Bouton1.BackColor= Bouton2.BackColor;

Signifie que l'on donne au Bouton1 la même couleur de fond que celle du bouton2 : on affecte la valeur BackColor du Bouton2 au Bouton1.

Attention en C# pour l'égalité on utilise == (contrairement au basic où le symbole pour l'affectation et l'égalité est le même).
Exemple :

if ( a ==b)  //Si a égale b

typeof permet d' obtenir l'objet System.Type d'un type. sizeof sa taille en octets en mémoire (sizeof ne fonctionne que sur les types valeurs primitifs : int, bool, double…).
GetType permet d'obtenir le type d'un objet.

int i = 10;

// Quel est le type de int et la place occupée en mémoire par un int
MessageBox.Show("i est de type " + typeof(int) + " et occupe " + sizeof(int) + " octet(s) en mémoire.");

//On veut connaitre la valeur maximum permise avec un int
// (Min existe aussi)
int max = int.MaxValue;
MessageBox.Show(max.ToString());

//Comment connaitre le type d'une variable
System.Type type = i.GetType();
MessageBox.Show(type.ToString());

Une constante, comme son nom l'indique, est constante : elle prend une valeur au départ qui ne peut pas être modifiée.
On met « const » avant le type.

public const int months = 12;

Ici on déclare une constante nommée months de type entier qui est public.

L'usage des constantes améliore la lisibilité et facilite les modifications.

III-D - Strings, Char, StringBuilder

Le type String peut contenir une chaîne de caractères (alphanumérique) comme du texte. La longueur de la chaîne n'est pas fixe. Il n'est pas nécessaire de mettre un caractère de fin de chaîne.

Le type char contient un seul caractère.

Pour information Char et String contiennent en interne le code des caractères au format Unicode (dans la variable,chaque caractère est codé sur deux octets) et pas de l'ASCII ou de l'ANSI…(ancien codage ou chaque caractère était codé sur un octet).
La String ocupe en mémoire 20+(n/2)*4 octets (arrondi à la valeur n/2 près), où n est le nombre de caractères. Les premiers caractères ont le même code Unicode et Ascii.

On peut utiliser string de C# ou System.String du Framework NET ; c'est exactement la même chose.

Déclarons une string :

string MyString;

Après avoir été créée, une String contient null c'est à dire rien (même pas une chaîne vide) ; il faudra l'initialiser pour qu'elle contienne quelque chose.

Il s'agit d'une classe string. Les strings sont de type référence.

Déclarons et initialisons :

string MyString= "Hello";

Notez bien l'importance des guillemets :
A est la variable A
"A" est une chaîne de caractères contenant le caractère "A"

Le signe « + » permet d'ajouter (de concaténer) des strings.

MyString= "Hello " + " word";

On peut voir si deux strings sont égales ou différentes (avec == et !=).

if ( a!= b)
MessageBox.Show (" a différent de b");

Par contre on ne peut pas utiliser supérieur, inférieur, supérieur et égal. Il faut utiliser Compare (voir plus bas).

On peut ajouter des caractères de contrôle dans une chaîne :
\' - simple quote, affiche '.
\" - double quote, affiche ".
\n : à la ligne.
\t : tabulation.
\f : saut de page.
\r : retour chariot.
\' : guillemet simple.
\" : guillemet double.
\\ : affiche \.
\u0066 ajoute le caractère de code Unicode 0066.

Pour éviter les \\ on peut aussi utiliser la notation littérale "verbatim" qui commence par @" :

string StartDirectory = @"c:\Users\exampleuser\start";

Il existe aussi le type (char) qui ne peut contenir qu'un seul caractère.

char mycar = 'd';

On note que pour un caractère, on utilise ' et non ".

string est une classe qui a des méthodes :

.ToUpper :

Elle retourne la chaîne de caractères en majuscules.

str=str.ToUpper();

Si str contenait "abc" il contiendra "ABC"

.ToLower transforme par contre la chaîne en minuscules.

.Trim :

Permet de supprimer des caractères en début et fin de chaîne.

Pour enlever les espaces avant et après la chaîne (cas le plus fréquent) :

string MyString= "  Informatique  "; //chaîne avec des espaces avant et après

MyString=MyString.Trim();            // enleve les espaces

MessageBox.Show("I"+MyString+"I");   // affiche 'IInformatiqueI'

Pour enlever un caractère :

string MyString= "/Informatique/";

MyString=MyString.Trim('/'); // enlever le caractère '/'

MessageBox.Show(MyString);

Notez bien qu'un caractère c'est '/' (une string c'est "/").

Pour enlever des caractères, il faut mettre en paramètre de Trim un char ou un tableau de char.

string MyString= "#@Informatique@#";  // chaîne initiale
char[] b;  // déclaration d'un tableau de Char
b=new char[2]  {'#', '@'};   //b est un tableau de Char contenant les 2 caractères à supprimer.

MyString=MyString.Trim(b);   //elimine les char en début et fin de chaîne

MessageBox.Show(MyString);   //affiche dans une MessageBox la chaîne traitée

Attention : bien utiliser Char[] qui est un tableau de caractères (et pas une string) pour définir les caractères à supprimer.

Il existe aussi TrimStart et TrimEnd pour agir seulement sur le début ou la fin de la chaîne.

Length :

Length : taille d'une chaîne en nombre de caractères.

Afficher la taille de la chaîne « Windows »

string MyString= "Windows";

MessageBox.Show(MyString.Length.ToString());

On remarque que MyString.Length retourne une valeur numérique qu'il faut convertir en string (à l'aide de ToString() pour pouvoir l'afficher.

Concat :

Concaténation de plusieurs chaînes : mise bout à bout :

string a="aa";
string b="bb";
string c="";

s=String.Concat(a,b);

Il est plus rapide de faire :

s= a + b

Concat est très pratique quand on veut mettre bout à bout tous les éléments d'un tableau :

string[] b = { "bb", "cc","dd"}; // création d'un tableau de 3 chaînes

string c = string.Concat(b);

MessageBox.Show(c);     // donne bbccdd

Insert :

Insère une chaîne dans une autre.

string MyString= "Windows";

string c = MyString.Insert(3,"  ");

MessageBox.Show(c);  //Affiche 'Win  dows'

Noter : le premier caractère a la position 0.

Remove :

Enlève des caractères (ici deux caractères) à une certaine position dans une chaîne.
Les paramètres sont (position premier caractère, nombre de caractères à enlever).

string c = MyString.Remove(3,2);

Replace :

Retourne une nouvelle chaîne, copie de la chaîne de départ, mais où toutes les occurrences d'une chaîne à remplacer ont été substituées par une autre chaîne.

Resultat=ChaîneDépart.Replace(ChaîneARemplacer,ChaîneQuiRemplace);

string MyString= "02.12.1934";

string c = MyString.Replace(".","/");

MessageBox.Show(c);  //Affiche '02/12/1934'

Replace est pratique pour éliminer un caractère dans une chaîne.

Exemple, éliminer les « / » dans une string :

entr = entr.Replace("/", "");

Split :

Découpe en plusieurs sous chaînes une chaîne de départ, cela par rapport à un séparateur.

Je récupère une chaîne de mots ayant pour séparateur « ; », je veux mettre chaque mot dans un tableau.

string MyString = "Philippe;Jean;Toto";
string[] b;

b = MyString.Split(';');

Il peut y avoir plusieurs caractères de séparation :

string MyString = "Philippe.Jean;Toto";
string[] b;

b = MyString.Split(';', '.');

Remarque : quand on déclare le tableau b, on ne donne pas le nombre d'élément, c'est Split qui crée autant d'élément qu'il faut.

Si le paramètre ne contient pas de caractères, les délimiteurs sont supposés être des espaces blancs.

On peut ajouter deux paramètres permettant d'indiquer le nombre de lignes maximum et forcer l'élimination des lignes vides.

mots = phrase.Split('/t', 3, StringSplitOptions.RemoveEmptyEntries);

Ici le séparateur est le caractère de tabulation (/t) : retour de trois éléments. Si un élément est vide (séparateur en premier ou dernier caractère par exemple) on l'élimine.

Autre exemple, on a une string contenant des mots, découper les mots et les mettre dans un tableau de string :

string text  = "Ceci est un cours C# pour les débutants et les autres";

// Conversion de la String en Tableau de mots :.
string[] mots= text.Split(new Char [] {' ', ',', '.', ';', ':'});

.Join :

Concatène tous les éléments d'un tableau et peut ajouter des séparateurs.

Si b[] est un tableau de string, je veux ajouter les éléments bout à bout en les séparant d'un espace :

string[] b = {"Bonjour","monsieur"};

string c = String.Join( " ", b);

MessageBox.Show(c);

.IndexOf .LastIndexOf :

Indique le numéro du caractère, la position (la première occurrence) d'une chaîne à chercher dans une autre. LastIndexOf commence par la fin.

string b = "Bonjour monsieur";

int pos = b.IndexOf( "our");

MessageBox.Show(pos.ToString());

Se souvenir : le premier caractère est en position 0 en NET.
.LastIndexOf retourne la dernière occurrence.
Si la chaîne n'est pas trouvée, renvoie -1.

On peut ajouter un paramètre indiquant que la recherche doit débuter à partir du caractère de position n.

int pos = b.IndexOf( " ",3 );

La position de départ de recherche peut être ensuite celle de la première occurrence pour retrouver la seconde occurrence.
On peut rajouter un troisième paramètre pour indiquer dans combien de caractères rechercher.

On peut rechercher un caractère :

int pos = b.IndexOf( 'c',3 );

.IndexOfAny .LastIndexOfAny :

Indique la position (la première occurrence) de tout caractère d'un tableau dans une chaîne avec en plus la possibilité d'indiquer la position de départ. Retourne -1 si les caractères ne sont pas trouvés.

string b = "Bonjour monsieur,";

int pos = b.IndexOfAny(new char[] { '@', '.', ',', '!' });

MessageBox.Show(pos.ToString());

.Compare :

Compare deux chaînes :

Il existe == et != qui permet de voir si deux chaînes sont égales ou différentes.
Par contre les autres opérateurs mathématiques de comparaison (supérieur, inférieur…) ne sont pas supportés par les chaînes.
Ainsi on ne peut pas utiliser a > b.
Il faut donc utiliser Compare.
Comparer un caractère veut dire voir s'il est avant ou après dans l'ordre alphabétique ; dans la culture en cours.

string a = "Hello";
string b = "Bonjour monsieur,";

int c = String.Compare(a, b);

MessageBox.Show(c.ToString()); // retourne '1'

Retourne un entier égal à :
-1 si a inférieur à b.
0 si a=b
1 si a supérieur à b

On peut comparer des sous chaînes et indiquer la sensibilité à la casse :

int c = String.Compare( a, 2, b, 3, 4, StringComparison.CurrentCultureIgnoreCase);

Paramètres : chaîne a, pos dans a, chaîne b, pos dans b, nombre de car à comparer, option de recherche : la majuscule et la minuscule d'un caractère sont considérées comme égales.

On peut imposer une autre culture (où l'ordre des caractères est différent) et ajouter des options de recherche :

int c = String.Compare(a, b, new CultureInfo("en-US"), CompareOptions.IgnoreCase)

'String.CompareOrdinal' fait une comparaison sur le code Unicode des caractères ; ici pas de culture ni d'option IgnoreCase, mais c'est plus rapide.

Pour être complet, on peut aussi utiliser CompareTo c'est une méthode d'instance :
Une string est comparée à un objet.

int cmpVal = str1.CompareTo(MyObject);

Respecte la casse et est spécifique à la culture.

.Equals

Permet de comparer deux chaînes. Retourne true, un bool, si elles sont égales.

string a = "Hello";
string b = "hello";

bool c = String.Equals(a, b, StringComparison.InvariantCultureIgnoreCase);

MessageBox.Show(c.ToString());

On peut utiliser a==b pour comparer plus simplement deux chaînes mais on n'a pas possibilité de choisir des options de comparaison comme ci-dessus.

.Contains :

Permet de savoir si une chaîne apparaît dans une autre :

string a = "Hello word";
string b = "hello";
bool c = a.Contains(b);

.Substring :

Extrait une partie d'une chaîne.

Le premier paramètre indique la position de départ ; le second, le nombre de caractères à extraire.

string a = "Hello word";
string c = a.Substring(2,2);

Exercice 1 : comment obtenir les quatre caractères de droite :

string a = "Hello word";

string c = a.Substring(a.Length-4);

MessageBox.Show(c);

Ici on omet le second paramètre, la longueur de la sous-chaîne va jusqu'a la fin de la chaîne.

Exercice 2 : comment obtenir les trois caractères de gauche :

string a = "Hello word";

string c = a.Substring(0,3);

MessageBox.Show(c);

On peut créer des chaînes avec la classe String :

string d = new string('o', 15);
MessageBox.Show(d);  //affiche 'ooooooooooooooo'

.PadRight :

Aligne les caractères de cette chaîne à gauche et remplit à droite en ajoutant un caractère Unicode spécifié pour une longueur totale spécifiée.

string a = "Hello word";

string c = a.PadRight(14,'.');

MessageBox.Show(c); // affiche 'Hello word....'

PadLeft fait l'inverse.

.StartsWith() et EndsWith() :

Permettent de tester si une string commence ou se termine par une string, retourne true ou false.

Tester si la string s commence par "abc" :

string a = "Hello word";

bool b = a.StartsWith("He");

MessageBox.Show(b.ToString());

On peut ajouter un argument gérant la culture ou la casse.

Les variables Char contiennent un caractère et un seul, un caractère est stocké sous la forme d'un nombre de 16 bits (deux octets) non signé dont les valeurs sont comprises entre 0 et 65 535. Chaque nombre représente un seul caractère Unicode.

Déclarons une variable c de type Char et initialisons la avec le caractère « 5 » (notez que pour indiquer que c'est un caractère on utilise ' ').

Char c = '5';
if (Char.IsDigit(c))
textBlock1.Text = "C'est un chiffre";

Ensuite on a testé si "c" était un chiffre (Char.IsDigit est une méthode static, c'est une méthode de la classe Char).

ToCharArray : Permet de passer une string dans un tableau de Char :

string maString  = "abcdefghijklmnop";
Char[] maArray = maString.ToCharArray();

La variable maArray contient à présent un tableau composé de Char, chacun représentant un caractère de maString.

Vous pouvez extraire un caractère particulier du tableau de caractères en faisant référence à l'index de ce caractère puis en le transformant en string grâce à ToString() :

textBlock1.Text = maArray[2].ToString();

On peut aussi très simplement lire un caractère à une certaine position dans une string :

string maString  = "abcdefghijklmnop";
Char ch = maString[0];

Utilisez les méthodes IsControl, IsDigit, IsHighSurrogate, IsLetter, IsLetterOrDigit, IsLower, IsLowSurrogate, IsNumber, IsPunctuation, IsSeparator, IsSurrogate, IsSurrogatePair, IsSymbol, IsUpper et IsWhiteSpace pour déterminer si un caractère est dans une catégorie Unicode particulière, comme un chiffre, une lettre, un signe de ponctuation, un caractère de contrôle, etc.

Char monChar='f';
bool test= Char.IsLetter(monChar);
bool test1 = Char.IsControl(monChar);
bool test2 = Char.IsDigit(monChar);
bool test3 = Char.IsLetterOrDigit(monChar);
bool test4 = Char.IsLower(monChar);
bool test5 = Char.IsNumber(monChar);
bool test6 = Char.IsPunctuation(monChar);
bool test7 = Char.IsSeparator(monChar);
bool test8 = Char.IsSymbol(monChar);
bool test9 = Char.IsUpper(monChar);
bool test10 = Char.IsWhiteSpace(monChar);

StringBuilder.
Une string est immutable (en anglais) , immuable en vrai français. Cela se dit d'un objet dont les propriétés sont définies à la création de l'objet puis ne peuvent plus changer durant la vie de l'objet.
C'est plus simple pour la gestion de la mémoire et pour le multithreading (comme la chaîne ne change pas tous les threads y ont accès).

Pourtant quand on utilise les méthodes de la Classe string (comme ToUpper) ou la concaténation de chaînes, on a l'impression que la string est modifiée. C'est faux : la chaîne d'entrée restera toujours à la même valeur alors que la valeur de retour sera une nouvelle chaîne traitée à partir de la première mais prenant le nom de la première.
Si j'ajoute 1000 fois un caractère à une chaîne, il y a création de 1000 chaînes ! En terme de performance c'est déplorable.

Dans la programmation courante (une concaténation, un passage en majuscule), il y a peu d'incidence ; mais si on concatène 1000 chaînes, il est préférable d'utiliser un StringBuilder qui lui est mutable.

using System.Text;

StringBuilder stringb = new StringBuilder();
for (int i = 0; i < 1000; i++)
stringb.Append("T");

Pour concaténer on utilise Append.
On peut utiliser Remove, Replace, Length, Clear, Insert, ToString.

Exemple avec Replace :

stringb.Replace('!', 'o');

Autre exemple :

float value = 123458.44f;

//Constructeur initialisant la StringBuilder avec une String
System.Text.StringBuilder stringb = new System.Text.StringBuilder("....");

//Append successif ajoutant une chaîne correspondant à une valeur numérique puis 5 étoiles.
stringb.Append(value).Append('*', 5);

MessageBox.Show ( sb.ToString() );

On peut aussi lire ou mettre un caractère à une position particulière de la StringBuilder :

char ch = stringb[5];

III-E - Variables numériques

Il y a plusieurs types de variable numérique :
les types en C# et leur équivalent en .NET.

Les entiers ou intégraux

Pour une variable entière il n'y a pas de possibilité de virgule ! Attention, une division de deux entiers donne un entier.
Pour le mathématicien, cela correspondent aux nombres entiers relatifs : entiers positifs ou négatifs et 0 (..-3 -2 -1 0 1 2 3…), mais avec une limite.

int en .NET c'est Int32 (integer (nombre entier)) occupe 4 octets, plage : [-2^31 ; 2^31-1]

uint en .NET c'est UInt32 (unsigned integer (nombre entier non signé)) occupe 4 octets, plage : [0 ; 2^32-1]

long en .NET c'est Int64 (nombre entier long) occupe 8 octets, plage : [-2^63 ; 2^63-1]

ulong en .NET c'est UInt64 (unsigned long (nombre entier long non signé)) occupe 8 octets, plage : [0 ; 2^64-1]

sbyte en .NET c'est SByte (signed byte (octet signé)) occupe 1 octet, plage : [-2^7 ; 2^7-1]

byte en .NET c'est Byte (octet) occupe 1 octet, plage : [0 ; 2^8-1]

short en .NET c'est Int16 (nombre entier court) occupe 2 octets, plage : [-2^15 ; 2^15-1]

ushort en .NET c'est UInt16 (unsigned short) (nombre entier court non signé) occupe 2 octets, plage : [0 ; 2^16-1]

Les nombres en virgule flottante ou réels ou décimaux

Un réel peut avoir une partie fractionnaire : 1,454 est un réel.
Pour le mathématicien, les Single, Double… correspondent aux nombres réels ou fractionnaires, mais avec des limites ( sur la précision et le fait qu'ils ne sont pas infinis).

float en .NET c'est Single (flottant )(nombre réel)occupe 4 octets, plage : ±1,5*10^-45 à ±3,4*10^+38 (7 chiffres de précision).

double en .NET c'est Double (double flottant )(nombre réel)occupe 8 octets, plage : ±5,0*10^-324 à ±1,7*10^+308 (15 à 16 chiffres de précision)

decimal :
Le type decimal prend en charge jusqu'à 29 chiffres significatifs et peut représenter des valeurs jusqu'à 7,9228 x 10^28. Ce type de données est particulièrement adapté aux calculs (par exemple financiers) qui exigent un grand nombre de chiffres, mais qui ne peuvent pas tolérer les erreurs d'arrondi. Par contre les calculs sont plus lents.

decimal en .NET c'est Decimal (nombre décimal) occupe 16 octets, plage : ±1,0*10^-28 à ±7,9*10^28 (28 à 29 chiffres significatifs)

On utilise les entiers quand c'est possible (c'est plus rapide), pour une variable de boucle par exemple.
On utilise les decimal quand il doit y avoir une précision parfaite (calcul financier) mais les calculs sont plus longs.
float et double sont largement utilisés quand on a besoin de nombre réels.

Attention, les variables numériques en informatique ont des limites et ne peuvent pas contenir de nombre infiniment grand ou infiniment petit car ils sont stockées dans un nombre d'octet bien précis. Il y a une limite maximum et une limite minimum : un int est par exemple codé sur 32 bits ce qui fait qu'il peut varier de -2147483648 à 2147483647. Si on utilise une valeur trop grande, une erreur se produit.

On peut déclarer, instancier une variable et donner une valeur a une variable numérique :

int myInt=12;

Le nombre 12 se nomme un littéral car il donne une valeur immédiatement, en dur.
Par défaut :
12 est un int
1.2 est un double
On remarque que dans un littéral le caractère décimal est toujours le point.

Mais comment forcer le littéral a être d'un type précis ?

Un nombre entier est considéré comme un int s'il tient dans la plage de valeurs d'un int (exemple : -135), et comme un long s'il ne tient pas dans la plage de valeurs d'un int mais dans celle d'un long (exemple : 5452862578).
Un u à la fin symbolise un uint. Exemple : 3u (ou 3U).
Un l à la fin symbolise un long. Exemple : 5l (ou 5L).
Un ul à la fin symbolise un ulong. Exemple : 1ul (ou 1UL).
Un nombre écrit avec une partie décimale, ou avec un d à la fin est un double. Exemple : 2.0 ou 2d (ou 2D).
Un f à la fin symbolise un float. Exemple : 7.0f ou 7f (ou 7F).
Un m à la fin symbolise un décimal. Exemple : 6.0m ou 6m (ou 6M).

Quand on a déclaré une variable, il faut lui assigner une valeur, sinon on ne peut pas l'utiliser :

int i;
if (i== null)     // n'est pas accepté

Ces types de variable numérique sont des types « valeur » car en fait se sont des structures (et non des classes). On verra cela plus loin.

Voyons les opérateurs :

On peut utiliser +, -, /, * (multiplier).

int myint=12;
int mysecondint;
mysecondeint= myint+ 3;

'%' donne le reste d'une division (modulo) :

int myInt = 21 % 10;

MyInt=1

Dans les conditions, on utilise les opérateurs :

égal, différent : ==, != supérieur, inférieur, supérieur ou égal, inférieur ou égal : > , <, >=, <=

Exemple : si A différent de B

if (A!=B)
{}

Écriture compacte, incrémentation :

i += 5; //à la place de i = i + 5;.
i -= 5; //à la place de i = i - 5;
i *= 5; //à la place de i = i * 5;
i /= 5; //à la place de i = i / 5;
//Exemple :
i += 5

// Pour ajouter 1 on incrémente, pour retirer 1 on décremente
//On peut post incrémenter ou post décrémenter
i++; //à la place de i = i +1;
i--; //à la place de i = i -1;
//On peut pré incrémenter ou pré décrémenter
++i; //à la place de i = i +1;
--i; //à la place de i = i -1;

En pré incrémentation l'expression ++i représente la valeur de l'opérande après son incrémentation ; la post incrémentation, la valeur d'avant.

//Exemple :
int i= 2;
int j= i++  //i=3 mais j=2 car l'incrémentation a été effectuée après l'assignation

Sur les intégraux, on peut utiliser des opérations sur les bits :

'>>' décalage à droite.<br/>
'<<' décalage à gauche.<br/>
'~'  inversion de bits.

Priorité des opérateurs :

S'il y a plusieurs opérateurs, * et / ont la priorité la plus forte (ils sont effectués avant) puis + et -.
Cela veut dire que C# effectue les multiplications et divisions puis les additions et soustractions.
S'il y a plusieurs opérateurs de même priorité, l'ordre des calculs se fait de gauche à droite.
Pour éviter toute faute d'interprétation utiliser des parenthèses :

double delta= B/(A-C);
//Et pas :
double delta= B/A-C;

Les variables numériques peuvent contenir des valeurs qui ne sont pas infinies, il y a une limite.
On peut connaître la valeur maximum grâce à MaxValue et la valeur minimum avec MinValue.

MessageBox.Show(double.MaxValue.ToString());

Affiche la plus grande valeur possible pour un double.

Dépassement de capacité, division par zéro :

Le dépassement de capacité d'entiers (si par exemple une multiplication de deux entiers donne un nombre plus grand que le plus grand des entiers) lève une exception OverflowException ou ignore les bits les plus significatifs du résultat (si pas de vérification).
La division d'entiers par zéro lève toujours une exception DivideByZeroException.
Un dépassement de capacité ou une division par zéro en virgule flottante ne lève jamais d'exception, car les types virgule flottante gèrent la représentation de l'infini et NaN (Not a Number).
Le dépassement de capacité décimal lève toujours une exception OverflowException. La division décimale par zéro lève toujours une exception DivideByZeroException.

Par défaut il n'y a pas de vérification de dépassement de capacité. Pour l'activer, passer par les propriétés du projet (menu Projet puis Propriétés).

Pour l'activer sur une portion de code seulement, il faut utiliser la directive Checked dans le code.

checked
{
int x = a + b;
}

Pour une variable en virgule flottante, il existe donc les valeurs NegativeInfinity et PositiveInfinity.
Pour une variable en virgule flottante, si on la divise par zéro on obtient NaN (Not a Number).

La classe Math : fournit des constantes et des méthodes statiques pour des fonctions trigonométriques, logarithmiques et d'autres fonctions mathématiques courantes. On a bien dit méthodes statiques ; on utilisera donc la syntaxe : Math.Pow() et pas mynumber.Pow.

Max retourne le plus grand des deux nombres :

double number1=12.3;
double number2=6.7;
double result = Math.Max(number1, number2);

Min retourne le plus petit des deux nombres :

double number1=12.3;
double number2=6.7;
double result = Math.Min(number1, number2);

Abs retourne la valeur absolue :

double number1=12.3;
double result = Math.Abs(number1);

Sign : retourne un int indiquant le signe (-1 si négatif, 1 si positif).

Ceiling : retourne la plus petite valeur entière supérieure ou égale.

double number1=12.6;
double result = Math.Ceiling(number1); // retourne 13

Floor : retourne le plus grand entier égal ou inférieur, arrondi à l'entier inférieur le plus proche en allant vers l'infini négatif.
(N=1.7 R=1)

Round retourne le nombre entier le plus proche.
' Si N=1.7 Round() retourne R=2
' Si N=1.2 Round() retourne R=1
' Si N=1.5 Round() retourne R=2
On peut rajouter un paramètre qui arrondit à x décimale(s) :

result = Math.Round(number, NbDecimal);

NbDecimal est un entier

Convient bien pour arrondir les valeurs financières avec Math.Round (number,2).

Sqrt permet d'avoir la racine carrée :

result = Math.Sqrt(number1);

Pow permet d'élever un nombre x à la puissance y :

result = Math.Pow(x, y);

PI donne le nombre pi.

decimal perimetre= 2* Math.PI * Rayon;

Log permet d'avoir le Log népérien, Log10 permet d'avoir le Log de 10 :

result = Math.Log(number1);

Il existe aussi Exp : exponentiel (e à la puissance spécifiée).

Enfin on a tous les méthodes trigonométriques :
Sin Cos Tan ASin ACos ATan (retourne l'angle à partir du Sin, Cos, Tan), Sinh Cosh, Tanh (hyperbolique).
Noter que les angles sont en radians. (Rappel : 2pi=360° ; Angle en radians= (2pi/360)*Angle en degrés). .

double d= Math.Cos(Math.PI);
MessageBox.Show(d.ToString()); //affiche -1

Problème de précision :

on a vu qu'avec les variables en virgule flottante, comme les float par exemple, certaines valeurs ou calculs sont représentés avec une certaine approximation, infime mais réelle.
Souvent le calcul est exact mais parfois (rarement en pratique courante) on peut avoir une infime manque de précision. Voici un exemple : on a 0,0001, avec une boucle on l'additionne dix milles fois :

int i;
float k=0;
float j = 0.0001F;

for (i = 1 ; i <10001; i++)    k = k + j;

MessageBox.Show(k.ToString()); //' Affiche 1.000054

Decimal k1=0;
Decimal j1  = 0.0001m;

for (i = 1 ; i <10001; i++)    k1 = k1 + j1;

MessageBox.Show(k1.ToString()); // Affiche 1

Avec les decimal le résultat est 1.
Avec les float le résultat est 1.000054

Avec les entiers la précision est parfaite.
Avec les decimal aussi précision parfaite, mais les calculs sont un peu plus lents.
Avec les float, les double la précision parfois n'est pas parfaite.

III-F - Autres types de variable : booléen, objet

Une variable de type bool contient une valeur booléenne :true ou false (vrai ou faux pour les sous doués en anglais !).

Exemple :

bool myBoolean;

myBoolean = true;

Après déclaration un boolean a la valeur par défaut false.

On peut créer une variable de type object, on peut y mettre ce qu'on veut : un int une string…

onject o;  //Déclaration
o = new object() //Instanciation

//ou
object o = new object();
o = 2;
o = "toto";

Une bonne règle est d'utiliser des variables le plus typées possible plutôt que des objets.
Un exemple : une variable qui doit contenir un objet visuel peut être non typée (Object), moyennement typée (Control) ou très typée (Button par exemple).
Plus la variable sera typée plus le code sera solide, plus on évitera les erreurs de programmation.

Souvent le paramètre sender d'une fonction évènement retourne un objet ; il faut le convertir dans le type de départ.

III-G - Nullable

Comment signaler que le contenu d'une variable ne correspond à rien ?

Les types valeur peuvent être étendus afin d'accepter une valeur normale habituelle et une valeur null. On peut déclarer un type Nullable de plusieurs manières :

Nullable<int> i=1;    // Classe Nullable
//ou
int? ii=null;         // c'est '?' qui indique que c'est ou nullable

On a déclaré un Nullable entier nommé i et un autre nommé ii.
Un entier nullable (i ou ii) peut prendre toutes les valeurs d'un entier (1, 2, 3…) et aussi la valeur null.
Il a deux propriétés :
HasValue (qui est égal à true quand Value contient un entier, et false quand le nullable= null),
Value contenant un entier. Si on tente d'utiliser Value quand le nullable= null, il y a levée d'une exception.

Il faut toujours bien initialiser une variable même nullable :

int? i;   // déclaré , non initialisé
i= 2;     // i=2 HasValue=True i.Value= 2
i= null;  // i=null HasValue=false i.Value non lisible

Si on essaie de lire la valeur d'une variable non initialisée, il y a erreur (il ne faut pas penser qu'elle contient null pour la raison qu'elle n'a pas été initialisée).

Autre exemple : déclarons un bool nullable :

bool? myBool= true;

myBool accepte les valeurs true, false et null.

Cela a de l'intérêt quand on travaille avec les bases de données qui ont des champs qui contiennent un Null et avec Linq.
Cela est aussi intéressant quand on veut indiquer qu'une variable n'a pas encore de valeur. Avant pour une variable indiquant un poids, quand le poids n'était pas renseigné on lui donnait la valeur -1. Maintenant on utilise un nullable et on lui donne la valeur null.

III-H - Tableau

Les tableaux permettent de regrouper des données de même type. Ils vous permettent de faire référence à un ensemble de variables par le même nom et d'utiliser un numéro, appelé index ou indice, pour les distinguer.

Le nombre d'éléments est décidé au départ. Sa longueur est donc fixe. Il n'est plus possible de modifier le nombre d'éléments par la suite.
Les tableaux sont des types référence.

Voici un tableau nommé c contenant trois éléments :
c[0]=2
c[1]=3
c[2]=15

On note que le premier élément du tableau a l'index 0.

On va déclarer un tableau nommé c (sans donner le nombre d'élément), l'initialiser à trois éléments (avec new) puis donner une valeur à chaque élément :

int[] c;       //déclaration de c; int[] c'est un tableau d'entiers, non encore alloué
c=new int[3];  //instanciation, allocation en mémoire de 3 éléments
c[0]=2;        //affectation d'une valeur au premier élément
c[1]=3;
c[2]=15;

On voit que pour un tableau à trois éléments, l'index va de 0 à 2 (de 0 à n-1 si n est le nombre d'éléments).

On peut grouper les lignes 1 et 2 :

int[] c = new int[3];
c[0]=2;
c[1]=3;
c[2]=15;

On peut grouper les lignes 2 et suivantes :

int[] c ;
c = new int[3] {2, 3, 15};

On peut déclarer et initialiser les valeurs, dans ce cas inutile de donner le nombre d'éléments :

int[] c = {2, 3, 15};

Exemple avec des tableaux de float et de strings :

float[] f ={2.5f, 3.8f, 1.5f};
string[] mystring ={"Paul", "Louis", "Jean"};

On remarque qu'on a ajouté un f aux valeurs de la première ligne pour forcer les valeurs à être des floats.

On peut ensuite modifier ou lire un élément :

int[] c {2, 3, 15};
c[0]=45;                //modification de la valeur du premier élément
int MaVariable= c[2];  //on déclare une variable Mavariable on y affecte la valeur de c[2]

Pour un tableau déclaré et alloué (après int[] c =new int [3];) les éléments contiennent la valeur par défaut du type (null pour un type référence, 0 pour un int par exemple).
Si on utilise un numéro d'index supérieur au plus grand index possible cela déclenche une IndexOutOfRangeException.

Travail sur la référence d'un tableau :

// premier tableau
string[] MonTableau ={"Paul", "Louis", "Jean"};

// second tableau
string[] AutreTableau;

//copie
AutreTableau= Montableau;

Maintenant AutreTableau contient trois éléments :"Paul", "Louis", "Jean" car les deux tableaux pointent sur la même zone mémoire (par référence oblige !).

Pour parcourir un tableau : on peut utiliser une boucle foreach.

string[] MonTableau ={"Paul", "Louis", "Jean"};
foreach (string s in Montableau)
{
System.Diagnostics.Debug.WriteLine(s);
}

ou une boucle for.

string[] MonTableau ={"Paul", "Louis", "Jean"};
for (int i = 0; i < t.Length; i++)
{
System.Diagnostics.Debug.WriteLine(t[i]);
}

On peut créer des tableaux à deux dimensions :

double[,] myArray = new double[,] { {0.5, 0.2}, {1.4, 1.0} };

myArray[0, 0] vaut 0,5
myArray[1, 1] vaut 1,0

On peut créer des tableaux de tableaux.

//on crée un grand tableau qui contient deux tableaux
int[][] myTableudeTableau = new int[2][];
//le premier tableau contient 5 éléments
myTableaudeTableau[0] = new int[5];
//on donne une valeur au deuxième élément du premier tableau
myTableaudeTableau[0][1] = 32;

Les tableaux multidimensionnels ne sont pas très bons en terme de performance : il est préférable d'utiliser des tableaux de tableaux.

Les tableaux héritent de la classe Array dont ils peuvent utiliser les méthodes :
La méthode static Clear efface les éléments d'un tableau, les deux derniers paramètres indiquent l'index du premier élément à effacer et le nombre d'éléments à effacer.
Pour effacer tous les éléments.

Array.Clear(numbers,0, numbers.Length);

Ne supprime pas d'élément du tableau (puisque la longueur est fixe), mais ça remplace chaque élément par la valeur par défaut.

On peut trier :

Array.Sort(numbers);

Il est possible d'ajouter un paramètre pour la méthode de comparaison.

BinarySearch permet une recherche dans le tableau, il retourne l'index de l'élément trouvé.
Et renvoie une valeur négative si l'élément n'est pas trouvé.
C'est une recherche dichotomique, ça suppose donc que le tableau soit déjà trié ; sinon ça va renvoyer n'importe quoi.

string[] mots = { "toto", "lili", "tata" };
int c =Array.BinarySearch(mots, "lili");
textBlock1.Text = c.ToString();

Il existe aussi Copy, IndexOf, LastIndexOf, Resize, Reverse.

Sur les tableaux, on peut utiliser les méthodes d'extension de Linq (voir le chapitre sur Linq, il y a des exemples sur des collections mais c'est pareil).

III-I - Conversion de variable, caractère décimal

On a souvent besoin de convertir le contenu d'une variable d'un certain type dans un autre type.
Par exemple : on peut faire des calculs avec les variables numériques mais on ne peut afficher que des string ; aussi il faut convertir souvent.

Il existe des conversions implicites (elles se font automatiquement sans avoir à l'indiquer).
Les conversions implicites, pour les variables numériques, sont celles entre les différentes gammes d'entiers et nombres en virgule flottante vers un type de plage plus grande. Aucune conversion implicite n'a lieu entre booléens et entiers, entre membres d'énumération et entiers.

int number = 4154613;
long bigNumber = number;

Ici on affecte un int a un long, l'int est transformé en long automatiquement ; cela est accepté par le compilateur car la plage des long est plus grande que celle des int, donc forcement l'int va pouvoir être transformé en long sans perte.

byte, short, int, long peuvent être transformés en long, float, double, decimal. Un double ne peut pas être transformé implicitement en int !

Certaines conversions doivent être indiquée clairement, on parle de conversion explicite :

Il est nécessaire parfois de convertir un type numérique dans un autre type (ayant une plage moins large). On parle de Cast.
Pour effectuer un cast (conversion), il faut spécifier le type désiré entre parenthèses devant la valeur ou la variable à convertir.

On veut caster un double en entier par exemple.

double Number = 12;
int NumberInt;
NumberInt= (int) Number;

(int) indique dans quel type convertir.

Parse et TryParse sont des méthodes permettant aussi de convertir une chaîne de caractère vers tous type numérique.

int.Parse convertira une chaîne en entier.

On a aussi souvent besoin de convertir une string en nombre : une string à été saisie au clavier, elle est dans un TextBox, je veux la transformer en nombre pour faire des calculs. double.Parse convertira en double.

Le séparateur décimal est celui de la culture en cours (',' pour le français).

Conversion une string en entier :

int i=int.Parse("12");

On peut être dans une culture française, mais vouloir utiliser le séparateur anglais ; dans ce cas on va ajouter un paramètre : la culture « en-US ».

string s ="1.2";
Double d = Double.Parse(s, new System.Globalization.CultureInfo("en-US"));

S'il y a impossibilité de convertir, il y a levée d'une exception et l'application s'arrête. Pour éviter cela on utilise TryParse qui retourne un bool qui prend la valeur false si la conversion échoue (et ne lève pas d'exception).
out indique la variable qui recevra la valeur convertie (out indique une variable de sortie qui est déclarée mais peut ne pas être initialisée).

Cast de texte qui est dans le TextBox TextQuantité en double, on retrouve le résultat de la conversion dans le double nommé Quantité ; ConversionIsOk= false si la conversion échoue :

Double Quantite ;
bool ConversionIsOk = double.TryParse(TextQuantite.Text, out Quantite);

Texte en date

DateTime MaDate ;
bool conversionIsOk = DateTime.TryParse(TextDate.Text, out MaDate);

On peut ainsi convertir une string en int, decimal, float, DateTime.

Là aussi on peut ajouter la culture.

Double Number ;
bool conversionIsOk = Double.TryParse(s, NumberStyle.Number ,new CultureInfo("en-US"), out Number);

On utilise souvent la méthode ToString().

Par exemple, j'ai une variable de type int (entier) ; pour l'afficher, je dois la convertir en string (on peut afficher une chaîne de caractères mais pas une valeur numérique). Pour cela j'ai la méthode ToString :

int entier=12;
string chaîneaAfficher;
chaîneaAfficher= entier.ToString();

On peut ajouter en paramètre le format de la string.
Ici on va afficher deux caractères après la virgule.

float Z= 12.4556;
string   mastring =Z.ToString("0.00")
// string= "12,45"

Enfin il y a la classe Convert qui permet de convertir les types primitifs :

int myint = 12;
double mydouble = Convert.ToDouble(myint);

Convert.ToDouble convertit en double ; il existe ToInt16, ToInt32, ToInt64, ToChar, ToSingle, ToDecimal, ToByte…

Pour convertir une string en Int32 :

int a = Convert.ToInt32(string);

Exemple pratique :
l'utilisateur saisit "12" dans un TextBox c'est une string, il faut faire un calcul (numérique) puis afficher le résultat dans un TextBlock.

double number;

//On convertit la string qui est dans Montextbox.text en double
bool res = double.TryParse(Montextbox.Text, out number);
//On fait le calcul
number= number *number;
// On convertit en string pour l'affecter à Montextblock.Text
Montextblock.Text= number.ToString();

Le séparateur décimal est celui de la culture en cours :
',' pour fr.
'.' pour en.

Où peut-on trouver ce séparateur ?

string separateur;
separateur=Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator;

ToString, TryParse… utilisent donc le séparateur de la culture en cours.

Par contre pour les littéraux (valeur tapée dans le code), le séparateur, c'est le point :

float mon float= 1.2;

III-J - Type Valeur et Type Référence

Un type de données est un type valeur s'il contient des données dans l'espace qui lui est alloué en mémoire. Un type référence contient une adresse vers un autre emplacement en mémoire contenant les données.

Type valeur :
La variable valeur contient directement la valeur de la variable.
Sont des variables par valeur :

les types numériques de base (int, double, décimal…) ;
les Booleans, Char, Date ;
les structures ;
les énumérations.

On remarque que les types numériques de base (int, double, decimal…) sont des structures (et non des classes) et donc bien par valeur.
On parle aussi de type primitif directement pris en charge par le compilateur et avec une syntaxe plus ou moins simplifiée.

Type référence :

La variable référence contient l'adresse où est située le contenu de la variable.
Sont des variables référence :

Les classes.
Les tableaux.
Les string.

Que se passe t-il si on affecte une variable à une autre ? Travaillons sur des variables type valeur (structure) puis référence (classe).

/////Variable par valeur

//Structure Adresse contenant un membre 'Ville'
struct Adresse
{

public string Ville;

}

// variables de type Adresse
Adresse MonAdresseA= new Adresse();
MonAdresseA.Ville = "Paris";

Adresse MonAdresseB= new Adresse();
MonAdresseB.Ville = "Lyon";

//J'affecte la valeur de MonAdresseB à MonAdresseA
MonAdresseA= MonAdresseB;

MessageBox.Show(MonAdresseA.Ville);//Affiche "Lyon"

// Je modifie un membre de MonAdresseB ; MonAdresseA n'est pas modifiée
//car c'est une copie, et non le même objet
MonAdresseB.Ville = "Lille";
MessageBox.Show(MonAdresseA.Ville);//Affiche "Lyon"

//////Variable par référence
//Classe Personne ayant une propriété Nom
public class Personne
{
private string _Nom;

public string Nom
{
get
{
return _Nom;
}
set
{
if (value != _Nom)
{
_Nom = value;

}
}
}
}

//variables de type Personne,
Personne A = new Personne();
A.Nom = "Dupont";

Personne B = new Personne();
B.Nom = "Durand";

A = B;      // A et B font référence à l'objet dont le nom= Durand
MessageBox.Show(A.Nom); //Affiche "Durand"

//je modifie B.Nom
B.Nom = "Jean";
//A.Nom et B.Nom ont même valeur .
MessageBox.Show(A.Nom); // Affiche "'Jean"

Les variables par valeur ou par référence ne réagissent pas de la même manière.
Si le type de variable est par valeur (valable pour les entiers, les longs… les structures…), chaque variable ayant sa valeur, si j'assigne la seconde à la première et que je modifie un membre de la seconde, le membre de la première n'est pas modifiée.
Si le type de variable est par référence (valable pour les tableaux, les classes, les string…), chaque variable est définie par sa référence (son lieu physique) ; assigner la seconde à la première fait qu'elles ont même référence : elles pointent sur le même objet. Si on modifie une propriété de la seconde, la propriété de la première est modifiée.

Que se passe t-il si on utilise == ? (Si l'opérateur == n'a pas été redéfini pour le type.)

Pour une variable par valeur, c'est le contenu, la valeur qui va être comparée.

Pour une variable par référence, c'est la réference qui va être comparée. Les objets concernés seront égaux s'ils pointent sur le même objet.

Comportement particulier des string :

Bien que string soit un type référence, les opérateurs d'égalité (== et !=) sont définis pour comparer les valeurs, pas les références. Le test d'égalité des chaînes est ainsi plus intuitif . Par exemple :

string a = "hello";
string b = "h";
b += "ello";
System.Diagnostics.Debug.WriteLine((a == b).ToString()); //Affiche true (comparaison des valeurs)
System.Diagnostics.Debug.WriteLine( ((object)a == (object)b).ToString());// Affiche false (comparaison des références)

En résumé :
(différence entre == et Equals.)

//Variable par valeur
int int1 = 12;
int int2 = 12;
MessageBox.Show((int1 == int2).ToString());// true car compare les valeurs
MessageBox.Show((int1.Equals(int2)).ToString());// true car compare les valeurs

//Variable par référence
object o1= 12;
object o2 = 12;
MessageBox.Show((o1 == o2).ToString());// false car compare les références
MessageBox.Show((o1.Equals( o2)).ToString());// true car compare les valeurs

//Variable string
string s1 = "12";
string s2 = "12";
MessageBox.Show((s1 == s2).ToString());// true car compare les valeurs
MessageBox.Show((s1.Equals(s2)).ToString());// true car compare les valeurs
MessageBox.Show((object)s1 == (object)s2).ToString());// false car compare les références

//Variable objet contenant une string
object ob1 = "12";
object ob2 = "12";

MessageBox.Show((ob1.Equals(ob2)).ToString());// true car compare les valeurs
MessageBox.Show((ob1 == ob2).ToString());// true car compare les références et
// des string littérales identiques dans le code font référence à la même instance de string

//mais
// Par contre, si une au moins des chaines n'est pas littérale mais construite dynamiquement,
// même en ayant la même valeur, ce n'est plus la même référence
object ob1 = "12";
object ob2 = "123".Substring(0, 2); // c'est la même valeur, mais ce n'est plus la même instance de string
MessageBox.Show((ob1 == ob2).ToString());// false car compare les références
MessageBox.Show((ob1.Equals(ob2)).ToString());// true car compare les valeurs

//puisque les variables sont de type object, le compilateur ne sait pas que ce sont des strings,
//et utilise l'opérateur == par défaut, qui compare les références

III-K - Bool, if, et , ou, switch, goto

On a parfois besoin de savoir si une assertion est vraie ou fausse(true ou false).

Pour stocker une information de ce type, on utilise une variable de type booléen. Une variable de ce type ne peut contenir que true ou false.
Éventuellement on peut créer un bool Nullable, il pourra prendre aussi la valeur null.

Le terme booléen vient de l'algèbre de Boole, cette algèbre ne travaille que sur les valeurs 1 ou 0 (true ou false).

Soit myBoolean une variable booléenne :

bool myBoolean;

On peut écrire :

myBoolean = true;

On peut aussi tester cette variable :

if (myBoolean == false)
{ }

L'expression après if est évaluée, si elle est vraie ce qui est entre {} est exécuté.
On peut aussi écrire :

if (!myBoolean)
{ }

Autre exemple montrant comment le raisonnement informatique est logique :

if (maValeur==2)
{}

L'expression maValeur==2 est évaluée, si maValeur est effectivement égale à 2, l'expression prend la valeur true (un booléen) ; dans ce cas le programme se poursuit après {.

Condition :

On a vu qu'après un if il y avait une condition qui peut utiliser :

Égale :==

On note bien que si = est l'affectation, == est le signe d'égalité.

Différent :!=

Not :! Négation logique. Il est défini pour bool et retourne true si l'opérande est false et false si l'opérateur est true.

> , <, >=, <=

Exemple, si A différent de B :

if (A!=B)
{}

Il existe aussi les opérateurs logiques :

& ou && veut dire And (Et)
Les deux opérateurs font la même chose à la différence que l'on n'évalue pas la seconde valeur seulement si la première valeur est égale à false. On utilise toujours &&.

| ou|| veut dire Or (ou)
Les deux opérateurs font la même chose à la différence que l'on n'évalue pas la seconde valeur seulement si la première valeur est égale à true. On utilise toujours ||.

^ veut dire Xor (A ou B mais pas les 2).

Exemple : si A compris entre 100 et 300 : a>99 vrai et a<301 vrai

if (A>99 && A<301)
{
B= 8;
}

&& évalue la seconde expression seulementsi la première est vraie

Comment mettre le code après if ?

Avec un bloc d'instruction entre {} :

if (A>99 && A<301)
{
B=2;
C= B+3;
}

Les accolades ne sont pas obligatoires, c'est bien pratique quand il y a une seule instruction après la condition  :

if (A>99 && A<301) B=2;

Mais attention :

if (condition)
A= 2;
B= 3;

L'indentation semble indiquer que B=3 est dans le bloc if, ce qui est faux.
Il est préférable de mettre des accolades.

Avec un else (sinon) exécuté seulement si la condition après if n'est pas égale à true :

if (A>99 && A<301)
{
B=2;
C= B+3;
}
else
{
B=8;
C= B+9;
}

Avec un else if (sinon si), on peut tester des conditions successives :

if (A>99 && A<301)
{
B=2;
C= B+3;
}
else if (A==1)
{
B=8;
C= B+9;
}

Il peut y avoir des if imbriqués. S'il n'y a pas d'accolades le else s'applique au if le plus proche ce qui peut entrainer des erreurs ; bien mettre des accolades :

if (A>99 && A<301)
{
if (B=2)
{C= B+3;}
else
{C= B+5;}
}
else
{
B=8;
C= B+9;
}

Une alternative au if else if else if est le switch.
On parle de commutateur.

switch (level)
{
case 1:
{
// niveau 1
}
break;

case 2:
{
// niveau 2
}
break;

default:
{
// autre niveau
}
break;
}

Les accolades ne sont pas nécessaires.
Il faut bien mettre un break à la fin de chaque case sinon il y a erreur de compilation.
Default est exécuté si la valeur de la variable du switch ne correspond à aucun des cases.

Attention l'expression de switch (level ici) ou la valeur de case doit être bool, string, char, enum, integral, nullable. Un int ou un long, oui mais pas un float un double ou un decimal.

On peut faire des calculs sur une plage de valeur après case ; ici avec des long :

long d = 2;
switch (d)
{
case 3-1:
//case 2
break;
case 5:

break;
}

L'exécution commence à un case qui correspond à la valeur de la variable du switch et continue jusqu'à ce que l'instruction break transfère le contrôle en dehors du corps du switch. Un break un return ou un throw sont nécessaires après chaque bloc case, y compris le dernier bloc (case ou default).
Si plusieurs valeurs doivent exécuter les mêmes instructions faire comme cela (pas de break donc après les 2 premiers case) :

int n = 2;
switch(n)
{
case 1:
case 2:
case 3:
System.Diagnostics.Debug.WriteLine("C'est 1, 2, ou 3.");
break;
default:
System.Diagnostics.Debug.WriteLine("C'est autre chose.");
break;
}

Cas particulier, ? est un opérateur ternaire :
A ? B : C veut dire si A est vrai => B, si A est faux => C.

string IsEgale = (2 == 6) ? "les nombres sont égaux" : "les nombres ne sont pas égaux";

IsEgale= "les nombres ne sont pas égaux"

Il existe une instruction qui permet de sauter d'un endroit du code à un autre :c'est goto.
On saute vers un label indiqué par « : » :

int a= 2;
goto Suite;
a=a +1;
Suite:
a=a+2;

On peut se passer du goto dans la majorité des cas car il casse le code, le rend moins lisible.
Elle ne doit (quasiment) jamais être utilisée.

III-L - Boucle

Comme son nom l'indique, une boucle permet d'effectuer plusieurs fois le code qui est dans la boucle.

Voyons la boucle for ; la boucle tourne en incrémentant un compteur tant qu'une condition est vraie :
Habituellement, on utilise donc une variable dite variable de boucle ; on lui donne une valeur de départ (initialisation) ; à chaque tour on l'incrémente (instruction de fin de boucle) ; quand elle atteint une valeur (condition), on quitte la boucle.

for (initialisation ; condition ;instruction de fin de boucle)
{instructions effectuées tant que condition=true}

Les trois arguments de for sont à l'intérieur d'une parenthèse et séparés par des points-virgules.
Chaque action du for est terminée par un point-virgule.
L'accolade n'est nécessaire que s'il y a plus d'une action.
L'accolade n'est pas suivie de point-virgule.

Exemple : faire tourner une boucle 10 fois (i, la variable de boucle prendra les valeurs 0, 1, 2 […] 8, 9.

for (int i=0; i<10; i++)
{
System.Diagnostics.Debug.WriteLine(i.ToString());
}

On crée une variable de boucle i qu'on initialise à 0 , la boucle tourne tant que i est inférieur à 10, on incrémente i après chaque passage dans la boucle.
Pour chaque tour de boucle, on affiche la valeur de la variable de boucle sur la console.

On peut utiliser la variable de boucle DANS la boucle.
La variable de boucle n'existe que dans la boucle.

L'initialisation, la condition et l'instruction de fin de boucle sont facultatives.
Voici une boucle qui tourne sans arrêts :

for (; ; )
{
//
}

break; permet de sortir de la boucle.
continue; permet d'interrompre immédiatement l'itération en cours et de passer à la suivante.

On peut initialiser plusieurs variables (de même type), et avoir plusieurs instructions de fin de boucle. Le séparateur est l','.

for (int i = 0 ,j=1; i < 10; i++ ,j--)           { }

Ici on a initialisé deux int, notez que le mot-clé int n'est présent qu'une fois. Ils n'existent que dans le corps de la boucle.

L'instruction foreach répète un groupe d'instructions pour chaque élément d'un tableau ou d'une collection.

string[] arr= new string[] {"Jan", "Feb", "Mar"};

foreach (string s in arr)
{
System.Diagnostics.Debug.WriteLine(s);
}

On ne peut pas, dans la majorité des collections, ajouter ou retirer un élément dans une boucle foreach (contrairement à la boucle for).

Voyons la boucle while :
On ne connait pas le nombre de répétitions au départ. On boucle tant que la condition est vérifiée. La boucle peut ne jamais être exécutée.

while (b != 0)
{
x = a%b;
a = b;
b = x;
}

Tant que b différent de 0 on boucle.
On peut mettre un break dans la boucle pour sortir.

On peut aussi utiliser do while qui exécutera au moins une fois le contenu de la boucle car la condition est en fin de boucle.
On boucle jusqu'à ce que la condition devienne fausse.

do
{
// Code qui boucle tant que la condition est satisfaite.
}
while (condition);

III-M - Les génériques

Un type générique (Generic) permet de créer une classe ou une méthode, ayant des types non définis au départ.
Les génériques répondent au besoin de méthodes qui ne diffèrent que par le type de certains de leurs paramètres.
En d'autres termes, les paramètres et variables n'ont pas de type : ce ne sont pas des strings, des int… Ce sont des génériques. Quand on utilise la classe ou la procédure, on indique le type. Là le compilateur compile la méthode en tant que méthode générique, et c'est à l'exécution que la méthode "réelle" avec un type spécifique va être créée.. Les génériques nous permettent de définir un comportement ou un algorithme commun sur les types ou un sous ensemble de types .NET.

Par exemple, je vais écrire une fonction Swap avec des génériques.
Voir le chapitre pour les fonctions.
Cette fonction existait dans certains langages, elle permettait d'inverser la valeur de deux variables, elle sera utilisable avec des int, des singles.

Swap reçoit 2 paramètres par référence de type TItem (un généric donc). Par convention, les noms des paramètres de type génériques commencent toujours par T.

void Swap<TItem>( ref TItem v1,  ref TItem v2)
{
TItem temp = default(TItem);// instanciation d'une variable temp de type ItemType

temp = v1;

v1 = v2;

v2 = temp;

}

Comment utiliser la fonction Swap ?

// avec des entiers
int var1 = 12;
int var2 = 2;
Swap< int>(ref var1, ref var2);
MessageBox.Show(var1.ToString() + var2.ToString());

// avec des strings
string c1 = "aa";
string c2 = "bb";
Swap<string>(ref c1, ref c2);
MessageBox.Show(c1 + "  " + c2);

Depuis c# 3 et donc ici, on peut omettre <int> ou <string> (grâce à l'inférence de type).

Swap(ref var1, ref var2);

On a à notre disposition des collections génériques,
la collections List par exemple.

Déclarons la collection Col, c'est une collection typée (elle ne peut contenir que des strings) au départ elle est vide :

List<string> Col = new List<string>();

Voir le chapitre sur les collections.

Pourquoi ne pas utiliser des types Object à la place des génériques ? Les génériques sont fortement typés. Si on crée une collection générique de long, on ne peut utiliser que des long : c'est mieux, cela évite les erreurs, les conversions de type. Ils sont plus rapides que l'usage des objets. S'il y a erreur, elle se produit probablement à la compilation et pas à l'exécution. Cela permet d'utiliser l'intellisense.

III-N - Les collections

Il existe deux manières de grouper des objets : avec des tableaux d'objets ou des collections d'objets.

Pour certaines collections (comme les listes, par exemple), le nombre d'éléments n'est pas défini au départ ; contrairement aux tableaux.
Les items affichés dans une ListBox donnent une idée concrète de ce qu'est une collection.

III-N-1 - Liste générique

Une liste fonctionne comme un groupe d'éléments, une liste d'éléments dans laquelle il est possible d'ajouter ou d'enlever un élément à n'importe quel endroit sans avoir à se préoccuper de la taille de la collection, ni où se trouve l'élément.
Le nombre d'éléments n'est pas défini au départ. Dans une liste, il n'y a aucun élément au départ, puis il n'y a que les éléments que l'on a ajouté.

List<T> représente une liste fortement typée d'objets accessibles par index.

C'est juste une Liste , d'items. C'est une liste de génériques. Elle est de type référence.
On peut décider que ce sera exclusivement des chaînes de caractères, des int, des decimal ou tel type… Il n'y a pas de clé.
Il faut inclure le namespace suivant :

using System.Collection.Generic;

Déclarons la collection Col, c'est une collection typée (elle ne peut contenir que des strings) au départ elle est vide :

List<string> Col = new List<string>();

J'ajoute des éléments (ou items) à cette collection.

Col.Add ("Toto");

Voici la collection :

La collection a maintenant un élément (on dit un item en anglais).

Je fais maintenant :

Col.Add("Lulu");

Col.Add("Titi");

La collection a trois éléments maintenant.

Il est aussi possible d'insérer un élément à une position particulière :

Col.Insert(2, "lulu");

On peut enlever un élément avec Remove :

Col.Remove("Lulu");

La collection n'a plus que deux éléments maintenant.

On voit que le nombre d'éléments n'est pas connu à l'avance, il varie en fonction des éléments ajoutés (ou retirés).

On peut aussi enlever l'élément d'index 1 grâce à RemoveAt :

Col.RemoveAt(1);

On peut aussi enlever une plage d'éléments à partir d'index grâce à RemoveRange :

Col.RemoveRange(1,3); // on enlève 3 éléments à partir du deuxième.

Attention le premier élément est l'élément d'index 0.

Un élément est repéré par son indice ou index.

string element= Col[1]; //contient "Titi" (le second Item de la collection)

Pour parcourir une collection on peut utiliser foreach :

List<string> Col = new List<string>();
Col.Add ("Toto");
Col.Add("Lulu");
Col.Add("Titi");

foreach (string prenom in Col)
{
MessageBox.Show (prenom);
}

On se souviendra qu'avec foreach on peut parcourir mais pas modifier les éléments.
Pour modifier il faut faire une boucle for sur les index :

List<string> Col = new List<string>();
Col.Add ("Toto");
Col.Add("Lulu");
Col.Add("Titi");

for (int index = 0; index < Col.Count; index++ )
{
Col[index] = Col[index] + ".";
MessageBox.Show(Col [index]);
}

On remarque qu'on a utilisé Count qui retourne le nombre d'éléments de la collection,
l'index allant de 0 à Count -1.

La List générique possède des méthodes propres aux collections génériques.

Contains permet de savoir si la list contient un élément :

bool ispresent = Col.Contains("toto");

IndexOf permet de chercher la position d'un élément dans une list (qui n'est pas obligatoirement triée, la recherche s'effectuant élément par élément).

int position= Col.IndexOf("lulu");// 'retourne l'index du premier'élément qui contient "lulu"

int position2 =Col.IndexOf("lulu", 1,2);// recherche à partir de l'élément 1 et sur 2 éléments.

Si on veut rechercher successivement les "lulu", on utilisera comme deuxième paramètre, la position précédente+1.

Il existe aussi un LastIndexOf qui commence par la fin.

BinarySearch recherche aussi un élément mais sur une liste triée (recherche dichotomique) ; c'est plus rapide.

Col.Sort(); //Il est nécessaire que le tableau soit trié

int i = Col.BinarySearch("lulu");

Revenons sur Sort() :
si on travaille sur une List d'int ou des strings, il y a une méthode de comparaison par défaut et le tri (QuickSort) s'effectue avec :

Col.Sort();

Par contre si on travaille sur une List d'objets de type ElementDeListe ayant trois propriétés nommées Nom, Numero, Texte ; il n'y a pas de comparateur par défaut.
Il faut le créer : ici on va trier sur le champ Nom.

static int CompareElements(ElementDeListe x, ElementDeListe y)
{
return string.Compare(x.Nom, y.Nom);

}

On a créé une fonction de comparaison ayant deux ElementDeListe comme paramètres (ceux à comparer).
La fonction retourne 1, 0 -1 si x supérieur, égal ou inférieur à y.
Pour faire le tri :

Col.Sort(CompareElements);

RemoveAll :
Permet de supprimer de la collection les éléments correspondant à une condition; retourne le nombre d'éléments supprimés.

int nombreDElementsSupprimés = nombres.RemoveAll(n => n % 2 != 0);

On peut créer une List à partir d'un tableau grâce à :

string[] input = { "Toto",
"Lulu",
"Riri" };

List<string> MyFriends = new List<string>(input);

On peut aussi créer un tableau à partir d'une liste :

string[] output = MyFriends.ToArray();

Il est possible d'avoir un sous-ensemble d'une liste :

List<string> Listoutput = MyFriends.GetRange(2, 3);

Il existe aussi RemoveRange, AddRange, InsertRange.

Au passage, pour enlever tous les éléments d'une collection :

MyFriends.Clear();

On peut aussi utiliser les méthodes d'extension. Elles permettent d'ajouter à des types existants, comme les collections, des méthodes (de LINQ, par exemple) : Min, Max, Sum, Average, Last, First, All, Any, Count,Select, Join, OrderBy, GroupBy, Where…

Il faut inclure l'espace de noms System.Linq.

Max
Quel est l'élément le plus grand (Max) d'une liste :

List<int> nombres = new List<int> { 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int a = nombres.Max();
MessageBox.Show (a.ToString());

Select
Chaque élément est passé individuellement à une fonction qui le transforme en autre chose et les éléments convertis sont enregistrés, grâce à ToList, dans une nouvelle collection.

List<string> strings = nombres.Select(n => n.ToString()).ToList();

Ici chaque élément de nombres est converti en string.

Any
List contient-il des éléments qui correspondent aux conditions définies par un prédicat (ici n>=10).

bool trouve = nombres.Any(n => n >= 10);

First, Where
Recherche un élément qui correspond aux conditions définies par le prédicat et retourne la première ou toutes les occurrences.
Ici on cherche les nombres paires (nombre modulo 2 =0).

int premierElement = nombres.First(n => n % 2 == 0);
List<int> listresult = nombres.Where(n => n % 2 == 0).ToList();

La classe de base pour List et toutes les collections génériques est Collection.

III-N-2 - Dictionary

Dictionary est un dictionnaire contenant des couples clé-valeur typés (génériques). Chaque ajout au dictionnaire se compose d'une valeur et de sa clé associée.

La clé toujours unique (pas de doublon) permet de retrouver la valeur, elle ne doit pas être vide.
La valeur peut être nulle.

Créons un Dictionary avec des clés de type string et des valeurs de type int.

Dictionary<int, string> days = new Dictionary<int, String> { { "Sunday", 0 }, { "Monday", 1 } };

Ici on a ajouté des éléments dès la déclaration.

Ajout d'éléments :

days.Add("Tuesday", 2);
days.Add("Tuesday", 2); // déclenche une exception car la clé 'Tuesday' est déjà dans le Dictionary

On peut tester si la clé existe avant de faire Add.
Si elle n'existe pas (days.ContainsKey("Tuesday") est égal à false), ajouter.

if (!days.ContainsKey("Tuesday))
{ days.Add("Tuesday", 2); }

Pour lire la valeur correspondant à la clé Monday.

string mavaleur = days["Tuesday"];

Attention, si la clé n'existe pas on a une exception de type KeyNotFoundException et l'application s'arrête.
Il faut mieux utiliser TryGetValue qui retourne true si la clé a été trouvée et met la valeur après out :

string value;
if (days.TryGetValue("Monday", out value))
{
// value contient la valeur correspondant à la clé 'Monday'
}
else
{
// pas de valeur pour la clé 'Monday'
}

Ici on a testé s'il y avait une valeur correspondant à la clé.
TryGetValue permet donc de rechercher une valeur correspondant à une clé, retourne false si la clé n'existe pas (sans déclencher d'erreur).

Parcourir un Dictionary (le type de clé/Value est le type KeyValuePair).

foreach (KeyValuePair<string, int> day in days)
{
string maday = day.Value;
}

Enlever un élément :

days.Remove("Monday");

On peut utiliser toutes les valeurs qui sont dans la ValueCollection du Dictionary.

Dictionary<string, int>.ValueCollection valueColl =
days.Values;

// La ValueCollection est typée (ici c'est une collection de int)
foreach (int s in valueColl)
{
System.Diagnostics.Debug.WriteLine( s.ToString());
}

Il y a aussi keys qui contient toutes les clés.

III-N-3 - LinkedList

Ce sont des Listes chaînées de génériques, chaque élément (node ou nœud) comportant une propriété Value (qui contient la valeur de l'élément) et les propriétés Next et Previous permettant d'atteindre les éléments suivant et précédent.

Ici on va créer des nœuds et tester s'il y a un nœud avant ou après le nœud :

// Creation d'une LinkedList de string.
LinkedList<String> ll = new LinkedList<String>();

//Creation d'un noeud nommé lln contenant "orange"
LinkedListNode<String> lln = new LinkedListNode<String>("orange");

// la valeur dans le node est dans lln.Value

// Ajouter le noeud lln à la liste ll.
ll.AddLast(lln);

// Ajouter un noeud avant et après le noeud "orange"
ll.AddFirst("red");
ll.AddLast("yellow");

//lln.List retourne la list a laquelle appartient le noeud
//lln.List.Count () nombre de noeuds dans la liste où est le noeud lln
if ( lln.List == null )
System.Diagnostics.Debug.WriteLine( " Le Noeud n'est pas lié." );
else
System.Diagnostics.Debug.WriteLine( " le Noeud appartient à une liste reliée avec {0} éléments.", lln.List.Count );

//Y a t-il un node avant?
if ( lln.Previous == null )
{
//pas de noeud avant
}
else
{
System.Diagnostics.Debug.WriteLine (lln.Previous.Value.ToString());
}

//Valeur du noeud courant
string valeutConrante = lln.Value;

//Y a t-il un noeud après
if ( lln.Next != null )
{//Il y a un noeud après
System.Diagnostics.Debug.WriteLine( "   Valeur du noeud suivant:     {0}", lln.Next.Value );
}

On note que pour ajouter un élément on a AddFirst, AddLast, AddBefore, AddAfter.

Pour effacer un élément on a RemoveFirst, RemoveLast, Remove.

On peut chercher un élément avec Find, First, Last, FindLast.

Autre exemple : on initialise la LinkedList avec un tableau puis on travaille sur la liste à partir de l'élément courant.

// Déclaration d'un tableau de string
string[] words = {"the", "fox", "jumped", "over", "the", "dog"};

//Déclaration d'une LinkedList qu'on 'chargera' avec le tableau
LinkedList<string> ListWords = new LinkedList<String> (words);

//Déclaration d'un noeud nommé 'current' correspondant à "the"
LinkedListNode<String> current = ListWords.FindLast("the");

//À partir de l'élément current, on peut ajouter un noeud avant ou après.

ListWords.AddAfter(current, "old");  //il y a aussi AddBefore

//À partir de l'élément courant, on peut parcourir le précédent ou le suivant

LinkedListNode<String> element = current.Previous ; //il y a aussi Next

//On peut déplacer un élément

ListWords.AddBefore(current, element);

On peut voir la valeur du premier ou du dernier élément : ListWords.First.Value ListWors.Last.Value

Il existe aussi Contains, Count.

III-N-4 - Queue

Collection générique de type FIFO (First In, First Out),

premier arrivé premier sorti.

C'est la queue devant un cinéma, le premier arrivé, prend son billet et sort le premier.

Les objets (string, int… ) stockés dans Queue sont insérés à une extrémité et supprimés à l'autre.

DeQueue retourne et supprime l'élément de début de liste ; il sort l'élément de la liste.

EnQueue ajoute un élément en fin de liste.

Peek retourne l'élément de début sans le supprimer.

Queue<int> myQ = new Queue<int> ();
myQ.Enqueue("One");
myQ.Enqueue("Two");
myQ.Enqueue("Three");

myQ.Count Affiche le nombre d'éléments.

Dequeue sort l'élément de début de liste :

MessageBox.Show (myQ.Dequeue());

Affiche le premier sorti en le sortant, "one" dans notre exemple. S'il n'y a plus d'élément cela lève une exception (une erreur) il faut donc gérer l'exception ou contrôler le nombre d'éléments avec la propriété Count.

int element= myQ.Peek();

Lit le premier élément sans l'enlever de la Queue.

myQ.Clear();

Efface tous les éléments de la queue.

III-N-5 - Stack

Collection générique de type pile (ou stack) LIFO (Last In, First Out), dernier entré, premier sorti.

Ce type de stack (pile) est très utilisé en interne par les programmes informatiques : on stocke dans une stack les adresses de retour des fonctions appelées, au retour on récupère l'adresse du dessus.

Push insère un objet en haut de la pile.

Pop enlève et retourne un objet en haut de la pile.

On peut utiliser une pile dans un programme pour gérer le déplacement de l'utilisateur dans un arbre, les éléments en cours sont stockés par Push, pour remonter en chemin inverse, on Pop.

Exemple :

Stack<int> LaPile = new Stack<int> () ;

Ajouter un élément en haut de la pile :

LaPile.Push(2);

Enlever un élément en haut de la pile :

int num = LaPile.Pop();

On peut aussi lire l'élément en haut sans l'enlever.

int a = LaPile.Peek();

LaPile.Clear(); 'Supprime tous les éléments.

On peut utiliser les méthodes d'extension : Min, Max, Sum, Average ; un exemple, somme de tous les éléments de la pile :

LaPile.Push(6);
LaPile.Push(2);
int a = LaPile.Sum();

III-N-6 - BitArray

Crée une collection de booléens (codés sur un bit). La valeur de chaque élément est true ou false.

III-N-7 - ObservableCollection

Il arrive souvent de lier (binder) une collection List à une ListBox par exemple, ce qui fait que les éléments de la liste sont affichés dans la ListBox automatiquement.
Si on ajoute un élément dans la liste avec le code suivant :

MaListe.Add(new ElementDeListe("kg-gr", "kg", "C"));

La ListBox n'est pas mise à jour car la liste n'implémente pas INotifyCollectionChanged qui permet d'envoyer des évènements sur l'ajout ou la suppression d'un élément dans une liste.

Pour que l'ajout ou la suppression l'élément dans la collection soient pris en compte par la ListBox, il faut utiliser une ObservableCollection.

Une ObservableCollection représente donc une collection de données génériques qui fournit des notifications lorsque des éléments sont ajoutés, supprimés, ou lorsque la liste entière est actualisée.

Il faut inclure l'espace de noms suivant :

using System.Collections.ObjectModel;

Ensuite on peut déclarer une ObservableCollection.

ObservableCollection<string> ob =new ObservableCollection<string>();

Attention, si on modifie un élément de la collection, la modification n'est pas reportée sur la ListBox car les éléments eux-même n'implémentent pas INotifyPropertyChanged. Si on veut que la modification de la valeur d'une propriété d'un élément soit reportée dans la ListBox, il faut donc implémentent INotifyPropertyChanged sur les éléments (voir le chapitre sur le binding).
Les propriétés et méthodes sont les mêmes que pour les List. Il n'y a pas Sort, Reverse, BinarySearch, RemoveAll, RemoveRange, AddRange, FindAll, et pas mal d'autres.

III-N-8 - Généralisation sur la notion de collection

Certains objets ont une liste de données, d'items de propriétés, le framework les organise en Collections.

Une collection peut donc faire partie des propriétés d'un objet.

Exemple : les contrôles ListBox possèdent une collection Items dans laquelle sont placés tous les éléments contenus dans la liste. Pour ajouter un élément on utilise la méthode Add de la collection Items :

ListBox.Items.Add( );

Un tas d'objets possèdent des collections.

Sur les collections, on peut utiliser les méthodes d'extension de Linq (voir le chapitre sur Linq).

III-O - Les énumérations

Enumérations simples.

Les énumérations sont utilisées lorsque l'on a un jeu de constantes liées logiquement.
Un bloc enum permet de créer une liste (une énumération) de constantes, le bloc commence par enum.

enum Saison
{  printemps,
été,
automne,
hiver
}

Cela crée les constantes suivantes :
Saison.printemps ;
Saison.été ;
Saison.automne ;
Saison.hiver.

Il s'agit d'un ensemble de constantes nommées.

Généralement on définit l'énumération au niveau de l'espace de nom ou de la classe, ainsi on peut l'utiliser partout dans l'espace de nom ou la classe.
Le « ; » semble facultatif.

// Au niveau  espace de nom
namespace compare
{
enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
public partial class MainPage : PhoneApplicationPage
{

//Au niveau classe
public partial class MainPage : PhoneApplicationPage
{
enum Saison { printemps, été, automne, hivers };

Il est ensuite possible d'utiliser une variable de type Saison et d'y mettre été :

Saison SaisonEnCours;
SaisonEnCours= Saison.été;
if (SaisonEnCours== Saison.hiver)
{
}

Une variable de type enum est une variable par valeur.

En interne à chaque valeur nommée est associée une valeur numérique ; par défaut sont utilisées les int : 0, 1, ,2…
Par défaut :

Saison.printemps = 0
Saison.été = 1
Saison.automne = 2
Saison.hiver =3

Mais on peut imposer une valeur différente :

enum Couleurs { Blanc=3, Rouge=8, Vert=45 };

Ensuite on peut afficher la valeur nommée (grâce à ToString), la valeur numérique (on cast en int puis on utilise ToString) ou récupérer la valeur nommée à partir de la valeur numérique(avec GetName) :

Couleurs maCouleur= Couleurs.Rouge;

MessageBox.Show(maCouleur.ToString());  //Affiche 'Rouge'.
MessageBox.Show(( (int) maCouleur).ToString());//Affiche '8'.
MessageBox.Show(Enum.GetName(typeof(Couleurs), 8));//Affiche 'Rouge' qui correspond à la valeur '8'.

Il est possible d'imposer un type différent :
Ici les valeurs seront des longs.

enum Couleurs : long { Blanc=3L, Rouge=8L, Vert=45L };

Notez que les valeurs données doivent être des longs (on a ajouté L aux valeurs).

Enumérations d'indicateur.

A utiliser lorsque plusieurs valeurs d'énumération peuvent être spécifiées en même temps.
On utilise d'attribut [Flags].
Les valeurs sont des puissances de 2. Elles peuvent être combinées avec l'opérateur de bit Or.
Voici une énumération Fichier qui permet d'indiquer si un fichier est en lecture, écriture, les deux et s'il est ouvert ou non.

[Flags]
public enum Fichier
{
None=0,
Read  = 0x01,
Write = 0x02,
Open  = 0x04
}

Le premier bit indique Read, le deuxième Write et le troisième Open.

// On instancie MonFichier
// On donne 2 valeurs: Read et Write grâce à Or
Fichier MonFichier= Fichier.Read | Fichier.Write;

MessageBox.Show(MonFichier.ToString());//Affiche 'Read,Write'

MessageBox.Show(((int)MonFichier).ToString());//Affiche '3'  1+2 (011 en binaire)

//Ajouter Open grâce à Or
//MonFichier = MonFichier | Fichier.Open;

MessageBox.Show(MonFichier.ToString());//Affiche 'Read,Write,Open'

MessageBox.Show(((int)MonFichier).ToString());//Affiche '7'  1+2+4 (111 en binaire)

// Enlever Write grâce à Xor, il force un bit à zéro
MonFichier = MonFichier ^ Fichier.Write ;

MessageBox.Show(MonFichier.ToString());//Affiche 'Read,Open'

MessageBox.Show(((int)MonFichier).ToString());//Affiche '5'  1+4 (101 en binaire)

// Tester grâce à And
bool test= (MonFichier & Fichier.Open) == Fichier.Open;
if (test==true)
{
MessageBox.Show("Est ouvert");
}

On a à notre disposition des énumérations fournies par le framework.

Les touches :

Key MyKey = Key.Enter;

III-P - Les structures

En règle générale, une structure est utilisée comme conteneur pour un petit jeu de variables permettant de regrouper des données de type différent.

Les structures sont intéressantes quand vous voulez utiliser des variables contenant plusieurs informations de différents types. Les structures sont une alternative aux classes.

Exemple : vous voulez définir une variable contenant une adresse composée d'un numéro, de la rue, de la ville.

Il faut d'abord définir la structure (au niveau module ou Class, pas dans une procédure).

struct Adresse
{
public int  Numero;

public string Rue;

public string Ville;

}

Puis on peut déclarer une variable de type structure :

Adresse MonAdresse;

On aurait pu utiliser new.

Adresse MonAdresse = new Adresse();

Si on n'utilise pas new, les champs ne seront pas assignés et l'objet ne pourra pas être utilisé tant que tous les champs n'auront pas été initialisés.

La variable MonAdresse est déclarée comme une Adresse, elle contient donc :

un numéro qui est dans MonAdresse.Numero ;

un nom de rue qui est dans MonAdresse.Rue ;

un nom de ville qui est dans MonAdresse.Ville.

On pourra enfin l'utiliser :

MonAdresse.Numero=2;

MonAdresse.Rue= "Grande rue";

MonAdresse.Ville= "Lyon";

Il peut y avoir des constructeurs et des fonctions dans une structure.

struct Adresse
{
public int  Numero;

public string Rue;

public string Ville;

//Constructeur
public Adresse(int s1, string s2, string s3)
{
Numero = s1;
Rue = s2;
Ville = s3;
}

}

Ce qui permet d'instancier de cette manière :

Adresse MonAdresse = new Adresse(12, "grande rue", "Lyon");

Quelles différences entre structure et classe ?
- Une variable d'un type structure est de type valeur et contient directement les données de la structure, alors qu'une instance de classe contient une référence aux données.

Cela a de l'importance : si je crée une variable avec une structure, que je copie cette variable dans une seconde, le fait de modifier une propriété de la première variable ne modifie pas la propriété de la seconde.

- Une structure ne peut pas hériter d'une autre structure ou classe ni servir de base à une autre structure.

On utilise une structure quand elle est limitée à quelques octets de données et qu'on n'a pas besoin de l'héritage.

En pratique on met rarement de membres de type référence dans une structure, car cela fait que la structure ne correspond plus vraiment à la sémantique de valeur que les structures ont habituellement. C'est acceptable si le type référence en question est immuable (comme string), sinon il vaut mieux éviter.

Remarquons que les types numériques de base (int, double, decimal…) sont de type structure et non classe. Cela explique que ces types sont des types valeur et qu''on ne peut pas créer une classe qui hérite de ces types.

III-Q - Les fonctions, procédures, méthodes

Une fonction est un ensemble d'instructions réalisant une certaine tâche et retournant un résultat.
Une procédure est une fonction qui ne renvoie pas de résultat. En programmation orientée objet, une fonction membre d'un objet est désignée par le terme de méthode.

Une fonction est une suite d'instructions regroupées sous un nom.
Elle a en entrée des paramètres et retourne un résultat.
Pour l'utiliser on l'appelle par son nom en ajoutant les paramètres, elle retourne un résultat :

int Multiplier(int x, int y)
{
return x*y;
}

Ici on a une fonction nommée Multiplier.
On a deux paramètres (ou arguments) de type int.(int x, int y).
Elle retourne un int (le int avant le nom de la fonction l'indique).
Dans le corps de la fonction (ce qui est entre { et }), on calcule x*y. La fonction retourne le produit ( return indique ce que doit retourner la fonction).

Pour utiliser la fonction :

int produit= Multiplier (2,3);

Après la ligne précédente produit sera égal à 6.

On peut bien sûr avoir des variables en paramètres d'appel :

int a= 12;
int b= 2;
int produit= Multiplier (a,b);

Les paramètres d'appel et ceux de la fonction doivent être de même type (on appelle la fonction avec 2 int, la fonction doit attendre 2 int).
Ou bien, il doit exister une conversion implicite du type de l'argument (i.e. la valeur passée à la fonction) vers le type du paramètre. Par exemple on peut passer un int à une méthode qui attend un double, car il y a une conversion implicite ; de même, une variable qui attend un object peut recevoir une valeur de n'importe quel type, puisque tous les types héritent de object.
Par contre les noms des paramètres peuvent être différents (a et b pour l'appel ; x et y dans la fonction).
La valeur de a est passée dans x, celle de b dans y.
On parle de passage de paramètre par valeur, c'est le passage par défaut.

Il existe un autre mode de passage de paramètres : par référence indiqué par ref.
Là ce n'est pas la valeur mais la référence (l'adresse en mémoire) qui est passée ; si la fonction modifie la valeur du paramètre, la variable d'appel est aussi modifiée.

void Doubler(ref int x,ref int y)
{
x= x*x;
y=y*y;
}

Pour l'appel il faut aussi utiliser ref :

int a= 12;
int b= 2;
Doubler (ref a, ref b);

Il peut arriver qu'un paramètre serve uniquement à récupérer une valeur initialisée par la fonction, dans ce cas on utilise out.
'out' indique une variable de sortie qui est déclarée mais peut ne pas être initialisée avant l'appel.

void initialise(out int y)
{
y=5;
}

Comment l'utiliser ?

int a; //Variable juste déclarée, n'ayant pas de valeur
initialise ( out a);  // a sera égal à 5

Si la fonction ne retourne rien, on utilise void (vide) avant le nom de la fonction, on devra l'appeler procédure.

void Affiche(int x)
{
MessageBox.Show ( x.ToString());
}

On peut surcharger une fonction :

La surcharge permet de créer des fonctions qui ont le même nom mais des paramètres différents (on parle de signatures différentes).
Pour avoir une signature différente il faut modifier le nombre de paramètres ou type d'un paramètre :

int Multiplier(int x, int y)
{
return x*y;
}

double Multiplier(double x, double y)
{
return x*y;
}

Ici on a une fonction nommée Multiplier qui a deux signatures, elle accepte des int ou des double.
C'est transparent ; quand on appelle la fonction avec des paramètres double c'est la seconde signature qui fonctionne.

Même si la fonction ou procédure n'a pas de paramètre, il faut mettre « () » pour l'appel.

Pourquoi utiliser des fonctions et des procédures ?
Pour découper un code complexe en fonction plus simple.
Si un code est utilisé plusieurs fois, on le met dans une fonction qu'il suffit d'appeler.

III-R - Erreurs et interception des erreurs

Dans une application, des erreurs peuvent survenir (on dit des exceptions car théoriquement elles surviennent exceptionnellement !) : division par zéro d'un entier, utilisation d'un index de tableau qui n'existe pas…
L'application plante et s'arrête !

Si on lance l'exécution avec le débogueur (F5) en mode Debug ou Release, lorsque survient une exception, l'émulateur s'arrête et le débogueur affiche un message indiquant l'exception.

On peut modifier le comportement en passant par le menu Déboguer puis Options et paramètres :

'Activer l'Assistant Exception' ouvre, en cas d'exception, une nouvelle fenêtre plus détaillée (c'est l'option par défaut) à la place de l'ancienne boîte de dialogue.

Vous pouvez modifier le comportement du débogueur vis à vis d'une exception en passant par le menu Déboguer puis Exceptions.

Si on coche "levé", le débogueur s'arrête dès que l'exception est levée, même si elle est interceptée plus tard. Si on coche seulement "non gérée par l'utilisateur", le débogueur s'arrêtera quand l'exception est levée, sauf s'il y a un bloc catch dans le code qui la gère.

Vous pouvez lancer l'exécution sans le débogueur (Ctrl+F5) :

Dans ce cas dans l'émulateur, s'il y a une exception, l'exécution s'arrête et l'application en cours s'arrête, on la quitte.
Ce comportement sera le même dans votre Windows Phone.

Il faut éviter cela.
Notons que WP est tolérant : si le code indique comme source d'une image un fichier .jpg qui n'existe pas, il n'y a pas de levée d'exception et l'application se poursuit.

En théorie, on peut parfois prévoir l'exception et l'éviter : tester par exemple le dénominateur d'une division et effectuer la division seulement si ce dénominateur n'est pas égal à 0.
Mais souvent cela n'est pas possible ; l'autre solution est de capter l'erreur.

Pour capter une erreur qui déclenche une exception et provoque un arrêt du programme, on utilise try catch.
Le bloc try ('essayer') contient le code protégé (entre { et }) risquant de provoquer l'exception. Y compris le code dans les méthodes appelées.
La clause catch intercepte l'exception si elle se produit.

Autrement dit, s'il y a une exception dans le bloc try, c'est le bloc catch qui est exécuté avec comme paramètre, si nécessaire, un objet de type Exception ou dérivé. Notons qu'il doit y avoir des accolades dans le bloc catch.

Catch peut contenir :
- rien : l'erreur est ignorée (c'est à éviter) ;
- du code avertissant l'utilisateur qu'il y a un problème ;
- du code résolvant le problème.

Catch peut être utilisé sans arguments ; dans ce cas, il intercepte tout type d'exception et est appelé la clause catch générale (c'est généralement à éviter. Autant que possible, il faut indiquer le type précis d'exception qu'on s'attend à recevoir). Il peut également prendre un argument de type System.Exception ou dérivé. Dans ce cas, il gère une exception spécifique.

Exemple : on tente de lire un élément de tableau avec un index trop grand.

monElement= array[index];

Si le tableau contient trois éléments et que index=6, il y a levée de l'exception IndexOutOfRangeException et le programme s'arrête.

Comment éviter cela : on peut faire simple avec un catch sans argument qui s'exécutera pour tous les types d'erreur:

try
{
monElement= array[index];
}
catch
{
MessageBox.Show ("Erreur index");
}

On peut donner un argument à catch :

try
{
monElement= array[index];
}
catch (System.IndexOutOfRangeException e)
{
MessageBox.Show (e.Message);
}

On remarque qu'après chaque catch, on a le paramètre « e » de type Exception ou d'un type plus spécifique :
ArithmeticException ;
IndexOutOfRangeException ;
DivideByZeroException.

« e » possède des propriétés comme e.Message qui contient le message de l'erreur.

On peut mettre plusieurs catch spécifiques pour chaque type d'erreurs. On peut terminer par un catch général qui captera toutes les autres exceptions.

try
{
string s = null;

}
catch (ArgumentNullException ex )
{
MessageBox.Show( ex.Message);
}
catch (Exception ex )
{
MessageBox.Show( ex.Message);
}

Attention : dès qu'une erreur est traitée par un catch, les autres catch sont ignorés. On note donc l'importance de l'ordre des catch : le dernier catch est le plus général et ne sera exécuté que si les autres ne le sont pas.
Autant que possible, il faut indiquer le type précis d'exception qu'on s'attend à recevoir et eviter les catch vides.

Parfois on a besoin d'intercepter l'erreur puis de la déclencher à nouveau ; dans ce cas on utilise throw qui force la levée d'une exception :

try
{
return array[index];
}
catch (System.IndexOutOfRangeException e)
{
MessageBox.Show (e.Message);
//
throw   new System.ArgumentOuthrowtOfRangeException("index parameter is out of range.", e);
}
finally
{
}

Le second argument (e) permet de transmettre l'instance de l'exception qui avait déclenché le catch. Ainsi dans la nouvelle exception InnerException indique l'erreur d'origine.
Ce second argument est facultatif.

"throw" tout court, permet de relancer la même exception après avoir fait un traitement.

On peut ajouter finally qui sera toujours exécutée.

Il y a souvent des erreurs dans les conversions (string vers int par exemple).
Pour éviter cela, sans utiliser de try catch, on peut utiliser tryparse qui fait la conversion mais ne déclenche pas d'exception si la conversion est impossible.

string a="12";
int b;
bool res =  int.TryParse(a, out b);
// si la conversion echoue res=false
//C'est une méthode de classe int.tryparse, double.trypars..
//retour du resultat dans b

III-S - Linq

C'est un langage de requêtes (permettant d'interroger une source de données) utilisable directement dans le code C# et à l'aide de mots clés familiers (issus du SQL, le langage d'interrogation des bases de données).
Cette source de données peut être une base de données, un fichier XML (Linq To XML) mais aussi une collection, un tableau, une chaîne de caractères (Linq To Objet).
Si un objet prend en charge l'interface IEnumerable, le fournisseur LINQ to Object vous permet de l'interroger.

LINQ (dixit Microsoft) offre trois principaux avantages par rapport aux boucles for Each traditionnelles.
Les requêtes:
- sont plus concises et lisibles, surtout lors du filtrage de plusieurs conditions ;
- fournissent des fonctions puissantes de filtrage, de classement et de regroupement avec un minimum de code ;
- peuvent être appliquées à d'autres sources de données avec peu ou pas de changement.

Il faut ajouter l'espace de noms System.Linq :

using System.Linq;

Principe :
À titre d'exemple simpliste, on a des données dans MyData et chaque donnée a les champs Nom, Prenom, Ville… Comment chercher les enregistrements ayant comme nom "toto" ?

var Resultat = from Element in MyData

where Element.Nom ==n "Toto"

select Element ;

On crée une variable de requête (ici var Resultat) qui sera chargée de contenir la requête (et pas les résultats), on laisse le soin au compilateur de choisir le type de variable (var qui prend implicitement le type générique IEnumerable de l'expression qu'on lui affecte, il ne s'agit pas de type dynamique, mais seulement de typage implicite).

Puis l'expression de requête composée de from : dans quoi chercher ? Dans quel élément ?
Ici on laisse aussi le soin au compilateur de choisir le type de la variable (il est également possible de préciser le type).

in : quelle source de données ? Dans MyData.

where : précise les conditions à appliquer, c'est le filtre.

select : précise les éléments à extraire qui vont apparaître dans Resultat

Remarquons donc qu'initialement on connaît MyData et on sait que chaque élément de MyData a un champ Nom, c'est tout ! On utilise dans la requête les nouvelles variables Resultat et Element sans avoir à déclarer leur type (on aurait pu le faire). Element est une variable de portée réduite comme élément de MyData.

Et pour afficher les noms dans une ListBox :

foreach (P in Resultat)
ListBox.Items.Add(P.Nom);

Ici la requête contenue dans la variable de requête Resultat est exécutée pour alimenter la boucle For Each. On remarque que l'exécution est différée. La requête n'est évaluée (exécutée) que quand on commence à parcourir le résultat.

La clause Where peut contenir des conditions complexes avec des et des ou.

Linq et les tableaux d'int :
un tableau peut être interrogé par Linq.
Exemple, rechercher les nombres pairs dans un tableau d'int :

// La Data source: c'est un tableau d'Integer

int[] numbers =  {0, 1, 2, 3, 4, 5, 6};

// Création de la requête.

//Pour chaque élément num de la source

//Si l'élément num est tel que num % 2=0 (num modulo 2, condition pour qu'il soit pair)

//Selectionner num et le mettre dans réponses

var réponses = from num in numbers

where num % 2 == 0

select num ;

// Exécution de la requête.

// On utilise les réponses

foreach (var number in réponses)

listBox1.Items.Add(number);

Cela affiche dans la ListBox : 0 2 4 6

On peut vouloir compter uniquement les nombres pairs :

var réponse = from num in numbers

where num % 2 == 0

select num ;

textBlock1.Text = réponse.Count().ToString();

Affiche 4.
Ici on exécute la méthode Count() qui retourne le nombre d'élément.

On aurait pu utiliser .Min() , .Max(), .Average() pour obtenir l'élément le plus petit, le plus grand ou la moyenne des éléments .

Linq et les caractères d'une string : une string peut être interrogée par Linq.
Exemple, rechercher les caractères qui sont des chiffres dans une string :

string MyString = "ABCjkjhkhs666KMOOP";

//Select les caractères qui sont des chiffres
var Query = from char ch in MyString
where char.IsDigit(ch) ==true
select ch;

// Exécution de la requête
foreach ( char  c in Query)
textBlock1.Text= textBlock1.Text+c;

// Combien y a t-il de nombres?
int count  = Query.Count();

Linq et les mots d'une string : une string peut être interrogée par Linq.
Exemple, rechercher un mot dans une string. On va découper tous les mots de la string et les mettre dans un tableau de string puis faire une requête.

string text  = "Ceci est un cours C# pour les débutants et les autres";

string searchTerm  = "cours";

// Conversion de la String en Tableau de mots:.
string[] dataSource= text.Split(new Char [] {' ', ',', '.', ';', ':'});

// Création et exécution de la requête
// Utiliser ToLower pour  trouver "cours" et "Cours"
var Query = from word in dataSource
where word.ToLower() == "cours"
select word ;

//Compter les 'cours'.
int count = Query.Count();
textBlock1.Text= count.ToString();

Méthodes d'extension de Linq : dès que le type d'une variable a l'interface IEnumérable (tableau, collection…) on peut utiliser les méthodes d'extension de Linq et là, c'est vraiment simple et puissant.

Il y a une relation entre la syntaxe de requête et les méthodes d'extension :

var query =
from i in values
where i % 2 == 0
select i * i;

est équivalent (en utilisant les méthodes d'extension) à :

var query = values.Where(i => i % 2 == 0).Select(i => i * i);

Ici on utilise une expression lambda avec l'opérateur = >, qui se lit « conduit à ». A gauche de l'opérateur lambda il y a les paramètres d'entrée et à droite le bloc d'expression ou d'instructions. L'expression lambda i => i * i se lit « i conduit à i fois i ».

Calculons une moyenne sur un tableau de Single :

Single [] numbers =  {0, 1, 2, 3, 4, 5, 6};

Single moyenne = numbers.Average();
textBlock1.Text = moyenne.ToString();

Pour une List d'int, Average retourne un double :

List<int> grades = new List<int> { 78, 92, 100, 37, 81 };

double average = grades.Average();

Calculons une somme sur un tableau de Single :

decimal [] numbers =  {0, 1, 2, 3, 4, 5, 6};

decimal MaSomme = numbers.Sum();

On peut aussi utiliser : First, Last, All, Any, Concat, Contains, Distinct, ElementAr, ElementAtOrDefault, Except, Intersect, Max, Min, Reserve, Skip, SkipeWhile, Take, TakeWhile, ThenBy, ToArray, ToDictionary, Where.

Voir le chapitre binding avec tri, filtre et image qui donne de beaux exemples.

III-T - Date et heure

Il existe un type de variable DateTime pour gérer les dates et heures, comment l'utiliser ?
Une variable DateTime contient une date plus l'heure (un moment précis). Elle occupe 8 octets (64 bits). Elle peut contenir une date comprise entre le 1er janvier de l'année 01 et le 31 décembre 9999 et une heure comprise entre 0:00:00 (minuit) et 23:59:59.9999999… En fait ce qui est codé dans la variable DateTime est le nombre de graduations ou "tick" (une graduation= 100 nanosecondes) écoulées à compter de minuit, le 1er janvier de l'année 1 jusqu'à la date et l'heure codée.

//Déclarons un DateTime
DateTime ddn;
//Donnons la valeur 1 janvier 2011
ddn = new DateTime(2011, 5, 1);// année mois, jour.

//Déclarons date1 et donnons une valeur an, mois, jours, heure, minute, seconde
DateTime date1 = new DateTime(2011, 5, 1, 8, 30, 52);

//Propriétés en lecture seule
int d = date1.Day;
int m = date1.Month;
int y = date1.Year;
int h = date1.Hour;
int mn = date1.Minute;
int s = date1.Second;

//Convertir un DateTime en string avec To..String()
MessageBox.Show(date1.ToLongTimeString());   //Affiche 08:30:52
MessageBox.Show(date1.ToShortDateString());  //Affiche 01/05/2011
MessageBox.Show(date1.ToShortTimeString());  //Affiche 08:30
MessageBox.Show(date1.ToLongDateString());   //Affiche dimanche 1 mai 2011
//Voir aussi le chapitre "Afficher" pour utiliser les formats :
MessageBox.Show(date1.ToString("hh:mm"));

// Convertir une string (saisie par l'utilisateur) en DateTime avec Parse
string dateString = "5/1/2011 8:32:50 AM";
DateTime dateIn = DateTime.Parse(dateString,
System.Globalization.CultureInfo.InvariantCulture);

//Convertir mais avec TryParse
DateTime DDR;
bool res = DateTime.TryParse("5/1/2011", out DDR);
//si conversion impossible retourne false sans planter

// DateTime particuliers
DateTime date2 = DateTime.Now; // Date et heure actuelles locales
DateTime date3 = DateTime.UtcNow; // en utc
DateTime date4 = DateTime.Today; // Date actuelle

Pour définir un intervalle de temps (une durée) on utilise un TimeSpan (c'est une structure).

TimeSpan Gr = new TimeSpan(288,0,0,0);// si 3 paramètres=h,m,s
// si 4 paramètres= j,h,m,s

//ou

TimeSpan Gr = new TimeSpan.FromDays(288);

Il existe aussi FromMonths, FromHours, FromMinutes, FromSeconds, FromMilliSeconds.

On peut ajouter un TimeSpan à un DateTime (une durée à un moment).

Quelle sera la date dans 100 jours : il faut utiliser la date du jour et ajouter un TimeSpan de 100 jours.

DateTime MyDate;
MyDate = DateTime.Now;
TimeSpan Gr = new TimeSpan(100, 0, 0, 0);
MyDate = MyDate + Gr;
MessageBox.Show(MyDate.ToShortDateString());

On aurait pu utiliser Add.

On peut soustraire un TimeSpan. Ici enlevons 10 jours :

MyDate=MyDate.Subtract(new TimeSpan(10,0,0,0));
MessageBox.Show(MyDate.ToShortDateString());

Enfin, il y a des méthodes pour ajouter des mois, jours, minutes, secondes (AddMonths, AddDays, AddMinutes, AddSecondes).

MyDate=MyDate.AddMonths(2);
MessageBox.Show(MyDate.ToShortDateString());

MyDate.DayOfWeek retourne le jour de la semaine en anglais sous forme d'un enum de type DayOfWeek (ex. Sunday..)
MyDate.ToString("dddd") retourne le jour de la semaine dans la culture en cours ; en français pour nous (ex. samedi).

La méthode Now retourne la date et l'heure actuelle.

DateTime d;
d = DateTime.Now;

Il y a aussi Today (aujourd'hui).

Les DateTime utilisent l'heure locale (.Now) par défaut mais on peut utiliser l'heure universelle (utc) comme dans .UtcNow. La méthode ToUniversalTime convertit en temps universel.

Il existe aussi DateTimeOffset : la structure DateTimeOffset stocke un DateTime et un TimeSpan qui indique la différence entre le DateTime et l'UTC (dans un champ Int16).
Cela permet à une valeur DateTimeOffset de répercuter l'heure dans un fuseau horaire quelconque, alors qu'un DateTime ne peut refléter que le fuseau horaire UTC et le fuseau horaire local.

III-U - Nombres aléatoires

On peut avoir besoin de nombres aléatoires pour simuler le hasard dans un programme.

La classe Random fournit des nombres pseudo-aléatoires.

La génération commence à partir d'une valeur initiale qu'on appele souvent "seed" (graine). Ensuite les nombres suivants sont sélectionnés à l'aide d'un algorithme mathématique, mais ils sont néanmoins suffisamment aléatoires pour des fins pratiques.
Lorsque la même valeur initiale est utilisée, la même série de nombres pseudo-aléatoires est générée.
Pour générer des séquences différentes, il faut que la valeur initiale soit différente.

Si on instancie un Random sans paramètres, la valeur initiale est fournie par l'horloge interne (qui change de valeur tous les ticks ou 100ns), elle est donc au hasard :

Random rand = new Random();

Ici une série différente est obtenue pour chaque nouvelle instance de Random (à condition qu'il se soit écoulé un peu de temps -au moins un tick- entre 2 instanciations de Random).
Si on utilise le code précédent, à chaque utilisation de l'application, la valeur initiale sera différente et donc la série de nombres sera différente.

Attention : si on instancie deux random à la suite, il y a de grandes chances pour que l'horloge interne garde la même valeur ; la valeur initiale sera la même et la série la même :

Random rand1 = new Random();  // même series
Random rand2 = new Random();

Mais, si l'horloge interne a changé entre les 2 instructions, ça donnera bien des valeurs différentes. C'est imprévisible…

Si on sépare les deux random d'un temps assez long , les séries ne seront pas les mêmes :

Random rand1 = new Random();
MessageBox.Show ("Deux séries");
Random rand2 = new Random();  //2 séries différentes

On peut forcer la valeur initiale à une valeur précise en donnant un paramètre (un int) lors de l'instanciation.

Random rand1 = new Random(50);  // On aura toujours la même série

Random rand1 = new Random(200);  // 2 series différentes mais toujours les mêmes
Random rand2 = new Random(3845);

Ensuite on peut générer une séries d'octets : valeur 0 (compris) à 255 (compris) hexadécimal= 0xFF.

Random rand = new Random();

// Génère  4 random octets dans un tableau.
byte[] bytes = new byte[4];
rand.NextBytes(bytes);

Générons un octet :

Random rand = new Random();
byte b = (byte)rand.Next(256); //Next retourne un int qu'il faut caster en byte

On peut générer des entiers :

Random rand = new Random();

// on génère succesivement 3 entiers
int r1= rand.Next();
int r2= rand.Next();
int r3= rand.Next();

// on génère 1 entier inférieur ou égal à 100
//strictement inférieur à 101
int r4= rand.Next(101);

// on génère 1 entier entre 1 (compris) et 6 (compris)
int r5= rand.Next(1 , 7);

// on génère 1 entier entre -10 (compris) et 6 (compris)
int r6= rand.Next(-10 , 7);

On remarque que, comme la valeur supérieure n'est pas incluse, il faut ajouter 1 à la valeur du paramètre pour obtenir le maximum désirée.
Dans Next(min, max) min est inclus, max est non inclus.
Par ailleurs, on peut avoir des nombres négatifs uniquement quand on utilise deux paramètres.

On peut générer des doubles : de valeur allant de 0.0 (compris) à 1.0 (non compris) soit : supérieur ou égal à 0 et inférieur à 1. Pour obtenir une plage de valeur( de min à max)il faut donc multiplier par max-min puis ajouter min.

Random rand = new Random();

// on génère succesivement 3 doubles (valeur entre 0 et 1 non compris)
double r1= rand.NextDouble();
double r2= rand.NextDouble();
double r3= rand.NextDouble();

// on génère 1 double entre 0 et 5
double r4= rand.NextDouble() *5 ;

// on génère 1 double entre 1 et 6 (non compris)
double r4= (rand.NextDouble() *5 )+1;

Pour les forts ! On peut créer une classe qui hérite de Random, en surchargeant Sample on peut modifier la distribution des valeurs.

public class RandomProportional : Random
{
//
protected override double Sample( )
{    // On modifie la distribution des valeurs.
//Ici la distribution est proportionnelle à la valeur:
//Les nombres se rapprochant de 1 (les plus grands) sont plus fréquents.
return Math.Sqrt( base.Sample( ) );
}

public override int Next()
{
return (int) (Sample() * int.MaxValue);
}
}

Ensuite on peut utiliser cette classe :

RandomProportional rand = new RandomProportional();

En cryptographie (pour générer des mots de passe par exemple), par sécurité, utilisez une classe dérivée de System.Security.Cryptography.RandomNumberGenerator telle que System.Security.Cryptography.RNGCryptoServiceProvider, par exemple.

III-V - Culture, globalisation

Une Culture concerne la langue d'affichage mais aussi les paramètres régionaux : format de date, d'heure, monnaie, séparateur décimal, calendrier.
Pour gérer la culture on utilise la classe CultureInfo (de System.Globalization) et la propriété CurrentCulture de thread (de System.Threading). Il y a également CurrentUICulture (langue d'affichage, c'est la culture utilisée pour récupérer les ressources localisées).

On peut voir et modifier la culture en cours dans les paramètres du téléphone :

Par défaut la CultureInfo est celle définie dans les paramètres du téléphone, probablement « fr-FR » sur votre téléphone.
fr signifie langue française et FR signifie « région France » ; il existe fr-CH (Suisse), fr-BE (Belgique).
Dans « en-US », « en » signifie anglais et « US » signifie USA.
On se rend compte que l'affichage est dépendant de la CurrentCulture du Thread en cours.

Exemple : si la CurrentCulture est la Culture Us et que j'affiche avec le format monétaire cela affiche un $ avant, si je suis en culture Française cela affiche le symbole de l'euro après.

Ajoutons :

using System.Globalization;
using System.Threading;

La culture en cours est dans CurrentCulture de System.Threading.

//Où peut-on trouver le  séparateur décimal?
string separateur;
separateur=Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencyDecimalSeparator;// separateur=","
separateur=Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator); separateur=","

// et le symbole monnetaire?
string symbole;
symbole = Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencySymbol;//symbole= le symbole euros.

//Le nom de la culture
MessageBox.Show(Thread.CurrentThread.CurrentCulture.Name);// Affiche fr-FR
MessageBox.Show(Thread.CurrentThread.CurrentCulture.NativeName);// Affiche français(France)
MessageBox.Show(Thread.CurrentThread.CurrentCulture.EnglishName);// Affiche french (France)

On peut modifier la CurrentCulture par code.

Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
MessageBox.Show(Thread.CurrentThread.CurrentCulture.DisplayName);

On peut aussi modifier le CultureInfo uniquement sur une instruction ToString TryParse ou Format :

Double d = 12.35;
MessageBox.Show (d.ToString()); //Affiche 12,35
MessageBox.Show( d.ToString( new CultureInfo("en-US")));//Affiche 12.35

On a donc vu que par défaut ToString utilise le CurrentCulture ; mais c'est aussi le cas pour toutes les instructions de conversion. On se souvient que par contre, pour un littéral, le séparateur décimal est toujours le point.

double d= 3.5;
MessageBox.Show (d.ToString()); //Affiche '3,5' chez nous

III-W - Afficher

On se souvient qu'on peut afficher dans un TextBlock, un TextBox, une ListBox… mais il faut que ce soit du texte et parfois il faut le formater, le mettre en forme.

Quand on a une variable numérique ou DateTime, pour la transformer en chaîne de caractères on utilise souvent la méthode ToString().

Par exemple, j'ai une variable de type int (entier) ; pour l'afficher, je dois la convertir en string (on peut afficher une chaîne de caractères mais pas une valeur numérique) :

int entier=12;
string chaîneaAfficher;
chaîneaAfficher= entier.ToString();

On peut ajouter en paramètre le format de la string.
Ici on va afficher deux caractères après la virgule.

float Z= 12.4556;
string   mastring =Z.ToString("0.00")
// string= "12,45"

A - Formats prédéfinis. On peut utiliser un caractère indiquant un format prédéfini.

"C" indique le format monétaire.

int MaVariable = 12;
TextBlock1.Text = MaVariable.ToString("C");

Affiche $12.00' (en culture anglaise ou "en-US").

On peut modifier la culture et afficher en Fr par exemple.

using System.Globalization;
//..
int MaVariable = 12;
TextBlock.Text = MaVariable.ToString("C", new CultureInfo("fr-FR"));//Affiche '12,00E'

"D" indique le format décimal.
Pour les intégraux uniquement (int, long… ). Le chiffre après D indique le nombre de chiffres.

int MaVariable = 12;
TextBlock1.Text = MaVariable.ToString("D"); // Affiche '12'
TextBlock1.Text = MaVariable.ToString("D6"); // Affiche '000012'

Dans la seconde ligne on a indiqué d'afficher 6 caractères.

"E" indique le format exponentiel. Aussi appelé "notation scientifique".

int MaVariable = 12;
TextBlock1.Text = MaVariable.ToString("E"); // Affiche '1,200000E+001'

"X" indique le format hexadécimal.

int MaVariable = 12;
TextBlock1.Text = MaVariable.ToString("X");  // Affiche 'C' 12 en hexadécimal
TextBlock1.Text = MaVariable.ToString("X2"); // Affiche '0C'

"F" indique le format en virgule fixe.

double MaVariable = 12.576;
TextBlock1.Text = MaVariable.ToString("F");  // Affiche '12,58'
TextBlock1.Text = MaVariable.ToString("F1"); // Affiche '12,6'

Par défaut F affiche deux chiffres après la virgule ; il arrondit au chiffre supérieur si le 3èm chiffre est supérieur à 5.
On peut spécifier le nombre de décimales à afficher.

"G" indique le format général (par défaut).

double MaVariable = 12.576;
TextBlock1.Text = MaVariable.ToString("G");  // Affiche '12,576'
TextBlock1.Text = MaVariable.ToString("G2"); // Affiche '13'

double MaVariable2 =121212.576 ;
TextBlock.Text = MaVariable2.ToString("G"); //Affiche '121212,576' (pas de séparateur des milliers)

Par défaut G affiche la notation la plus compacte entre "F" et "E".

double d1 = 0.003;
string s1 = d1.ToString("G"); // Affiche "0,003"

double d2 = 0.000003;
string s2 = d2.ToString("G"); // Affiche "3E-06"

On peut spécifier le nombre de chiffre à afficher ('G2' affiche deux chiffres). Il arrondit au chiffre supérieur si le 3e chiffre est supérieur à 5.

"N" indique le format numérique.
Il utilise le séparateur décimal et celui des milliers de la culture.

double MaVariable = 121212.576;
TextBlock1.Text = MaVariable.ToString("N");  // Affiche '121 212,576'
TextBlock1.Text = MaVariable.ToString("N1"); // Affiche '121 212,6'

Par défaut N affiche tous les chiffres.
On peut spécifier le nombre de chiffre après le séparateur décimal. Il arrondit le dernier chiffre affiché au chiffre supérieur si le précédent chiffre est supérieur à 5.

"P" indique le format pourcentage.
Le pourcentage doit être entre 0 et 1 pour afficher 0% à 100% ( mais rien n'empêche d'utiliser 42 qui en format pourcentage donnera 4200%). La valeur sera multipliée par 100. Le symbole % sera ajouté.

double MaVariable = 0.5;
TextBlock1.Text = MaVariable.ToString("P");  // Affiche '50,00%'
TextBlock1.Text = MaVariable.ToString("P0");  // Affiche '50%'

"R" indique le format aller-retour.
Il retourne une chaîne de caractères qui, si on la reconvertit en nombre, redonnera la valeur de départ.

Cette valeur de format ('C', 'X'… ) peut être fournie comme paramètre formatString dans un élément de mise en forme utilisé avec des méthodes telles que String.Format, System.Diagnostics.Debug.WriteLine et StringBuilder.AppendFormat.

outputBlock.Text = String.Format("La valeur de la variable est {0:C2}", MaVariable) + "\n";

Affiche "La valeur de la variable est 12" ; il faut que le TextBlock soit suffisamment large pour afficher la totalité du texte ; c'est bête !

Si la valeur d'une variable à virgule flottante Single ou Double est l'infini positif, l'infini négatif ou une valeur non numérique (NaN), la chaîne mise en forme est la valeur de la propriété PositiveInfinitySymbol, NegativeInfinitySymbol ou NaNSymbol qui est spécifiée par l'objet NumberFormatInfo.

"B" - Formats personnalisés

On peut créer de toute pièce un format, on utilise pour cela des caractères réservés.

"0" indique une espace réservé de 0

Chaque 0 est réservé à un chiffre. Affiche un chiffre ou un zéro. Si le nombre contient moins de chiffres que de zéros, affiche des zéros non significatifs. Si le nombre contient davantage de chiffres à droite du séparateur décimal qu'il n'y a de zéros à droite du séparateur décimal dans l'expression de format, arrondit le nombre à autant de positions décimales qu'il y a de zéros. Si le nombre contient davantage de chiffres à gauche du séparateur décimal qu'il n'y a de zéros à gauche du séparateur décimal dans l'expression de format, affiche les chiffres supplémentaires sans modification.

"#" indique un espace réservé de chiffre.

Chaque # est réservé à un chiffre. Affiche un chiffre ou rien. Affiche un chiffre si l'expression a un chiffre dans la position où le caractère # apparaît dans la chaîne de format ; sinon, n'affiche rien dans cette position.

Ce symbole fonctionne comme l'espace réservé au 0, sauf que les zéros non significatifs et à droite ne s'affichent pas si le nombre contient moins de chiffres qu'il n'y a de caractères # de part et d'autre du séparateur décimal dans l'expression de format.

"." (point) indique l'emplacement du séparateur décimal (celui affiché sera celui du pays).

Vous devriez donc utiliser le point comme espace réservé à la décimale, même si vos paramètres régionaux utilisent la virgule à cette fin. La chaîne mise en forme apparaîtra dans le format correct pour les paramètres régionaux.

"," (virgule) indique l'emplacement du séparateur de milliers.

Le séparateur de milliers sépare les milliers des centaines dans un nombre de quatre chiffres ou plus à gauche du séparateur décimal.

"Littéral" la chaîne sera affichée telle quelle.

"%" affiche en pour cent.

Multiplie l'expression par 100. Le caractère du pourcentage (%) est inséré.

"E0" affiche en notation scientifique.

":" et /" sont les séparateurs d'heure et de date.

";" est le séparateur de section : on peut donner trois formats (un pour les positifs, un pour les négatifs, un pour zéro) séparés par ";".

Exemple :

Chaîne de format '0000', avec le nombre 145 cela affiche '0145'.

Chaîne de format '####', avec le nombre 145 cela affiche '145'.

Chaîne de format '000.00', avec le nombre 45.2 cela affiche '045.20'.

Chaîne de format '#,#', avec le nombre 12345678 cela affiche '12,345,678'.

Chaîne de format '#,,' avec le nombre 12345678 cela affiche '12'.

La chaîne de formatage' #,##0.00 ' veut dire obligatoirement deux chiffres après le séparateur décimal et un avant.

Si on affiche avec ce format :

1.1 cela donne 1,10.

.5 cela donne 0,50.

4563 cela donne 4 563,00.

Exemple pratique :

double MaVariable = 0.5;
TextBox.Text = MaVariable.ToString("$#,##0.00;($#,##0.00);Zero");// Affiche '$0,50'

double MaVariable = 0;
TextBox.Text = MaVariable.ToString("$#,##0.00;($#,##0.00);Zero");// Affiche 'Zero'

C - String.Format

Permet de combiner des informations littérales à afficher sans modification et des zones formatées.

Les arguments de String.Format se décomposent en deux parties séparées d'une virgule.

- Chaîne de formatage entre guillemets avec du texte si nécessaire et les éléments à inclure avec un numéro d'ordre entre {}. Exemple "{0} + {1} = {2}", les numéros indiquent l'ordre des valeurs.

- Variables ou valeurs à afficher dans l'ordre, la première étant d'indice zéro, séparées par des virgules. Exemple= A, B, A+B.

Exemple :

int A=3;
int B=5;
MessageBox.Show(String.Format("L'addition {0} + {1} = {2}", A, B, A + B));//affiche'L'addition 3+5=8'

Autre exemple:

string MonNom = "Philippe";
MessageBox.Show(String.Format("{0} il est {1:hhmm}", MonNom, DateTime.Now));//affiche'Philippe il est 0705''

On voit qu'on peut utiliser après le numéro d'ordre et « : » une chaîne de formatage prédéfinie ou personnalisée.

Ici on a utilisé un format de date.

Pour afficher les DateTime, il y a des formats prédéfinis et les formats personnalisés.

DateTime thisDate = new DateTime(2012, 3, 3, 14, 9, 20, 45);

//Les formats prédéfinis.

System.Diagnostics.Debug.WriteLine(thisDate.ToString("d"));//Date courte  "03/03/2012"
System.Diagnostics.Debug.WriteLine(thisDate.ToString("D"));//Date longue  "samedi 3 mars 2012"
System.Diagnostics.Debug.WriteLine(thisDate.ToString("t"));//Heure courte  "14:09"
System.Diagnostics.Debug.WriteLine(thisDate.ToString("T"));//Heure longue  "14:09:20"
System.Diagnostics.Debug.WriteLine(thisDate.ToString("f"));//Pleine date, Heure courte "samedi 3 mars 2012 14:09"
System.Diagnostics.Debug.WriteLine(thisDate.ToString("F"));//Pleine date, Heure longue "samedi 3 mars 2012 14:09:20"
System.Diagnostics.Debug.WriteLine(thisDate.ToString("g"));//Date Heure courte "03/03/2012 14:09"
System.Diagnostics.Debug.WriteLine(thisDate.ToString("G"));//Date Heure longue "03/03/2012 14:09:20"
System.Diagnostics.Debug.WriteLine(thisDate.ToString("M"));//Mois  "3 mars"
System.Diagnostics.Debug.WriteLine(thisDate.ToString("Y"));//Année  "mars 2012"

//Formats personnalisés

//Jour : d jour du mois(1 à 31), dd (01 à 31), ddd (jour abrégé de la semaine), dddd (jour complet de la semaine)
System.Diagnostics.Debug.WriteLine(thisDate.ToString("dddd dd")); // "samedi 03"

//Heure : h(1 à 12), hh (01 à 12), H (1 à 23), HH (01 à 23)
System.Diagnostics.Debug.WriteLine(thisDate.ToString("HH"));// "14"

//Minute : m (0 à 59) mm (00 à 59) //Seconde : s, ss
System.Diagnostics.Debug.WriteLine(thisDate.ToString("mm,ss"));// "09,20"

//Mois : M, MM, MMM, MMMM
System.Diagnostics.Debug.WriteLine(thisDate.ToString("MMMM"));// "mars"

//Année : y, yy, yyy, yyyy
System.Diagnostics.Debug.WriteLine(thisDate.ToString("yyyy"));// "2012"

//Exemples (notez le séparateur des heures ":" et celui des dates "/")
System.Diagnostics.Debug.WriteLine(thisDate.ToString("hh:mm:ss"));// "02:09:20"
System.Diagnostics.Debug.WriteLine(thisDate.ToString("d MMMM"));//"3/mars"

On se souvient qu'on peut aussi utiliser :

DateTime date1 = new DateTime(2011, 5, 1, 8, 30, 52);

MessageBox.Show(date1.ToLongTimeString());   //Affiche 08:30:52
MessageBox.Show(date1.ToShortDateString());  //Affiche 01/05/2011
MessageBox.Show(date1.ToShortTimeString());  //Affiche 08:30
MessageBox.Show(date1.ToLongDateString());   //Affiche dimanche 1 mai 2011

Bien sur la conversion se fait dans la culture du thread en cours.

On peut ajouter a ToString un paramétre indiquant la culture à utiliser :

CultureInfo cultureUS = new CultureInfo("en-US");

DateTime date1 = new DateTime(1890, 9, 10);

MessageBox.Show(date1.ToString("d", cultureUS));

III-X - Le Framework.NET

Le code se compose de mots clés C# et de classes faisant partie du Framework .NET de Silverlight.

Les classes sont regroupées sous la dénomination d'espace de noms (NameSpace).

Un framework est un ensemble d'espace de noms et de classes.

On a à notre disposition le Framework.NET qui contient des milliers de classes.

Pour pouvoir utiliser une classe il faut inclure l'espace de noms auquel appartient cet objet.
En C#, on le fait grâce à using qui sera placé en haut du code, avant toutes autres instructions :

using System.Collection.Generic;

Il faut aussi que l'assembly correspondant (sous forme de DLL par exemple) soit référencé dans le projet (fenêtre Explorateur de solution, Références). Par défaut les références du Framework (les DLL) sont déjà dans le projet ; les principaux espaces de noms aussi.
Dans la suite du cours s'il faut inclure un assembly, ou un espace de noms, je le signale.

Voici quelques espaces de noms du Framework du Windows Phone (7.1)

Microsoft.Devices, Microsoft.Devices.Radio, Microsoft.Devices.Sensors, Microsoft.Phone, Microsoft.Phone.Controls, Microsoft.Phone.Info, Microsoft.Phone.Marketplace, Microsoft.Phone.Shell, Microsoft.Phone.Tasks, System, System.Data, System.Windows.Controls.

Voyons le détail de l'espace de noms System : il contient les types (classes , structures) suivants (et d'autres).

System
System.String
System.Objet
Systen.Array
System.Collections
System.Enum
System.Exception
System.Math
System.Random
System.Boolean
System.Byte
System.Char
System.Int16
System.Int32
System.Int64
System.Single
System.DateTime
System.Double
System.Decimal

On connaît bien ces classes qu'on utilise dans le code C#.

On peut retrouver les espaces de noms de Silverlight sur MSDN. À partir de là, on clique sur un espace de noms (System par exemple) puis une classe (Enum par exemple), on voit les différents membres.

On retrouve des icônes (les mêmes que dans Visual Studio) :

Pour une méthode :

- le petit carré à gauche indique que c'est une méthode (s'il y a une petite clé, la méthode est protected sinon elle est public) ;

- "s" indique si elle est static ;

- le téléphone indique qu'elle existe sur Windows Phone ;

- la boule indique qu'elle existe sur Xbox.

Cliquez sur le nom d'une méthode pour voir les détails et des exemples.

Revoir le chapitre Objet, classe, Espace de noms.

III-Y - Création de classe

On est DANS la boîte.

Jusqu'à présent on avait utilisé des classes déjà présentes dans le Framework Silverlight pour créer des objets.
Ici on va créer notre propre classe (nommée ElementDeListe, possédant trois propriétés), menu Projet, Ajouter une classe. On donne un nom puis Ok.
Ensuite on crée la classe ElementDeListe qui a trois propriétés : Titre, Formule, Mode.

public class ElementDeListe  // la classe se nomme 'ElementDeListe'
{
}

On veut un champ (une variable) nommé Titre pour cette classe ; pour cela on va créer une variable de type string privée nommée _titre.

private string _titre;

Ce champ (cette variable), n'est pas accessible à l'extérieur de la classe (private).
Un champ est habituellement private ou protected (on pourra l'utiliser dans la classe ou une classe dérivée).
Noter que le nom du champ commence par "_" ; c'est une bonne pratique pour les champs privés.
Le nom est normalement en camelCase (première lettre en minuscule, puis première lettre de chaque "mot" en majuscule).

Noter qu'on peut (ce n'est pas recommandé) créer des champs public :

public string Titre;

Les noms de classe et les membres publics sont en PascalCase (première lettre en majuscule, puis première lettre de chaque "mot" en majuscule).
Les paramètres et variables locales sont en camelCase, sans le préfixe "_".

Il existe aussi internal (classe ou membre accessible que dans des fichiers figurant dans le même assembly).
On remarque que les variables sont instanciées comme habituellement avec un ";" à la fin de la ligne.

On peut aussi ajouter const (champ constante qui ne peut être modifié que lors de la déclaration) ou readonly (ne peut être modifié que lors de la déclaration ou dans un constructeur) pour que le champ soit une constante (de compilation) ou en lecture seule (constante d'exécution).

Pour un accès public il vaut mieux utiliser une propriété.
On va donc créer une propriété nommée Titre, elle est public, et il faut écrire les Get et Set permettant de lire et modifier cette propriété.
La variable _titre sert à stocker la valeur de Titre :

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

}
}
}

Quand on lira la propriété Titre, on aura la valeur de _titre ; quand on écrira dans Titre, on donnera une valeur à _titre.
Il vaut mieux utiliser une propriété plutôt qu'un champ public car cela permet d'utiliser du code dans Get et Set (contrôle de type, de validité, calcul…).

On remarque que pour une propriété, il n'y a pas de ";" après « public string Titre » mais des accolades.

Voici le code complet de la classe avec ses trois propriétés:
(menu Projet, Ajouter une classe pour créer la classe).

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 CalculeTout // 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; }
}

}

La dernière ligne est un constructeur avec 3 paramètres, on lui donne le nom de la classe, on indique des 3 paramètres du constructeur et dans quelles variables il faut passer ces paramètres.

Comment utiliser cette classe ?

On va créer une instance (un objet) nommée element à partir de la classe ElementDeListe :

public ElementDeListe Element;
Element = new ElementDeListe ();

Maintenant on peut donner une valeur aux propriétés :

Element.Titre = "Multiplication";
Element.Formule = "*";
Element.Mode = "sans";

On peut aussi utiliser le constructeur avec trois paramètres :

public ElementDeListe Element= new ElementDeListe("Multiplication", "*", "sans");

On peut aussi créer une collection d'objets.
Dans le code de la page C#, on va instancier une collection nommée ListeMath contenant des objets de type ElementDeListe.

public partial class PanoramaPage : PhoneApplicationPage
{
public List<ElementDeListe> ListeMath { get; set; } //la collection sera public

public PanoramaPage()
{
InitializeComponent();

ListeMath = new List<ElementDeListe>();

//On va créer de nouveaux ElementDeliste en utilisant le constructeur à 3 paramètres.
ListeMath.Add(new ElementDeListe("Multiplication", "*", "sans"));
ListeMath.Add(new ElementDeListe("Division", "/", "sans"));
ListeMath.Add(new ElementDeListe("Addition", "+", "sans"));

ListBoxConversion.DataContext = ListeMath;// en prime on ajoute un binding (une liaison) sur une listbox

Notez bien qu'on instancie ListeMath en haut de la page et avec public afin que cette List soit accessible partout dans la page.
On voit qu'on instancie trois ElementDeListe et qu'on donne une valeur aux trois champs grâce au constructeur.

On a vu qu'a la fin de la classe on avait ajouté un constructeur, il peut y en avoir plusieurs :

public class ElementDeListe  // la classe se nomme 'ElementDeListe'
{
private string _titre;  // Variable privée

// constructeur par défaut: sans paramètres
public ElementDeListe()
{
_titre = "sans";
}

// Second Constructeur avec un paramètre:
public ElementDeListe(string titre)
{
this._titre = titre;
}

}

Si vous ne fournissez pas de constructeur pour votre classe, C# en créera un par défaut qui instanciera l'objet et affectera à toutes les variables membres les valeurs par défaut.

On peut ajouter des méthodes à la classe :

public class ElementDeListe  // la classe se nomme 'ElementDeListe'
{
// Affiche titre :
public void AfficheTitre()
{
MessageBox.Show ( this._titre);
}
}

En résumé :

Un objet peut être vu comme un regroupement de données (champs et propriétés). La différence entre un champ et une propriété est qu'alors que le champ est une simple variable, la propriété est contrôlée par du code interne qui peut modifier le comportement de la lecture ou de l'écriture de la propriété.

Mais un objet va plus loin : il peut contenir un comportement sous forme de procédures et fonctions contenant du code. Ce sont les méthodes de l'objet, elles représentent le comportement commun de tous les objets appartenant à la classe.

Tous les éléments de la classe peuvent être public (visibles par l'utilisateur de la classe ou des classes dérivées) ou private (non accessibles à l'utilisateur de la classe et utilisés en interne dans la classe mais pas dans les classes dérivées).
Ils peuvent aussi être protected (non accessible à l'utilisateur mais accessible dans la classe et les classes dérivées).

Les champs sont habituellement private et des propriétés public permettent de modifier ces champs.

Héritage :

À partir d'une classe existante, la classe de base (ou classe mère), on peut créer une nouvelle classe, la classe dérivée (ou classe fille) qui hérite des membres de la classe de base. Ici on va créer une classe ElementDeListe2 qui hérite de ElementDeListe (on ajoute au nom de la classe deux points puis le nom de la classe mére) :

public class ElementDeListe2  : ElementDeListe
{
}

ElementDeListe2 hérite des membres (champs, propriétés et méthodes) de ElementDeListe (si ils sont protected ou public, pas si ils sont private): ainsi dans la classe ElementDeListe2 on a la méthode AfficheTitre.
On peut ajouter dans ElementDeListe2 d'autres champs, propriétés et méthodes.

public class ElementDeListe2  : ElementDeListe
{
private _titre2;
}

On peut utiliser sur une instance de la classe fille une méthode de la classe mère (si elle est accéssible).
On peut aussi redéfinir (substituer, remplacer) les méthodes de la classe mère grâce à override à condition que la méthode de la classe de base soit virtual. Dans ElementDeListe2, si on ne veut pas utiliser la méthode AfficheTitre de la classe mère ; on va remplacer cette méthode par une méthode de même nom dans la classe ElementDeListe2 :

La methode de la classe mère doit donc être public virtual ou protected virtual (ou abstract) :

public class ElementDeListe  // la classe se nomme 'ElementDeListe'
{
// Affiche titre :
public virtual void AfficheTitre()
{
MessageBox.Show ( this._Titre);
}
}

Dans la classe fille on peut maintenant redéfinir la méthode AfficheTitre (comme la méthode de base est public, la méthode dans la classe fille doit aussi être public) :

public class ElementDeListe2  : ElementDeListe
{

public override void AfficheTitre()
{
MessageBox.Show ( this._Mode);
base.AfficheTitre();

}
}

Ici on crée donc une nouvelle méthode. Notez qu'on peut appeler la méthode de la classe mère avec base.AfficheTitre();.

Écriture rapide de classe :
On peut écrire rapidement une classe et ses propriétés avec la syntaxe suivante :

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;
}

}

Les get et set sont compact, pas de rajout de code possible.

Classes ou méthodes abstraites :
Elles ne peuvent pas être instanciées. On ne peut donc pas utiliser new. Elles servent à quoi alors ?
La classe abstraite sert de classe de base pour une autre classe qui va en hériter.

public abstract class MaClasseAbstraite
{
public abstract void MaMethodeAbstraite();
//Cette méthode abstract ne doit pas avoir de corps, d'accolades {}
}

Voici la classe dérivée :

public  class MaClasse : MaClasseAbstraite
{
public override void MaMethodeAbstraite()
{

}

}

La méthode abstraite est redéfinie.

À l'inverse, pour qu'une classe ne puisse pas être héritée, il faut ajouter sealed (scellée) avec le mot Class.
On remarque qu'il y a beaucoup de classe sealed dans le Framework.NET : string par exemple.

On résume les modificateurs d'accès autorisés pour un membre ou un type :
public : L'accès n'est pas limité dans la classe et pour l'utilisateur de la classe.
protected : L'accès est restreint à la classe conteneur ou aux types dérivés de la classe conteneur (pas pour l'utilisateur de la classe ou l'utilisateur de la classe dérivée).
internal : L'accès est autorisé dans la classe et pour l'utilisateur de la classe mais restreint à l'assembly en cours.
protected internal : L'accès est restreint à l'assembly en cours ou aux types dérivés de la classe conteneur.
private : L'accès est restreint au type conteneur. L'utilisateur de la classe n'y a pas accès.

III-Z - Exercices en C#

1 - Exercice sur les voyelle.

Soit une string c contenant un caractère, si c'est une voyelle, afficher "C'est une voyelle".

2 - Exercice sur les et et les ou.

Sont bissextiles les années divisibles par 4 mais non divisibles par 100 et les années divisibles par 400.
Soit une variable nommée annee', écrire le code affichant dans la TextBox resultat si l'année est bissextile.

3 - Exercice sur les majuscules.

Soit une string Nom, si nécéssaire mettre le premier caractère en majuscule.

4 - Exercice sur les fonctions :

Sachant qu'un angle en radian = (angle en degrés/180)x Pi, écrire les fonctions convertissant les degrés en radians et radians en degrés.

Réponses aux exercices.

1 - Réponse à l'exercice sur les voyelles.

Soit une string c, si c'est une voyelle, afficher « C'est une voyelle ».

On va créer une string nommée voyelle, l'initialiser avec une chaine contenant toutes les voyelles.
Puis on verra si voyelle contient la string c.

string Voyelle = "aeiouy";

string c = "e";

if (Voyelle.Contains(c) )
{
MessageBox.Show("c'est une voyelle");

}

2 - Réponse à l'exercice sur les et et les ou.

Sont bissextiles les années divisibles par 4 mais non divisibles par 100 et les années divisibles par 400.
Soit une variable nommée annee, écrire le code affichant dans la TextBox nommé resultat si l'année est bissextile. Si le reste de la division par 4 égale zéro est vrai ET si le reste de la division par 100 égale zéro est faux OU si le reste de la division par 400 égale zéro est vrai l'année est bissextile.

int annee= 2012;
if (((annee % 4 == 0) & !(annee % 100 == 0)) | (annee % 400 == 0))
{
resultat.Text = "Année bissextille";
}
else
{
resultat.Text = "Année non bissextille";
}

Notez bien la position des parenthèses: (… et… ) ou (… ).

3 - Réponse à l'exercice sur les majuscules.

Soit une string Nom, si nécéssaire mettre le premier caractère en majuscule.

string Nom  = "philippe";

//Le premier caractère est minuscule?
if (Char.IsLower(Nom[0]))
{
//Le transformer en majuscule et afficher
MessageBox.Show(Char.ToUpper(Nom[0]) + Nom.Substring(1, Nom.Length - 1));

}

4 - Réponse à l'exercice sur les fonctions :

Sachant qu'un angle en radian = (angle en degrés/180)x Pi, écrire les fonctions convertissant les degrés en radians et radians en degrés.

static double DegreeToRadian(double angle)
{
return (angle / 180.0) * Math.PI;
}

static double RadianToDegree(double radian)
{
return (radian / Math.PI) * 180.0;
}

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 où 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 le terme 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 où 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
}
}

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).

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.

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 deux 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.

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 :

Ressources pour Windows Phone 7.1.

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;
}

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.

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,
}

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) :

VI - Les différents contrôles

- Contrôles disponibles.
Border
Button
Canvas
CheckBox
ContentControl
ContentPresenter
Control
Grid
HyperlinkButton
Image
InkPresenter
ListBox
MediaElement
MultiScaleImage
Panel
PasswordBox
ProgressBar
RadioButton
ScrollViewer
Slider
StackPanel
TextBlock
TextBox
Viewbox
UserControl

Tous les contrôles ne sont pas disponibles dans la boîte à outils.

On peut si on le désire ajouter un contrôle dans la boîte à outils. Dans celle-ci, clic droit sur « Windows Phone Controls », puis dans le menu « Choisir les éléments » et cocher un contrôle à ajouter dans la liste de la boîte à outils.

- Contrôles Silverlight différents dans Windows Phone.
ScroolBarr
ToolsTip
ComboBox
RichTextBox

- Ne sont pas disponibles dans Windows Phone.
System.Windows.Controls.OpenFileDialog
System.Windows.Controls.SaveFileDialog

- Ne sont pas disponibles les contrôles qui sont dans le Silverlight SDK.
Calendar
ChildWindow
DataGrid
DatePicker
Frame
GridSplitter
Label
Page
TabControl
TreeView

On peut ajouter le ToolKit Silverlight for Windows Phone Toolkit pour Mango 7.1 :http://silverlight.codeplex.com/.

- Cela ajoute :
AutoCompleteBox
ContextMenu
DatePicker
GestureService/GestureListener
ListPicker
LongListSelector
Page Transitions
PerformanceProgressBar
TiltEffect
TimePicker
ToggleSwitch
WrapPanel

Il faut les ajouter à la boîte à outils. Voir le chapitre sur le Toolkit.

VI-A - Page

Une application très simple peut se composer d'une page principale (nommée « MainPage » par défaut).
C'est le cas quand on crée une application en choisissant « Application Windows Phone » :

Ensuite on peut créer d'autres pages (menu « Projet » puis « Ajouter un nouvel élément »).

Dans la page principale on peut ouvrir une autre page à l'aide du code suivant :

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

Quand on crée une page :
Elle est dans la Frame de l'application.
Cette Page contient une grille principale (nommée LayoutRoot).
Elle contient un StackPanel (nommé TitlePanel) comprenant le titre de l'application et le titre de la page.
Dessous il y une grille nommée ContentPanel dans laquelle il faudra mettre le contenu de la Page.

<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 où 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>

Une page fait 800 * 480 px. Le SystemTray en haut fait 32 px
(il peut y avoir des écrans de 480 * 320).

Si on regarde le code C#, on constate que l'espace de noms est celui du projet (comme pour le code XAML) ; il y a une classe partielle « MainPage » (la classe MainPage est donc composée du code XAML et de la classe partielle en C#).

using System.Windows.Shapes;
using Microsoft.Phone.Controls;

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

Cette class MainPage hérite de la classe PhoneApplicationPage qui est la page standard.
Dans cette classe MainPage, il y a une fonction « public MainPage() » qui est le constructeur de la page ; on y initialise les composants de la page ; on peut y ajouter du code qui sera exécuté lors de la création de la page.

Dans une page, on peut utiliser des évènements ; Loaded, par exemple.
Dans ce cas il faut ajouter « Loaded=… »

<phone:PhoneApplicationPage
x:Class="CalculeTout.Inflation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
...
Loaded="PhoneApplicationPage_Loaded">

Quand la page est chargée, la routine suivante, dans le code C#, est exécutée :

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{

}

On verra qu'il est plus judicieux, pour mettre du code quand la page est chargée, d'utiliser une surcharge de OnNavigatedTo :

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
}

Il y a deux autres manières de structurer une application Windows Phone :
En utilisant le Pivot ou le Panorama.

VI-B - Pivot et Panorama

Utilisons une page « Pivot ».

C'est le cas quand on crée une application en choisissant « Application Pivot Windows Phone » :

Voici un exemple de pages pivots :

Une application « Pivot » contient plusieurs pages, on passe de l'une à l'autre en glissant horizontalement ou en tapant sur le nom grisé en haut de la page désirée.

Dans le code XAML on voit que le contrôle « Pivot » contient des PivotItem ayant un titre (Header).

<controls:Pivot Title="MON APPLICATION">

<controls:PivotItem Header="premier">
</controls:PivotItem>

<controls:PivotItem Header="second">
</controls:PivotItem>
</controls:Pivot>

Code complet avec dans chaque PivotItem une ListBox :

<!--LayoutRoot est la grille racine où tout le contenu de la page est placé-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Contrôle Pivot-->
<controls:Pivot Title="MON APPLICATION">
<!--Élément un de tableau croisé dynamique-->
<controls:PivotItem Header="premier">
<!--Liste double trait avec habillage du texte-->
<ListBox x:Name="FirstListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0"
Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PivotItem>

<!--Élément deux de tableau croisé dynamique-->
<controls:PivotItem Header="second">
<!--Liste triple trait, aucun habillage du texte-->
<ListBox x:Name="SecondListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding LineOne}" TextWrapping="NoWrap" Margin="12,0,0,0"
Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineThree}" TextWrapping="NoWrap" Margin="12,-6,0,0"
Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PivotItem>
</controls:Pivot>
</Grid>

Utilisons maintenant un « Panorama ».

C'est le cas quand on crée une application en choisissant « Application Panorama Windows Phone ».

Voici une page panorama :

Une application « Panorama » contient une page unique très large, on se déplace en glissant horizontalement. Le titre est large et s'étale sur toute la grande page. Le début du contenu de la page suivante de droite est légèrement visible.

Le contrôle Panorama contient des PanoramaItem ayant un titre (Header). Mis côte à côte les PanoramaItem forment la grande page virtuelle.

<controls:Panorama Title="mon application">
<controls:PanoramaItem Header="premier élément">
</controls:PanoramaItem>
<controls:PanoramaItem Header="second élément">
</controls:PanoramaItem>
</controls:Panorama>

Code complet avec dans chaque PanoramaItem une ListBox :

<!--Contrôle Panorama-->
<controls:Panorama Title="mon application">
<controls:Panorama.Background>
<ImageBrush ImageSource="PanoramaBackground.png"/>
</controls:Panorama.Background>

<!--Élément un de panorama-->
<controls:PanoramaItem Header="premier élément">
<!--Liste double trait avec habillage du texte-->
<ListBox Margin="0,0,-12,0" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0"
Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>

<!--Élément deux de panorama-->
<!--Utilisez 'Orientation="Horizontal"' pour activer un panneau qui s'affiche horizontalement-->
<controls:PanoramaItem Header="second élément">
<!--Liste double trait avec espace réservé pour une image et habillage du texte-->
<ListBox Margin="0,0,-12,0" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,0,17">
<!--Remplacer le rectangle par l'image-->
<Rectangle Height="100" Width="100" Fill="#FFE5001b" Margin="12,0,9,0"/>
<StackPanel Width="311">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0"
Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>
</controls:Panorama>

VI-C - Les Conteneurs

Les conteneurs peuvent contenir plusieurs contrôles et permettent de les positionner.

VI-C-1 - Les Grid

En Silverlight une Grid est un conteneur, comprenant des cellules servant à aligner les contrôles (et pas une grille de données comme dans les Windows forms).

Aller chercher une Grid dans la boîte à outils à gauche et la mettre sur la page.

<Grid Height="520" HorizontalAlignment="Left" Margin="81,51,0,0" Name="grid1" VerticalAlignment="Top" Width="335">
</Grid>

On a une grille mais avec une seule cellule.

En fait quand on ajoute une page vide à un projet, la fenêtre contient déjà une Grid.

Comportement d'une Grid a une seule cellule.
Ce n'est pas recommandé mais ajoutons deux TextBlock, une image et une ellipse dans la cellule unique :

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Text="TextBlock aligné à droite et en haut"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"/>
<TextBlock Text="TextBlock aligné à gauche et en bas"
HorizontalAlignment="Left"
VerticalAlignment="Top" />
<Image Source="c:\vshaghet.jpg" />
<Ellipse Stroke="{StaticResource PhoneAccentBrush}"
StrokeThickness="24" />

Cela donne :

On se rend compte que les éléments comme l'image et l'ellipse sont centrés et occupent le maximum de place.
Les TextBox sont placés en fonction de leur alignement.
Enfin on remarque que les éléments se superposent : l'ellipse qui est dans la dernière ligne du code XAML est au dessus.

Pour ajouter des lignes et des colonnes dans la grille (dans le designer), il faut cliquer dans les barres grises horizontales ou verticales de la grille et positionner les traits à la souris.

Cela génère le code XAML suivant :

<Grid Height="520" HorizontalAlignment="Left" Margin="81,51,0,0"
Name="grid1" VerticalAlignment="Top" Width="335">
<Grid.RowDefinitions>
<RowDefinition Height="265*" />
<RowDefinition Height="255*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="190*" />
<ColumnDefinition Width="145*" />
</Grid.ColumnDefinitions>
</Grid>

On voit dans le code XAML les largeurs des colonnes (Grid.ColumnDefinition) et les hauteurs des lignes (Grid.RowDefinition).

On peut aussi vouloir définir le nombre de lignes et de colonnes sans définir leurs largeurs et hauteurs : simplement mettre trois lignes, deux colonnes.

<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
</Grid>

Dans la définition des dimensions des lignes et colonnes : on peut, comme on l'a vu, donner dans le designer la taille que l'on veut aux cellules.

Mais on peut, dans le code XAML, donner les dimensions des cellules :
- un chiffre ("150", par exemple) indique la largeur ou hauteur en pixels ;
- '*' signifie autant que possible' (cette ligne reçoit une proportion pondérée de l'espace disponible , 2* indiquant deux fois la proportion) ;
- 'Auto' force à prendre la taille des éléments qu'elle contient.

Exemple de colonnes avec Auto :

<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />

Exemple de lignes avec des étoiles :

<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="2*" />

Si on fait :

<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

On aura trois lignes d'égale hauteur et trois colonnes d'égale largeur. Cela donne neuf cellules carrées.

On peut rendre les lignes de séparation visibles :

ShowGridLines="True"

On peut ensuite ajouter des contrôles dans les cellules de la grille pour les positionner.

Pour chaque contrôle ajouté dans une cellule de la Grid, on définira la cellule où le contrôle se trouve, ses marges et son ancrage si nécessaire.

Ici on va ajouter une ListBox en bas à droite de la grille, voyons ce que cela donne dans le code XAML.

<Grid Height="279" Name="Grid1">
<Grid.RowDefinitions>
<RowDefinition Height="74*" />
<RowDefinition Height="84*" />
<RowDefinition Height="121*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="37*" />
<ColumnDefinition Width="28*" />
<ColumnDefinition Width="31*" />
<ColumnDefinition Width="176*" />
</Grid.ColumnDefinitions>

<ListBox Grid.Column="3" Grid.Row="2"  Name="ListBox1">
<ListBoxItem>toto</ListBoxItem>
<ListBoxItem>lululu</ListBoxItem>
</ListBox>
</Grid>

ListBox Grid.Column="3" Grid.Row="2" indiquent dans quelle cellule est la ListBox.

Attention la première colonne est Column=0 et la première ligne est Row=0.

On aurait pu ajouter Grid.ColumnSpan=2 pour indiquer que la ListBox occupe deux colonnes (la 3 et la 4 dans ce cas).

Il existe aussi Grid.RowSpan pour étendre le contrôle sur plusieurs lignes de la Grid.

On aurait pu ajouter :
Margin="22,26,21,19" pour indiquer les distances au conteneur autour de la ListBox.

Comment faire la même chose en C# ?
La Grid1 étant présente, ajoutons une ListBox comme ci dessus :

ListBox lb = new ListBox();
lb.Margin = new Thickness(22, 26, 11, 19);
Grid.SetColumn(lb, 3);
Grid.SetRow(lb, 2);
grid1.Children.Add(lb);

VI-C-2 - Les StackPanel

Arrange, empile les contrôles sur une même ligne qui peut être horizontale ou verticale (on parle d'empilement).

En C# :

StackPanel instance =new StackPanel();

En XAML :

<StackPanel> </StackPanel>

On a ajouté dans le StackPanel trois boutons.

En XAML :

<StackPanel Height="362" HorizontalAlignment="Left" Margin="29,51,0,0" Name="stackPanel1"
VerticalAlignment="Top" Width="390">
<Button Content="Button" Height="85" Name="button1" Width="300" />
<Button Content="Button" Height="85" Name="button2" Width="300" />
<Button Content="Button" Height="85" Name="button3" Width="300" />
</StackPanel>

On voit qu'il suffit de mettre les objets dans le StackPanel, ils seront empilés.

La valeur par défaut de Orientation du StackPanel est Vertical, aussi les contrôles seront positionnés de haut en bas. L'attribut Orientation="Horizontal" permet de mettre les contrôles enfants de gauche à droite.

<StackPanel Orientation="Horizontal" Height="362" HorizontalAlignment="Left" Margin="29,51,0,0"
Name="stackPanel1" VerticalAlignment="Top" Width="390">
<Button Content="Button" Height="220" Name="button1" Width="100" />
<Button Content="Button" Height="220" Name="button2" Width="100" />
<Button Content="Button" Height="220" Name="button3" Width="100" />
</StackPanel>

C'est bien pratique quand on veut mettre sur une même ligne, les différents champs d'un objet (nom, prénom, adresse) :

<StackPanel Orientation="Horizontal">
<TextBlock Padding="5,0,5,0"
<Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
<TextBlock Text=", " />
<TextBlock Text="{Binding Address}" />
</StackPanel>

En C#, voici un autre exemple :

StackPanel StackPanel = new StackPanel();

StackPanel.Orientation = System.Windows.Controls.Orientation.Vertical;

StackPanel.HorizontalAlignment = System.Windows.HorizontalAlignment.Right;

StackPanel.VerticalAlignment = System.Windows.VerticalAlignment.Center;

Button MyButton1 = new Button();

Button MyButton2 = new Button();

MyButton1.Content = "bouton1";

MyButton2.Content = "bouton2";

StackPanel.Children.Add(MyButton1);

StackPanel.Children.Add(MyButton2);

this.ContentPanel.Children.Add  (StackPanel);

On a instancié des boutons que l'on ajoute à la collection Children du StackPanel.

Enfin, on met donc le StackPanel dans une grille nommée ContentPanel.

On peut insérer un bouton3 supplémentaire à une position définie :

Button MyButton3 = new Button();
MyButton3.Content = "bouton3";
StackPanel.Children.Insert(1, MyButton3);  //1 indique la position d'insertion.

Le bouton2 est automatiquement repositionné.

VI-C-3 - Les Canvas

Il existe un contrôle Canvas qui permet de positionner des contrôles en indiquant leurs coordonnées (comme dans les Windows Forms), mais il n'y a pas de repositionnement automatique quand on modifie les dimensions du Canvas.

On positionne les contrôles avec les propriétés attachés Canvas.Top et Canvas.Left qui se trouvent dans le contrôle enfant.

En XAML :

<Canvas Margin="72,65,118,302">
<CheckBox Canvas.Top="80" Canvas.Left="0">Check Me</CheckBox>
<RadioButton Canvas.Top="0" Canvas.Left="80">Yes</RadioButton>
</Canvas>

Les Canvas ont une propriété attachée ZIndex permettant d'imposer l'ordre de superposition des éléments enfants (syntaxe dans l'élément enfant : Canvas.ZIndex = "2").

VI-C-4 - Les ScrollViewer

Représente une zone défilante qui peut contenir d'autres éléments visibles bien plus grands ; on fait défiler le contenu qui est dans le ScrollViewer au doigt, il apparaît des ScrollBarr si nécessaire.

Une utilisation habituelle est de mettre dans un petit ScrollViewer qui utilise la place disponible, un grand TextBlock, contenant un long texte.

<ScrollViewer Height="232" Width="448" HorizontalScrollBarVisibility="Auto" >
<TextBlock Width="530" TextWrapping="Wrap"
Text="texte très long...." />
</ScrollViewer>

HorizontalScrollBarVisibility et VerticalScrollBarVisibility permettent de forcer ou non l'affichage de la ScrollBarr ; ici il est à « Auto» .

Voici comment on positionne le ScrollViewer (qui reste sur l'écran) et le TextBlock interne (plus grand que l'écran) dans le designer :

Le ScrollViewer peut aussi être utilisé comme conteneur de tous les objets de la page ; si on passe en mode paysage les objets du bas de la page ne sont plus visibles mais on peut remonter la page pour en voir le bas.

VI-C-5 - WrapPanel

Le WrapPanel qui est un conteneur où les éléments sont mis à la suite l'un de l'autre mais où il y a "retour à la ligne" quand on atteint la limite du conteneur. Les éléments se positionnent de gauche à droite et de haut en bas.
Voir le chapitre sur le Toolkit.

VI-C-6 - Positionnement dynamique : les grandes règles

Layout dynamique : permet un positionnement quel que soit la résolution de l'écran.
On utilise un StackPanel ou surtout une Grid comme conteneur.
Dans les cellules, on met des contrôles avec les propriétés Width et Height= Auto. Ils occupent toutes la cellule (Margin permet de laisser un espace autour).
S'il y a un texte à afficher en totalité on donne une valeur à MinHeight et MinWidth pour que le texte ne soit pas tronqué.
On donne des dimensions relatives pour les colonnes et les lignes de la Grid (dans RowDefinition et ColumnDefinition) à l'aide des *.

VI-D - Les Boutons

VI-D-1 - Les Button

Pour ajouter un bouton, on clique sur le bouton dans les outils à gauche puis sur le formulaire, on appuie (bouton gauche de la souris), on déplace puis on lâche. Le bouton apparaît.

En bas à droite, on a la fenêtre de propriétés du bouton.

Le nom du contrôle est en haut, après « Name ».

Dans le designer, la propriété « Content » contient le texte à afficher sur le bouton.

Si on double-clique sur un bouton, par exemple, on se retrouve dans la fonction évènement correspondante qui est Button1_Click.

Créons un bouton avec du code XAML.

Il faut taper dans la fenêtre XAML :

<Button>Ok</Button>

Cela crée un bouton sur lequel est affiché « Ok ». Il apparaît en haut.

Faisons plus complet :

<Button Height="150" Margin="55,50,79,0" Name="Button1" VerticalAlignment="Top" Click="OnClick5">Ok</Button>

Voyons le détail de cette ligne :

La balise « Button » crée un bouton.

Name="Button1" indique le nom du contrôle dans le designer.

VerticalAlignment="Top" indique que le contrôle est ancré en haut (il reste toujours à la même distance du bord sur lequel il est ancré. Valeur possible : Top, Bottom, Center.

HorizontalAlignment="Left" même chose pour l'alignement horizontal.

Margin définit la distance par rapport aux bords du conteneur.

Width="" Height="" indiquent les dimensions du bouton (largeur, hauteur).

On peut aussi ajouter MinWidth MinHeight MaxWidth et MaxHeight qui indiquent les tailles minimales et maximales.

On aurait pu utiliser l'attribut Content="Ok" pour mettre un texte dans le bouton.

On peut positionner le « Content » dans le bouton en indiquant des alignements et marges internes (Padding) :

<Button Content="Cliquez ici"
Padding="70 100"
HorizontalContentAlignment="Right"
VerticalContentAlignment="Bottom" />

Click="OnClick5" indique que l'action de cliquer sur le bouton déclenche la fonction OnClick5.

Cela crée automatiquement, dans le code C#, la routine suivante :

private void OnClick5(object sender, RoutedEventArgs e)
{

}

sender est l'objet qui a déclenché l'évènement.

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

On peut ajouter du code dans la routine, pour modifier les propriétés du bouton, par exemple :

private void OnClick5(object sender, RoutedEventArgs e)
{
Button1.FontSize = 16;
Button1.Content = "Ok Ok..";
}

Mettre des formes dans un bouton puis y mettre une image.

<Button>
<StackPanel>
<Ellipse Height="40" Width="40" Fill="Blue"/>
<TextBlock TextAlignment="Center">Button</TextBlock>
</StackPanel>
</Button>

<Button>
<Rectangle Height="40" Width="40" Fill="Blue"/>
</Button>

<Button Height="130" Margin="9,362,0,0" Name="Button2" VerticalAlignment="Top" >
<Image Source="wphone.jpg"></Image>
</Button>

Cela donne :

Pour mettre une image dans un bouton il faut avoir le fichier image, l'ajouter au projet (menu « Projet » puis « Ajouter un élément existant ») ajouter Image et renseigner la source.

On peut choisir un fichier GIF avec un fond transparent ce qui permet de ne pas voir le fond de l'image.

Comment mettre un texte et une image dans un bouton ?

Il faut mettre un StackPanel dans le bouton (puisque celui-ci ne peut contenir qu'un seul objet), dans ce StackPanel mettre un TextBlock et une Image. Le faire en tapant du code XAML (dans le designer VB c'est difficile de mettre un StackPanel dans un Button, il se met dessus et pas dedans, donc copier/coller le code XAML). De plus l'image doit être dans les ressources.

<Button Height="170" Margin="31,377,-22,0" Name="Button2" VerticalAlignment="Top" >
<StackPanel Height="212">
<TextBlock HorizontalAlignment="Center">Ok</TextBlock>
<Image Source="wphone.jpg" Height="84" Width="96"></Image>
</StackPanel>
</Button>

Cela donne :

Voir aussi le chapitre sur les ressources.

Créons un bouton avec du code C# :

Button myButton = new Button();    //Création du bouton
myButton.Content = "ok";           //Afficher 'Ok' dans le bouton
myButton.Background = new SolidColorBrush(Colors.Blue);      //Fond en bleu
ContentPanel.Children.Add(myButton);   //Ajouter le bouton à la grid
myButton.Click += new RoutedEventHandler(buttonClick);//Indiquer que le click sur le bouton
//doit exécuter 'buttonClick'

private void buttonClick(object sender, RoutedEventArgs e)
{
MessageBox.Show("ok");
}                                      'doit exécuter la Sub nommée 'Click'

On remarque que, comme on crée par code, il faut soi même écrire la gestion des évènements.

Quand on crée le bouton en mode designer, et que l'on double-clique sur le bouton, la routine myButton_Click est automatiquement affichée.

VI-D-2 - HyperLinkButton

Représente un bouton qui affiche un lien hypertexte.

<HyperlinkButton Content="Cliquer ici pour voir Silverlight.net"
NavigateUri="http://www.silverlight.net" TargetName="_blank" Margin="10,60,0,0"/>

NavigateUri donne l'adresse.
TargetName a la valeur :
_blank, _media ou _search, cela charge le document dans une nouvelle fenêtre vide.
_parent, _self, _top, ou "" cela charge la page dans la fenêtre dans laquelle l'utilisateur a cliqué sur le lien (la fenêtre active).
L'effet n'est pas visible.

Si on met une adresse Internet, cliquer sur le lien ouvre le navigateur Web.

On peut aussi naviguer vers une autre page :

<HyperlinkButton NavigateUri="MySecondPage.xaml" />

VI-E - Les contrôles contenant du texte

Les contrôles permettant de voir ou de modifier du texte sont :

- les TextBlock ;

- les TextBox ;

- les RichTextBox ;

- les PasswordBox.

VI-E-1 - Les TextBlock

Permettent d'afficher du texte dans une page en lecture seule, sans possibilité de le modifier. Il est bien adapté pour afficher une ou au maximum quelques lignes.

Prendre un TextBlock dans les outils et le mettre dans un formulaire.

Pour mettre rapidement un petit texte dedans en C#, utiliser la propriété Text.

TextBlock1.Text= "Mon texte";

La même chose en XAML :

<TextBlock>
Mon texte
</TextBlock>

TextWrapping="Wrap"

Permet de passer automatiquement à la ligne.

On peut modifier le texte (Text), la hauteur des caractères (FontSize), la couleur du texte (Foreground) :

<TextBlock Name="TextBlock1"  Text="Hauteur:" FontSize="46" Foreground="Cyan" Height="55" Width="308" />

Un TextBlock a une propriété Foreground (couleur du texte) mais pas de Background (pas de couleur de fond), pas de bords.

Pour mettre une couleur de fond, il faut mettre le TextBlock dans un Rectangle.
Pour avoir un bord, mettre le TextBlock dans un Border (dans le TextBox mettre Margin="0").

<Border  Margin="12,189,0,352" BorderThickness="2" BorderBrush="Bisque">
<TextBlock Name="resultat"  HorizontalAlignment="Left" Margin="0"  TextAlignment="Center">
</TextBlock>
</Border>

On peut ajouter des sauts de lignes simplement grâce à \n.

TextBlock1.Text= "Mon texte  \n  Seconde ligne ";

On peut ajouter dans le code XAML plusieurs lignes (avec la balisee Run qui correspond à un morceau de texte) avec des couleurs, des tailles, des polices différentes et des retours à la ligne avec des LineBreak.

<TextBlock Height="151" HorizontalAlignment="Left" Margin="65,243,0,0" Name="textBlock4"
VerticalAlignment="Top" Width="290">
<LineBreak/>
<Run Foreground="Maroon" FontFamily="Courier New" FontSize="24">Courier New 24</Run>
<LineBreak/>
<Run Foreground="Teal" FontFamily="Times New Roman" FontSize="18" FontStyle="Italic">Times New Roman Italic 18</Run>
<LineBreak/>
<Run Foreground="SteelBlue" FontFamily="Verdana" FontSize="14" FontWeight="Bold">Verdana Bold 14</Run>
</TextBlock>

On peut le faire en C# :

using System.Windows.Documents;

TextBlock textlong = new TextBlock();
Run r = new Run();
r.Text = "mon texte";
r.FontSize = 20;
r.FontWeight = FontWeights.Bold;
r.Foreground = new SolidColorBrush(Colors.Orange);
textlong.Inlines.Add(r);
textlong.Inlines.Add(new LineBreak());

On déclare un « Run », on modifie ses propriétés ; on l'ajoute à la collection de lignes (InLines). On peut aussi ajouter un saut de ligne.

Exercice : j'ai une string mySting, je veux l'ajouter à un TextBlock avec des passages à la ligne et mettre la première ligne en orange.

string myString= "Titre:?Première ligne.?Seconde ligne.";

J'ai ajouté un caractère '¤' signifiant retour à la ligne.
Je vais découper la string grâce à Split puis avec une boucle je vais ajouter à la collection InLines un Run (un texte ) puis un LineBreak.

int i=0;
foreach (var line in myString.Split('?'))
{

if (i == 0)
{
var r = new Run();
r.Text = line;
r.FontSize = 20;
r.FontWeight = FontWeights.Bold;
r.Foreground = new SolidColorBrush(Colors.Orange);
TextLong.Inlines.Add(r);
TextLong.Inlines.Add(new LineBreak());
i++;
}
else
{
TextLong.Inlines.Add(line);
TextLong.Inlines.Add(new LineBreak());
}
}

VI-E-2 - Les TextBox

Permettent d'afficher du texte, il est modifiable par l'utilisateur.

La police de caractères, sa taille, la couleur, l'enrichissement des caractères affectent la totalité du texte. Il n'est pas possible d'enrichir (gras, italique…) une partie du texte seulement.

Créons un TextBox en XAML :

<TextBox></TextBox>

Donnons lui un nom :

<TextBox Name="TextBox1"></TextBox>

Mettons un texte dedans :

<TextBox Name="TextBox1">Ceci est le texte</TextBox>

En C#

TextBox1.Text="le texte";

TextBox1.Text contient le texte affiché dans la TextBox.

TextBox1.IsReadOnly=true  interdit les modifications du texte.

TextBox1.AcceptEnter=true  autorise le passage à la ligne quand on tape « Enter ». Si AcceptEnter=false, on ne peut saisir qu'une seule ligne.

VerticalScrollBarVisibility=true  affiche une ScrollBar verticale.

TextBox1.MaxLength permet de définir le nombre de caractères. 0 pour une saisie illimitée.

Si l'utilisateur a sélectionné du texte, il est dans TextBox1.SelectedText.

TextBox1.SelectionStart, TextBox1.SelectionLength indique la position du premier caractère sélectionné (le premier caractère du texte étant le caractère 0) et le nombre de caractères sélectionnés.
On peut utiliser ces propriétés pour sélectionner du texte avec du code ou utiliser TextBox1.Select(3, 2), il existe enfin SelectAll qui selectionne la totalité du texte.

Si le texte est modifié cela déclenche :

private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{

}

Double_cliquez sur le TextBox, cela fera apparaître le code qui précède.

Quand l'utilisateur frappe une touche cela déclenche les évènements KeyDown puis KeyUp (pas de keyPress !)

Pour ajouter la gestion de l'évènement KeyUp, cliquer sur le bouton. Dans la fenêtre des propriétés cliquer sur l'onglet « Évènements ».

Puis dans la liste double-cliquez sur « KeyUp » vous voyez apparaître :

<TextBox  Name="textBox1" Text="TextBox1"  KeyUp="textBox1_KeyUp" />

Et dans le code C# :

private void textBox1_KeyUp(object sender, KeyEventArgs e)
{

}

On note que la fonction a un paramètre e de type KeyEventArgs qui a la propriété Key qui contient la touche tapée. Malheureusement e.Key est en lecture seule ! On ne peut donc pas modifier le caractère tapé !
De plus le contenu de e.Key est bizarre ! Des expressions comme if (e.Key==Key.B)… ne fonctionnent pas car e.Key ne contient pas ce qui est attendu.

Pour modifier les caractères tapés on les modifie directement dans la propriété text :

private void textBox1_KeyUp(object sender, KeyEventArgs e)
{
TextBox TextBoxControl = (TextBox)sender;
string keys;
keys = "1234567890";

for (int j = 0; j < TextBoxControl.Text.Length; j++)
{
if (!(keys.Contains(TextBoxControl.Text.Substring(j, 1))))
{
TextBoxControl.Text = TextBoxControl.Text.Remove(j, 1);
TextBoxControl.SelectionStart = TextBoxControl.Text.Length;
}
}

}

Quand on tape sur une TextBox et qu'elle prend le focus, il apparaît automatiquement un clavier virtuel en bas.

Le clavier par défaut, si on n'indique rien est le clavier "Default".
Il permet de taper des minuscules et donne accès aux majuscules et aux chiffres :

Ce petit clavier à l'écran est appelé "panneau de saisie logiciel" ou SIP.

Mais on peut avoir besoin d'afficher un autre clavier virtuel.
Voici le clavier TelephoneNumber permettant de taper des numéros de téléphone :

Comment l'obtenir ?
En utilisant un InputScope.

<TextBox Name="entree1" InputScope="TelephoneNumber" Height="75"   Text="" >
</TextBox>

Ci dessous une syntaxe plus longue mais permettant d'avoir de l'aide à la saisie, la liste des valeurs s'affiche quand on saisit NameValue :

<TextBox Height="90" Name="textBox1" Text="" Width="197" Margin="79,105,181,412">
<TextBox.InputScope>
<InputScope>
<InputScopeName NameValue="Digits"></InputScopeName>
</InputScope>
</TextBox.InputScope>
</TextBox>

En C# :

InputScope scope = new InputScope();
InputScopeName name = new InputScopeName();

// Saisir des dates
name.NameValue = InputScopeNameValue.Date;

// Saisir un numéro de téléphone
name.NameValue = InputScopeNameValue.TelephoneNumber;

// Saisir un montant
name.NameValue = InputScopeNameValue.CurrencyAmount;

scope.Names.Add(name);
MyTextBox.InputScope = scope;

Il y a aussi DateDay, DateMonth, DateYear, Address, Chat, Digits, FileName, Number, OneChar, PostalCode, Text, Time, Url, Xml…
Voici quelques exemples :

Text : la première lettre tapée est en majuscule puis le clavier passe en minuscules.
Quand on tape au moins une lettre la zone au dessus du clavier propose des mots sur lesquels on peut taper.
En plus de ces suggestions de texte, il y a la correction automatique, l'auto-apostrophe, l'auto-accentuation et l'auto capitalisation.
Chat est le même clavier (contrairement à la version anglaise et a ce qui est dit par ailleurs).

Url :
permet de saisir des adresses Internet.
L'appui long sur « .com » donne accès à d'autres nom de domaine.

Digit :
pour saisir les chiffres de 0 à 9 et le point (même si on est en clavier français).
CurrencyAmmont et Number sont le même clavier en français.

CurrencyAmountAndSymbol :
permet de saisir chiffres et symboles.

Contrairement à ce qui est dit ailleurs les inputscope sont différents de ceux de la version anglaise.
Sous Mango, l'InputScope « Number », représente maintenant uniquement un clavier numérique, alors qu'auparavant c'était le clavier chiffre+symboles.

En cours de saisie, il ne faut pas oublier de filtrer les caractères tapés (voir chapitre « Comment faire »).

VI-E-3 - Les RichTextBox

Rich Text veut dire Texte enrichi.
Le contrôle RichTextBox permet d'afficher, d'entrer et de manipuler du texte mis en forme. Il effectue les mêmes tâches que le contrôle TextBox, mais il peut également afficher des polices, des couleurs pour une partie du texte et des liens, charger du texte et des images incorporées à partir d'un fichier, ainsi que rechercher des caractères spécifiques.

En Wp RichTextBlox est ReadOnly, donc pas de possibilité d'écrire dedans ! ce qui lui enlève son intérêt.

Ce contrôle n'apparaît pas dans la liste d'outils ; il faut l'ajouter :

Dans la boîte à outils, clic droit sur « Windows Phone Controls », puis clic sur la ligne « Choisir les éléments ».

Là on peut cocher le contrôle « RichTextBox » à ajouter dans la liste de la boîte à outils.

En XAML :
on peut ajouter un texte dans la balise Paragraph, l'enrichir avec les balises Bold, Italic, Underline, Hyperlink.
On peut aller à la ligne avec LineBreak.

<RichTextBox Name="rtb" Height="169" HorizontalAlignment="Left" Margin="42,6,0,0"  VerticalAlignment="Top" Width="348">
<Paragraph>
Un RichTextBox avec du texte en <Bold> gras </Bold> en
<Italic>italique</Italic> en
<Underline>souligné</Underline>
<LineBreak/>
<Hyperlink NavigateUri="http://search.msn.com">avec un lien"</Hyperlink>.
</Paragraph>
</RichTextBox>

Noter la syntaxe du lien qui montre que dans la balise on peut ajouter des attributs.
On va voir un autre exemple avec un Border et l'utilisation d'attributs pour modifier la couleur et la taille des caractères :

<RichTextBox Name="rtb" Height="339" HorizontalAlignment="Left" Margin="42,6,0,0"
VerticalAlignment="Top" Width="348" BorderThickness="4">
<RichTextBox.BorderBrush>
<SolidColorBrush Color="Red" />
</RichTextBox.BorderBrush>

<Paragraph>
<Run Foreground="Red">RichTextBox avec du texte en rouge</Run>
<LineBreak/>
<Run FontSize="50">C'est beau.</Run>
</Paragraph>
</RichTextBox>

Cela donne :

En C# c'est un peu plus difficile :
On crée des « Run » (portion de texte) qu'on met dans la collection Inlines des « Paragraph ». Ensuite on met les paragraphes dans la collection Blocks du RichTextBox.

//Créer un RichTextBox
RichTextBox MyRTB = new RichTextBox();
//VertalScrollBar à auto
MyRTB.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;

// Créer deux 'Run' de texte et un 'Bold' avec du texte en gras.
Run myRun1 = new Run();
myRun1.Text = "Un RichTextBox avec ";
Bold myBold = new Bold();
myBold.Inlines.Add("du texte en gras ");
Run myRun2 = new Run();
myRun2.Text = "dedans.";

// Créer un 'Paragraph' et y ajouter les 2 Run et le Bold.
Paragraph myParagraph = new Paragraph();
myParagraph.Inlines.Add(myRun1);
myParagraph.Inlines.Add(myBold);
myParagraph.Inlines.Add(myRun2);

// Ajouter le paragraph au RichTextBox.
MyRTB.Blocks.Add(myParagraph);

//Ajouter le RichTextBox à la grille.
ContentPanel.Children.Add(MyRTB);

VI-E-4 - Les PasswordBox

Permet de saisir un mot de passe.

La propriété PasswordChar détermine le caractère affiché à la place des caractères tapés.

En XAML :

<PasswordBox Name="pwdBox" MaxLength="64" PasswordChar="#" PasswordChanged="PasswordChanged" />

Ici le fait de taper un mot de passe déclenche la fonction PasswordChanged.

En C# on récupère le mot de passe dans :

pwdBox.Password

VI-F - Les cases à cocher et RadioButton

VI-F-1 - Case à cocher

Créons une case à cocher.

C'est une CheckBox. L'utilisateur peut la cocher ou non en cliquant dessus.

Dans le designer, la propriété Content contient le texte à afficher à côté de la case.

Les évènements les plus utiles sont  : Checked et Unchecked.

<!-- CheckBox à 2 états -->
<CheckBox x:Name="cb1" Content="CheckBox à 2 états"
Checked="HandleCheck" Unchecked="HandleUnchecked" Margin="5" />
<TextBlock x:Name="text1" Margin="5" />

<!-- CheckBox à 3 états -->
<CheckBox x:Name="cb2" Content="CheckBox à 3 états"
IsThreeState="True" Checked="HandleCheck"
Indeterminate="HandleThirdState" Unchecked="HandleUnchecked" Margin="5" />
<TextBlock x:Name="text2" Margin="5" />

private void checkBox1_Checked(object sender, RoutedEventArgs e)
{

}

Dans le code XAML IsChecked="True" permet de cocher un CheckBox. Dans le code C#, la propriété IsChecked permet de voir si la case est cochée ou non.

if (MyradioButton1.IsChecked == true)
{ conv = 0.6; }

VI-F-2 - RadioButton

Les RadioButton peuvent être cochés ou non.

Ils sont généralement regroupés pour offrir aux utilisateurs un choix unique parmi plusieurs options, un seul bouton à la fois peut être coché. Si on clique sur un RadioButton dans un groupe, on le sélectionne, cela désélectionne les autres.

Vous pouvez regrouper des contrôles RadioButton en les plaçant dans un parent commun ou en leur attribuant un nom de groupe (GroupName).

Quand un RadioButton est sélectionné, l'événement Checked est déclenché. Comme le montre l'exemple de code suivant, si votre application doit faire une action quand la sélection de RadioButton change, vous pouvez gérer l'événement Checked.

<RadioButton Name="MyRadioBUtton"
IsChecked="True"
Checked="Myroutine"
GroupName="MyGroupe">
Texte
</RadioButton>

VI-G - Les ListBox

Une ListBox est un contrôle qui contient une collection de ListBoxItem. Chaque ListBoxItem a une propriété « Content ».

Créons une ListBox avec trois ListBoxItem en XAML :

<ListBox Height="176" HorizontalAlignment="Left" Margin="19,27,0,0" Name="listBox1" VerticalAlignment="Top" Width="400">
<ListBoxItem Content="Paris" />
<ListBoxItem Content="Lyon" />
<ListBoxItem Content="Marseille" />
</ListBox>

Créer en C# une ListBox puis ajouter des éléments à la ListBox :

ListBox ListBox1 = new ListBox();
ListBox1.Items.Add("Paris");
ListBox1.Items.Add("Lyon");
ListBox1.Items.Add("Marseille");
ContentPanel.Children.Add(ListBox1);

On peut créer en C#, une collection « Villes », y ajouter des éléments et ensuite faire un binding :

List<string> Villes = new List<string>
{
"Paris",
"Lyon",
"Marseille"
};
listBox1.ItemsSource = Villes;

Dans ce cas dans le code XAML ItemsSource doit indiquer qu'il y a binding.

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

On peut effacer la liste.

ListBox.Items.Clear();

SelectedIndex indique l'index de l'élément sélectionné (-1 si pas de sélection).
SelectedItem indique l'item (objet) sélectionné. On peut l'utiliser pour forcer par code la sélection.

ListBox.SelectedItem = ListBox.Items[2];

On peut ajouter un bord autour du ListBox :

<ListBox  BorderThickness="5" BorderBrush="Red"  />

Maintenant on veut personnaliser chaque Item.
Pour cela, on va utiliser un style pour les ListBoxItem.
On va créer dans les ressources de la grille racine un style pour les ListBoxItem ; ce style définira un template qui affiche un bord pour chaque Item.
Enfin on créera la ListBox en utilisant pour chaque ListBoxItem le style précédent.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.Resources>
<Style x:Key="ListBoxTemplate" TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border BorderThickness="3" BorderBrush="Azure" Height="50" Margin="4">

<ContentPresenter  HorizontalAlignment="Center"
VerticalAlignment="Center" Content="{TemplateBinding Content}"/>

</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

</Grid.Resources>
<ListBox  BorderThickness="5" BorderBrush="Red"  Height="192" HorizontalAlignment="Left" Margin="19,27,0,0"
Name="listBox1" VerticalAlignment="Top" Width="400" SelectionChanged="listBox1_SelectionChanged">

<ListBoxItem Content="Paris"  Style="{StaticResource ListBoxTemplate}" />
<ListBoxItem Content="Lyon"  Style="{StaticResource ListBoxTemplate}" />
<ListBoxItem Content="Marseille"  Style="{StaticResource ListBoxTemplate}" />
</ListBox>
</Grid>
</Grid>

On remarque que le Template du ListBoxItem contient dans le Border un ContentPresenter ; dans ce ContentPresenter, on indique que le Content doit être lié au content du ListBoxItem.

On peut aussi modifier l'item template pour, au lieu d'afficher les items les uns au dessus des autres, les afficher de gauche à droite avec passage à la ligne (comme dans le Media Library). Il faut pour cela utiliser un WrapPanel (voir dans le chapitre sur le Toolkit).

Mettre des boutons, des cases à cocher dans une ListBox :

Les CheckedListBox n'existent pas en WP ? On va les créer dans une ListBox1 :

<ListBox  BorderThickness="5" BorderBrush="Red"  Height="192"  Name="listBox2"
SelectionChanged="listBox1_SelectionChanged">
<CheckBox Content="Option1"></CheckBox>
<CheckBox Content="Option2"></CheckBox>
</ListBox>

De la même manière, on peut créer une liste de boutons…

Utilisation du binding sur une ListBox.

On reprend l'exemple du chapitre sur le binding en le complétant.
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 !
C'est normal elle affiche ce que retourne la méthode ToString().

On a une première méthode pour éviter l'affichage du type : on surcharge la méthode ToString de la classe ElementdeListe :

public override string ToString()
{
return Titre + " - " + Formule;
}

Cela marche bien mais uniquement avec du texte.

Une autre méthode (plus générale) pour afficher Titre et Formule, on va ajouter un ItemTemplate contenant un StackPanel horizontal qui contient trois TextBlock. Le premier et le troisième sont liés au 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>

Utilisation d'ItemTemplate complexe.

On veut afficher pour chaque Item une petite sphère, la propriété « Titre » en gros et la propriété « Formule » dessous en plus petit :

<ListBox Name="listBox1"  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 Titre et Formule-->
<TextBlock Foreground="Azure" Text="{Binding Titre}" TextWrapping="Wrap"
Style="{StaticResource PhoneTextLargeStyle}"   />
<TextBlock Text="{Binding Formule}" TextWrapping="Wrap" Margin="12,-6,12,0"
Style="{StaticResource PhoneTextSubtleStyle}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Bien respecter les majuscules et minuscules dans les liaisons car si on se trompe d'une lettre, rien ne signale une erreur mais cela ne marche pas.

On voit que l'ItemTemplate est beaucoup plus puissant, on peut afficher dans la ListBox du texte mais aussi des images…

Et les évènements !

Il est indiqué partout d'utiliser l'évènement SelectionChanged :

<ListBox ItemsSource="{ Binding}" Height="193" HorizontalAlignment="Left"
Name="listBox1"  SelectionChanged="ListBoxSelChange"/>

Ainsi quand l'utilisateur tape sur une ligne de la ListBox l'évènement SelectionChanged se produit et la routine suivante est exécutée.

private void ListBoxSelChange(object sender, RoutedEventArgs e)
{
if (e.AddedItems.Count > 0)
string TexteSelectionné = e.AddedItems[0].ToString();
}

L'inconvénient est que si je tape deux fois de suite sur le même item de la ListBox, la seconde fois ne déclenche rien puisque la sélection n'a pas changé.

J'utilise plutôt MouseLeftButtonUp.

<ListBox ItemsSource="{Binding}" Height="193" HorizontalAlignment="Left"
Name="listBox1" MouseLeftButtonUp="ListBox_Appel"/>

Quand l'utilisateur tape sur une ligne de la ListBox l'évènement MouveLeftButtonUp se produit et la routine suivante est exécutée.
Je peux utiliser l'item sélectionné : SelectedItem.

private void ListBoxConversion_Appel(object sender, RoutedEventArgs e)
{//sender est l'objet qui a déclenché l'évènement, je le cast en listbox
ListBox list = sender as ListBox;
//SelectedItem est aussi un objet qui doit être casté
ElementDeListe  ItemSelectionne = list.SelectedItem as ElementDeListe;
if (ItemSelectionne == null)
return;
}
//....Ici on peut utiliser ItemSelectionne
}

VI-H - Les Slider

Un Slider est un curseur qu'on peut déplacer avec le doigt :

<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>

Ici on utilise un Slider pour modifier la hauteur d'un rectangle grâce au binding.

VI-I - Les MessageBox

Comment afficher un message pour l'utilisateur (sans sortir de la page) ?

MessageBox.Show("Erreur sur le format de la date");

Cela donne :

Il y a un bouton « Ok » par défaut pour sortir du MessageBox.

Sinon on peut ajouter un titre et un bouton « Annuler ».

MessageBox.Show("Poursuivre?", "Exemple MessageBox", MessageBoxButton.OKCancel);

MessageBox peut retourner dans une variable de type MessageBoxResult le résultat de l'action de l'utilisateur :
A-t-il tapé sur Ok ou Annuler ?

MessageBoxResult.Ok
ou
MessageBoxResult.Cancel
sera retourné suivant le bouton qui a été tapé par l'utilisateur.

MessageBoxResult result = MessageBox.Show("Poursuivre?",
"Exemple MessageBox", MessageBoxButton.OKCancel);

if (result == MessageBoxResult.OK)
{
MessageBox.Show("Vous avez tapé sur ok.");
}

En WP7 il n'y a que deux boutons possibles.
Les InputBox n'existent pas.

VI-J - Image, Vidéo, Son

On peut ajouter un contrôle « Image ».

Comment mettre une image dans un contrôle image ?
En C# :
(image qui est sur Internet puis image locale) :

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

//Image sur Internet
//On instancie un Uri adresse internet d'une image
Uri uri = new Uri("http://www.MonSite/MonImage.jpg");

//On instancie un BitmapImage on charge l'image
BitmapImage bmp = new BitmapImage(uri);

//On met le Bitmap dans le contrôle image
Image.Source = bmp;

//Image locale (avec comme 'option de génération'= 'Contenu')
Image.Source = new BitmapImage(new Uri("MyImage.jpg", UriKind.Relative));

//Image locale (avec comme 'option de génération'= 'Resource')
Image.Source = new BitmapImage(new Uri("NomProjet;component/MyImage.jpg", UriKind.Relative));

Dans le code XAML :

<Image x:Name="Image" Source="/Images/MyImage.jpg" Width="60" Height="60" />

Ici l'image (option de génération = Contenu) est dans un répertoire « Images » du projet.

Si l'image n'existe pas, il n'y a pas de levée d'exception. Pas d'arrêt du logiciel avec un code d'erreur. Cool !

Par contre, si on veut, on peut intercepter l'erreur en s'abonnant à l'évènement ImageFailed.

L'image doit être mise dans le projet : menu « Projet », « Ajouter un élément existant », choisir l'image puis « Ok », elle apparaît dans la fenêtre de l'explorateur de solution à droite.
Dans ses propriétés en bas à droite, il faut mettre « Option de génération » à « Contenu » ou le laisser à « Resource » (et Copier… à « Copier si plus récent »).

On se souvient que le chemin de l'application est « C:\Users\Philippe\Documents\Visual Studio 2010\Projects\Mon application ».

Utiliser des fichiers JPEG plutôt que des PNG (le décodage des PNG se fait au niveau logiciel alors que celui des JPEG se fait au niveau matériel).
Par contre pas de transparence dans les JPEG.
Pas de GIF dans WP.

On peut donner un chemin absolu :

<Button  HorizontalAlignment="Left" Margin="4,0,0,0" Name="Button1" VerticalAlignment="Top" Width="150"  Height="100">
<Image Source="C:/test.JPG" />
</Button>

On peut aussi utiliser les ressources pour mettre l'image (voir chapitre Ressources).

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

<Image Source="C:/test.JPG" Opacity="0.5"/>

L"image est semi-transparente.

L'attribut Stretch permet de gérer les dimensions de l'image ; par défaut elle a la valeur « Uniform », ainsi la totalité de l'image rentre dans le contrôle sans déformation.
'UniformToFill' conserve les proportions mais ne force pas la totalité de l'image a être affichée.

'Fill' adapte l'image au conteneur, mais il peut y avoir des déformations.
« None » respecte les dimensions en pixel de l'image, l'image peut être plus petite ou plus grande que le conteneur ; on peut ne pas la voir en totalité.

<Image Margin="10,10,10,10" Source="fr.png" Stretch="UniformToFill" />

StretchDirection = UpOnly ne permet que l'agrandissement, DownOnly permet le contraire. Par défaut sa valeur est égale à Both.

On peut appliquer une transformation à une image (ici une rotation de 90 degrés) :

<Rectangle Width="200" Height="100" Stroke="Black" StrokeThickness="1">
<Rectangle.Fill>
<ImageBrush Stretch="UniformToFill">
<ImageBrush.ImageSource>
<BitmapImage UriSource="sampleImages\myimage.jpg" />
</ImageBrush.ImageSource>
<ImageBrush.RelativeTransform>
<RotateTransform CenterX="0.5" CenterY="0.5" Angle="90" />
</ImageBrush.RelativeTransform>
</ImageBrush>
</Rectangle.Fill>
</Rectangle>

Pour le son, la vidéo : on peut lire des Windows Media Video (WMV), Windows Media Audio (WMA), et fichier MP3.

<MediaElement Name="media"
Source="silverlight.wmv" />

On peut arrêter, mettre en pause, démarrer une vidéo :

private void StopMedia(object sender, RoutedEventArgs e)
{
media.Stop();
}
private void PauseMedia(object sender, RoutedEventArgs e)
{
media.Pause();
}
private void PlayMedia(object sender, RoutedEventArgs e)
{
media.Play();
}

On peut ajouter une propriété « Volume » avec une valeur allant de 0 à 1 (défaut=0.5) en échelle logarithmique.

On peut aussi ajouter une propriété « AutoPlay », elle est à true par défaut. Il y a aussi Stretch et IsMuted.

VI-K - Formes

Le rectangle

<Rectangle Fill="Blue" Stroke="Red"  StrokeThickness="3"  Width="97" Heightc="20"/>

Fill : couleur de remplissage.
Stroke : couleur du bord.
StrokeThickness : épaisseur du bord.
RadiusX et RadiusY : arrondir les angles.

L'ellipse

exemple : une sphère, on remplit un cercle (une ellipse aussi haute que large) 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>

On peut ajouter un bord autour d'un conteneur (ou d'un contrôle) :

<Border BorderBrush="White" BorderThickness="2"  CornerRadius="20" Height="47">
<TextBlock  Margin="2,4">Rectangle</TextBlock>
</Border>

L'attribut CornerRadius permet d'arrondir les angles.

Voici un exemple en code c# :

Ellipse monEllipse = new Ellipse();
monEllipse.Stroke = new SolidColorBrush(Colors.Cyan);
monEllipse.Fill = new SolidColorBrush(Colors.DarkGray);
monEllipse.HorizontalAlignment = HorizontalAlignment.Left;
monEllipse.VerticalAlignment = VerticalAlignment.Center;
monEllipse.Width = 70;
monEllipse.Height = 95;
ContentPanel.Children.Add(monEllipse);

On remarque que pour modifier le bord, le tour de la forme, on utilise Stroke (couleur) et StrokeThickness (épaisseur).

Polygone
On définit des points, il y a une ligne entre un point et le suivant, le dernier point étant relié au tout premier.

<Polygon
Fill="Blue"
Stroke="Black"
StrokeThickness="2"
Points="10,200,60,140,130,140,180,200" />

Il y a une collection « PointCollection » dans lequel vous ajoutez les « Point » pour former la collection.
Polylines fait de même sauf que le dernier point n'est pas relié au premier.

Path (chemin)
Permet de dessiner un chemin complexe :

<Path Stroke="Pink" StrokeThickness="4"
Data="M 140,200 C 100,25 400,350 400,175 V 140 H 140" />

Data contient :
M : coordonnées (absolu) du point de départ ('m' si coordonnées relative) ;
C : courbe de Bézier commençant à la position de départ, se terminant à 400, 175 (2 dernières valeurs) ;
Passant pas les points 100,25 et 400,350 ;
V : ligne vertical se terminant à l'ordonnée 140 ;
H : ligne horizontale se terminant à l'abscisse 140.

Exemple plus complexe : dans un bouton, dessiner une loupe, l'agrandir grâce à RenderTransform :

<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/>

Line
Elle va du point X1,Y1 à X2,Y2.

En XAML :

<Line  StrokeThickness="4" X1="150" Y1="150" X2="100" Y2="100" />

En C# :

maLine = new Line();
maLine.Stroke = System.Windows.Media.Brushes.Blue;
maLine.X1 = 2;
maLine.X2 = 55;
maLine.Y1 = 1;
maLine.Y2 = 60;
maLine.HorizontalAlignment = HorizontalAlignment.Left;
maLine.VerticalAlignment = VerticalAlignment.Center;
maLine.StrokeThickness = 2;
maGrid.Children.Add(myLine);

VI-L - Barre System en haut

Le SystemTray est la bande en haut qui contient les icônes de charge, de réseau, de type de réseau, l'heure.

Elle fait 32 pixels en vue portrait et 72 pixels en vue paysage. En mode paysage la barre est sur le rebord gauche de l'écran et pas en haut.
Par défaut, elle est blanche ou noire en fonction du thème. La grille « LayoutRoot » commence dessous.

Dans le XAML, dans la balise « PhoneApplicationPage », on charge l'espace de noms « Microsoft.Phone.Shell » (il l'est pas défaut).
Puis on peut modifier le SystemTray.

On peut ne pas afficher le SystemTray.

<phone:PhoneApplicationPage
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
..
shell:SystemTray.IsVisible="False">

Il est possible de modifier la couleur des icônes et du fond. Cela ne marche qu'à partir de WP7.1 (Mango).

shell:SystemTray.ForegroundColor="Red"
shell:SystemTray.BackgroundColor="Blue"

Il y a un bug et pour mettre du blanc il faut écrire : shell:SystemTray.ForegroundColor="#FEFEFE".

On peut modifier l'opacité (de 0 à 1).

shell:SystemTray.Opacity="0.5">

L'opacité n'a pas d'incidence sur les icônes.
Si on modifie l'opacité, la page débute sur le bord de l'écran (en arrière du SystemTray) on voit donc la page par transparence à travers le SystemTray.

Sinon la page débute en dessous du SystemeTray.

En C# on peut modifier l'opacité de la bande (0 à 1) et les couleurs :

using Microsoft.Phone.Shell;

SystemTray.SetIsVisible(this, false);
SystemTray.SetOpacity(this, 0.5);
SystemTray.SetBackgroundColor(this, Colors.Purple);
SystemTray.SetForegroundColor(this, Colors.Yellow);

Ici on indique la page en premier paramètre.
Il existe aussi GetIsVisible… pour lire la propriété.

Quand on a un arrière fond clair et si on a modifié la taille de la Grid LayoutRoot, cela fait une bande blanche en haut ; pour éviter cela, il ne faut pas afficher le System Tray et surtout ne pas donner de hauteur à la grille, ainsi elle occupera tout l'espace (on peut donner, sans le vouloir, une valeur en déplaçant par erreur la grille) :

shell:SystemTray.IsVisible="False"

<!--LayoutRoot est la grille racine où tout le contenu de la page est placé-->
<Grid x:Name="LayoutRoot" >

On peut aussi ajouter un ProgressIndicator.
Si IsIndeterminate = true cela affiche les points qui bougent de gauche à droite.
Si IsIndeterminate = false cela affiche une barre rouge de gauche à droite. La valeur de « Value » donne donne la longueur de la barre : 0= pas de barre, 1= barre complète.

public partial class MainPage : PhoneApplicationPage
{
ProgressIndicator prog;
// Constructeur
public MainPage()
{
InitializeComponent();

prog = new ProgressIndicator();

SystemTray.SetIsVisible(this, true);
SystemTray.SetBackgroundColor(this, Colors.Gray);
SystemTray.SetForegroundColor(this, Colors.Yellow);

prog.IsVisible = true;
prog.IsIndeterminate = true; //Affiche les points qui bougent de gauche à droite
//false nécessite de donner des valeurs croissantes à 'Value'
prog.Text = "Clicker...";

SystemTray.SetProgressIndicator(this, prog);

}

VI-M - Application Barre

L'application Barre permet de mettre une barre en bas avec des icônes (quatre maximum) et même d'ajouter un menu qu'on ouvre (avec les trois petits points).
Le code de l'ApplicationBar est déjà donné par Microsoft (il est en commentaire en bas de la page), j'ai ajouté la gestion des clics.

<!--Exemple de code illustrant l'utilisation d'ApplicationBar-->
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBarIconButton IconUri="ApplicationBar.Cancel.png" Text="Annuler" Click="bouton1"/>
<shell:ApplicationBarIconButton IconUri="ApplicationBar.Check.png" Text="Ok" Click="bouton2"/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text="Menu 1" Click="menu1"/>
<shell:ApplicationBarMenuItem Text="Menu 2" Click="menu2"/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

</phone:PhoneApplicationPage>

private void menu2(object sender, EventArgs e)
{
MessageBox.Show("menu2");
}

private void menu1(object sender, EventArgs e)
{
MessageBox.Show("menu1");
}

private void bouton1(object sender, EventArgs e)
{
MessageBox.Show("bouton1");
}

private void bouton2(object sender, EventArgs e)
{
MessageBox.Show("bouton2");
}

L'option de génération des fichiers images doit avoir la valeur « Contenu » et l'option « Copier dans… » la valeur « Copier si plus récent ».
Les icônes n'apparaîssent pas dans l'IDE, il faut tester avec l'émulateur.

On peut trouver des icônes dans le SDK, ici "C:\Program Files\Microsoft SDKs\Windows Phone\v7.1\Icons\dark", utiliser uniquement le répertoire dark, WP se chargera d'adapter l'image au thème.
On peut en trouver aussi sur Internet ou les dessiner soi-même. Microsoft conseille des images de 48 x 48 px avec le fond graphique au centre de 26 x 26 px. Couleur d'avant plan blanche et fond transparent ; pas de cercle qui sera dessiné par WP sur le bouton.

Si on veut désactiver le second bouton dans le code-behind :

//Inclure l'espace de noms
using Microsoft.Phone.Shell;

(this.ApplicationBar.Buttons[1] as ApplicationBarIconButton).IsEnabled = false;

VI-N - AdControl pour la publicité

À partir de Mango, on peut ajouter dans une application de la publicité rémunérée proposée par Microsoft.
Aller sur https://pubcenter.microsoft.com pour s'inscrire.
Une fois inscrit vous devrez choisir la taille de votre bannière (480*80 ou 300*50) et vous obtiendrez deux identifiants (ApplicationId et AdUnitId).

À partir de la boîte à outils glisser/déposer un contrôle AdControl :

Cela donne dans l'XAML :

<my:AdControl AdUnitId="Image480_80" ApplicationId="test_client" Height="80"  Margin="-12,179,0,0"
Name="adControl1"  Width="480" />

Bien mettre les Id récupérés sur le site.
Le contrôle ici a une taille de 480*80.
Il est impératif de respecter la taille et de ne pas modifier l'opacité.

VI-O - Le Toolkit

Le Silverlight Toolkit pour Windows Phone est un projet open source où l'équipe Microsoft Silverlight partage de nouveaux composants et fonctionnalités qui ne sont pas dans le noyau de Silverlight pour Windows Phone.

Le Toolkit ajoute donc des contrôles indispensables.

Se rendre sur le site codeplex :

http://silverlight.codeplex.com/.

Charger Silverlight for Windows Phone Toolkit pour Mango 7.1.

Ajouter la référence (la DLL) du ToolKit au projet. Dans l'explorateur de solution à droite, clic droit sur référence puis dans le menu clic sur « Ajouter une référence ».

Puis cliquer sur « Microsoft.Phone.Controls.ToolKit ».

Dans le code XAML ajouter l'espace de noms « Microsoft.Phone.Controls.Toolkit » :

xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"

Maintenant on peut ajouter un contrôle du ToolKit directement dans le code XAML :

<toolkit:DatePicker />

On peut si on le désire ajouter le contrôle dans la boîte à outils. Dans celle-ci, click droit sur « Windows Phone Controls », puis dans le menu « Choisir les éléments ».

Là on peut cocher le contrôle à ajouter dans la liste de la boîte à outils.

Le ToolKit (version d'octobre 2011) fournit les contrôles suivant :
AutoCompleteBox, ContextMenu, DateTimePicker, DateTimeConverters, ExpanderView, GestureService/GestureListener, HeaderedItemsControl, HubTile, ListPicker, LocalizedResources, LockablePivot, LongListSelector, MultiselectList, Page Transitions, PerformanceProgressBar, PhoneTextBox, TiltEffect, TimePicker, ToggleSwitch, WrapPanel, LockablePivots .

AutoCompleteBox permet d'avoir une TextBox proposant une liste de mots dès qu'on tape des lettres.

<toolkit:AutoCompleteBox Margin="25,42,0,0" Name="autoCompleteBox1"  />

On va charger la liste des mots dans le code-behind :

List<string> villes = new List<string>();
villes.Add("Barcelona");
villes.Add("Bélize");
villes.Add("Beaune");
villes.Add("Londres");
villes.Add("Paris");
villes.Add("Prague");
villes.Add("Madrid");
villes.Add("Rome");
this.autoCompleteBox1.ItemsSource = villes;

On peut modifier quelques propriétés :

//La liste apparaît après la frappe de 3 caractères
this.autoCompleteBox1.MinimumPrefixLength = 3;

//Tient compte de la casse de la première lettre
this.autoCompleteBox1.FilterMode = AutoCompleteFilterMode.StartsWithCaseSensitive;

On peut ajouter un filtre, afficher des images… voir le libre blanc.

Le DatePicker permet de saisir une date.

<toolkit:DatePicker x:Name="datePicker" Value="2/4/2012"/>

En C# :

using Microsoft.Phone.Controls;
DatePicker datePicker = new DatePicker();

On a dans la page ce contrôle :

Quand on clique dessus, la page de saisie de date s'ouvre :

Il faut mettre dans le répertoire « Toolkit.Content » les fichiers « ApplicationBar.Cancel.png » et « ApplicationBar.Check.png ».

Où trouver les fichiers ? Aller sur le site http://silverlight.codeplex.com/releases. Dans « Other Available Downloads » charger le ZIP « Source Code Silverlight for Windows Phone Toolkit Source and Sample - Nov 2011.zip ». Dans le ZIP les fichiers PNG des icônes sont dans « Source and Samples\PhoneToolkitSample\Toolkit.Content ».

Il faut créer avec l'explorer, un répertoire « Toolkit.Content » à la racine du projet et y mettre les icônes « ApplicationBar.Cancel.png » et « ApplicationBar.Check.png » puis ajouter le répertoire au projet (dans l'explorateur de solution, clic sur l'icône « Afficher tous les fichiers », clic droit sur le répertoire puis « Ajouter », « Ajouter le répertoire ») ; enfin dans les propriétés de chaque fichier image, « Action de génération » doit avoir la valeur « contenu ». Copier doit avoir la valeur « Copier si plus récent ».

ToggleSwitch est un interrupteur :

<toolkit:ToggleSwitch x:Name="toggle" Content="ToggleSwitch est on"
IsChecked="True" Header="DemoToggleSwitch"/>

Ici on le met à On et on ajoute un petit titre au-dessus :

En C# :

ToggleSwitch toggleSwitch = new ToggleSwitch();

On peut ajouter les EventHandler « Checked » et « Unchecked » et les fonctions correspondantes qui modifient le texte et la couleur du contrôle en fonction de son état « on » « off» :

namespace PhoneApp2
{
public partial class MainPage : PhoneApplicationPage
{
// Constructeur
public MainPage()
{
InitializeComponent();
this.toggle.Checked += new EventHandler<RoutedEventArgs>(toggle_Checked);
this.toggle.Unchecked += new EventHandler<RoutedEventArgs> (toggle_Unchecked);
}
void toggle_Unchecked(object sender, RoutedEventArgs e)
{
this.toggle.Content = "ToggleSwitch est off";
this.toggle.SwitchForeground = new SolidColorBrush(Colors.Red);
}
void toggle_Checked(object sender, RoutedEventArgs e)
{
this.toggle.Content = "ToggleSwitch est on";
this.toggle.SwitchForeground = new SolidColorBrush(Colors.Green);
}

}
}

On peut simplement tester l'état du ToggleSwitch :

if (this.toggle.IsChecked == true)
{ MessageBox.Show("On"); }

Dans le Toolkit il y a aussi le WrapPanel qui est un conteneur où les éléments sont mis à la suite l'un de l'autre mais où il y a "retour à la ligne" quand on atteint la limite du conteneur.
Les éléments se positionnent de gauche à droite et de haut en bas.

C'est pratique pour afficher une série de photos comme dans la Media Library de WP.
Exemple du blog d'Audrey Petit, il faut rajouter les binding.
Le template par défaut d'une ListBox n'est autre qu'un StackPanel avec une orientation verticale.
C'est pour cela que les items d'une ListBox se positionnent par défaut les uns en dessous des autres. Nous allons donc modifier ce template afin de lui faire utiliser un WrapPanel et positionner nos éléments les uns à côté des autres, avec passage à la ligne lorsqu'une ligne est complète.

xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"

<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="DataTemplatePhoto">
<Grid Width="150" Height="150">
<Image x:Name="imageItem" Margin="5"/>
</Grid>
</DataTemplate>
<ItemsPanelTemplate x:Key="PanelTemplatePhoto">
<toolkit:WrapPanel />
</ItemsPanelTemplate>
</phone:PhoneApplicationPage.Resources>

<ListBox x:Name="collectionPhoto" ItemTemplate="{StaticResource DataTemplatePhoto}"
ItemsPanel="{StaticResource PanelTemplatePhoto}" />

La Toolbox permet aussi d'ajouter des effets comme le 'Tilt' effect, effet d'appui sur un élément tapé. Cela rend visible les contrôles tapés par le doigt.

<ListBox toolkit:TiltEffect.IsTiltEnabled="True">

Le Toolkit ajoute aussi une gestion des gesrures (voir chapitre sur la saisie tactile et les gestures).

Consulter la bible (en anglais) du ToolKit, il y a tout en détail :http://www.windowsphonegeek.com/upload/ebooks/Windows%20Phone%20Toolkit%20Aug%202011%20in%20depth-v1.pdf.

VI-P - MessageBox

Comment afficher un message pour l'utilisateur sans sortir de la page ?
En utilisant la méthode Show de MessageBox.

MessageBox.Show("Erreur sur le format de la date");

Cela donne :

Il y a un bouton « Ok » par défaut pour sortir du MessageBox.

Sinon on peut ajouter un titre et un bouton annuler.

MessageBox.Show("Poursuivre?", "Exemple MessageBox", MessageBoxButton.OKCancel);

MessageBox peut retourner dans une variable de type MessageBoxResult :
MessageBoxResult.Ok ou MessageBoxResult.Cancel, suivant le bouton qui a été tapé par l'utilisateur.

MessageBoxResult result =
MessageBox.Show("Poursuivre?",
"Exemple MessageBox", MessageBoxButton.OKCancel);

if (result == MessageBoxResult.OK)
{
MessageBox.Show("Vous avez tapé sur ok.");
}

En WP7 il n'y a que deux boutons possible.

VII - Grandes fonctions WP

VII-A - Données

VII-A-1 - État de l'application

On peut enregistrer temporairement des données simples (de type clé/valeur) dans le dictionnaire d'état qui est temporaire (elles sont conservées en désactivation et en tombstoned, on perd les infos si le logiciel s'arrête).
Le dictionnaire d'état est accessible par la classe PhoneApplicationService et permet de conserver l'état de l'application (données, Web Service, etc.).
Voir le chapitre sur le tombstone.

Il faut au préalable inclure l'espace de noms Microsoft.Phone.Shell.

using Microsoft.Phone.Shell;

C'est aussi bien pratique, comme on l'a vu, pour passer des données simples d'une page à l'autre.
On enregistre (avec la clé « ToDo ») :

private void todoTb_TextChanged(object sender, TextChangedEventArgs e)
{
PhoneApplicationService.Current.State["Todo"] = todoTb.Text;
}

Pour récupérer les informations, on peut le faire dans le constructeur de l'autre page par exemple :

MainPage.xaml.cs

public MainPage()
{
InitializeComponent();

if (PhoneApplicationService.Current.State.ContainsKey("CurrentTodo"))
todoTb.Text = (string)PhoneApplicationService.Current.State["Todo"];

...
}

Autre exemple : pour conserver l'état d'une page, on peut aussi l'enregistrer dans le dictionnaire d'état.
Là on va enregistrer dans la surcharge de la méthode OnNavigatedFrom et lire dans la surcharge de la méthode OnNavigatedTo.
En haut de la classe MainPage, on instancie MyappService de type PhoneApplicationService.
Pour lire, on utilise la méthode TryGetValue qui permet d'éviter les problèmes si la clé n'existe pas.

public partial class MainPage : PhoneApplicationPage

{
PhoneApplicationService MyappService = PhoneApplicationService.Current;

// Constructeur
public MainPage()
{
InitializeComponent();

}

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs args)
{
MyappService.State["LeText"] = MyTextBox.Text;

base.OnNavigatedFrom(args);
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs args)
{
object text;
if (MyappService.State.TryGetValue("LeText", out text))
MyTextBox.Text = text as string;

base.OnNavigatedTo(args);
}

}

VII-A-2 - Isolated Storage

Pour enregistrer des données, il est préférable d'utiliser l'Isolated Storage.
L'enregistrement est permanent.
'Isoladed' car seule votre application aura accès aux données, pas les autres applications.
Votre application ne pourra pas lire les données d'autres applications.

On peut utiliser 200 Mo par application.
Il y a deux manières d'utiliser l'Isolated Storage :
- l'IsolatedStorageSetting pour enregistrer des clé/valeurs ;
- l'IsolatedStorageFile pour enregistrer des répertoires et des fichiers.

Il faut au préalable inclure l'espace de noms :

using System.IO.IsolatedStorage;

L'IsolatedStorageSetting pour enregistre des clé/valeurs.

Exemple : enregistrer un nom dans le Setting sous la clé « name ».
Il faut instancier un objet de type IsolatedStorageSettings.ApplicationSettings. La méthode Add permet ensuite d'ajouter une clé/valeur. On peut ensuite lire la valeur correspondant à la clé.

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.IO.IsolatedStorage; // Ajouter cette ligne

namespace IsolatedStorage
{
public partial class MainPage : PhoneApplicationPage
{
//Instanciation de userSettings+++++++++++++++++
private IsolatedStorageSettings userSettings = IsolatedStorageSettings.ApplicationSettings;

// Constructeur
public MainPage()
{
InitializeComponent();
}

private void Enregistrer_Click(object sender, RoutedEventArgs e)
{
userSettings.Add("name", "Philippe");// On ajoute la clé/valeur
}

private void Lire_Click(object sender, RoutedEventArgs e)
{
string name = (string)userSettings["name"]; //On lit la valeur de la clé "name"
textBlock1.Text=name;
}
}
}

On peut voir si une clé existe :

if (userSettings.Contains("name"))

{ textBlock1.Text="clé 'name' présente"; }

On peut ensuite modifier la valeur d'une clé :

userSettings["name"] = "Paul";

On peut effacer une clé :

userSettings.Remove("name");

On peut effacer toutes les clés ou compter le nombre de clés.

userSettings.Clear();

tbResults.Text = "Count: " + userSettings.Count();

Exemple complet
On enregistre :

private void btnSaveSetting_Click(object sender, RoutedEventArgs e)
{
IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
// txtInput is a TextBox defined in XAML.
if (!settings.Contains("userData"))
{
settings.Add("userData", txtInput.Text);
}
else
{
settings["userData"] = txtInput.Text;
}
settings.Save();
}

Les données qui sont dans le Setting sont enregistrées automatiquement quand l'application (ou la page) qui utilise la classe se termine. On peut forcer l'enregistrement immédiat avec la méthode Save (voir dernière ligne de code qui précède).

On peut aussi enregistrer des objets. Attention, les objets doivent être « serializables ». La sérialisation consiste à transformer l'état d'une information qui est en mémoire sous la forme d'une suite d'informations qui pourra être enregistrée ou transportée.

Si on écrit soi-même la classe, il faut la rendre serializable : mettre [DataContract] avant la classe et [DataMember] avant les champs publics.

Il faut ajouter :

using System.Runtime.Serialization;

[DataContract]
public class ElementDeListe
{

private string _Titre;
[DataMember]
public string Titre
{
get
{
return _Titre;
}
set
{
if (value != _Titre)
{
_Titre = value;

}
}
}

Exemple :
soit une classe « ElementDeListe » permettant d'instancier un objet currentCalcul.
On va enregistrer currentCalcul (dans OnNavigatedFrom par exemple) :

ElementDeListe currentCalcul;
private IsolatedStorageSettings userSettings = IsolatedStorageSettings.ApplicationSettings;

if (userSettings.Contains("currentCalcul"))
{ userSettings.Remove("currentCalcul"); }
userSettings.Add("currentCalcul", currentCalcul);// On ajoute la clé/valeur
userSettings.Save();

On va lire currentCalcul (dans OnNavigatedTo par exemple) :

currentCalcul = (ElementDeListe)userSettings["currentCalcul"]; //On lit la valeur de la clé "name"

L'IsolatedStorageFile pour enregistrer des fichiers.

Pour pouvoir utiliser l'Isolated Storage il faut récupérer une référence vers le conteneur dédié à votre application. Cela se fait avec la méthode statique GetUserStoreForApplication de la classe IsolatedStorageFile.
À partir d'une instance de la classe IsolatedStorageFile, il est possible de manipuler répertoires et fichiers grâce aux méthodes.

FileExists.
CreateFile.
OpenFile.
DeleteFile.
DirectoryExists.
CreateDirectory.
DeleteDirectory.
Remove.

On peut écrire et lire des fichiers avec des IsolatedStorageFileStream.
Un « Stream » est un flux de données.
Vous pouvez lire à partir des flux. La lecture est le transfert de données d'un flux vers une structure de données (du texte avec ReadLine, par exemple).
Vous pouvez écrire dans les flux. L'écriture est le transfert de données d'une structure de données vers un flux (du texte avec ReadLine, par exemple).

using System.IO.IsolatedStorage;
using System.IO;

private void EnregistreTexte_Button_Click(object sender, RoutedEventArgs e)
{
//Obtenir une référence de l'Isolated Storage
IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();

//Créer un nouveau subdirectory
fileStorage.CreateDirectory("textFiles");

//Créer un nouveau StreamWriter pour écrire
StreamWriter fileWriter = new StreamWriter(new IsolatedStorageFileStream("textFiles\\newText.txt",
FileMode.OpenOrCreate, fileStorage));
//Ecrire le contenu de MonTexte.Text dans le fichier.
fileWriter.WriteLine(MonTexteText.Text);
//Fermer le StreamWriter.
fileWriter.Close();
}

private void LitTexte_Button_Click(object sender, RoutedEventArgs e)
{
//Obtenir une référence de l'Isolated Storage
IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
//Créer un nouveau StreamReader
StreamReader fileReader = null;

try
{
//Lire le fichier.
fileReader = new StreamReader(new IsolatedStorageFileStream("textFiles\\newText.txt", FileMode.Open, fileStorage));
//Lire la ligne.
string textFile = fileReader.ReadLine();

//Mettre la ligne lue dans le textBlock.
MonTexte.Text = textFile;
fileReader.Close();
}
catch
{
//Si on lit avant d'avoir enregistrer!!.
viewText.Text = "Le fichier n'existe pas.";
}
}

VII-A-3 - LINQ to SQL, données sur le Web

Il est possible d'utiliser une base de données locale et de l'interroger avec LINQ to SQL.

Il y a plusieurs réseaux et technologies Web service que vous pouvez utiliser pour récupérer des données distantes :
HTTP classes ;
WCF services ;
WCF Data Services (OData services) ;
Windows Azure Services.

VII-B - Activation, désactivation, Tombstone.

Terminer une application
On se souvient que, sur la page principale (la première page) d'une application, si on fait « Retour » (Back bouton de gauche), on sort de l'application elle est fermée.
Si on veut utiliser de nouveau l'application, il faut la relancer.

Suspendre une application
Si par, contre, il y a un appel téléphonique ou si on fait « Démarrer » (bouton du milieu avec logo Windows), l'application est désactivée.
L'utilisateur est censé y revenir et poursuivre l'utilisation de l'application.
Avec le bouton « Back », on peut revenir dans l'application et poursuivre.
(Si on clique sur l'icône du programme, l'application est relancée et redémarre à zéro.)

Durant la désactivation, pendant un certain temps WP conserve toutes les données (c'est l'état dormant) ; si on utilise « Back », on va réactiver l'application et se retrouver dans la même page avec les données affichées dans les TextBox par exemple.

État Tombstone
Il n'est pas possible que toutes les applications désactivées restent au stade désactivé (dormant) éternellement.
Donc après un certain temps, et sans prévenir, quand WP a besoin de place, il fait une désactivation totale dite Tombstone (pierre tombale) ; l'OS tue le process ; et là, si ensuite on fait « Back », on se retrouve dans la page qu'on avait quittée mais on a perdu les informations contenues dans la page (et les données de l'application) qui est réactivée.

En effet, Windows Phone enregistre la pile de retour d'une application lors d'une désactivation Tombstone. Au moment de l'activation, il restaure uniquement la dernière page qui était active avant la désactivation Tombstone de l'application (mais pas les données, les objets ni les contenus des contrôles, des TextBox…).

L'état persistant de l'application (le contenu des contrôles visuels, les données, les objets) doit être enregistré par l'application avant la désactivation. C'est au programmeur de le faire.

Voici le cycle de vie d'une application WP donné par Microsoft :

Si on veut réactiver une application qui est tombstoned et poursuivre là où on était, l'OS se chargera de réafficher la dernière page mais il appartient au programmeur d'enregistrer l'état de la page (Page State= contenu des contrôles de la page) et les données de l'application (Application State) lors de la désactivation et de les restituer à la réactivation.

Voyons les évènements que propose WP pour faire cela.

A - Activated, Desactivated de l'application
Dans App.xaml.cs
Les évènement Launching (lancement) et Closing (fermeture) sont exécutés au lancement et à la fermeture de l'application. C'est classique.
Les évènement Activated (activé) et Desactivated (désactivé) sont moins habituels, ils sont exécutés quand on retourne à l'application et, au cours de l'exécution de votre application, quand on utilise le bouton « démarrer » (bouton du milieu) ou quand le téléphone sonne.

// 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)
{
}

// 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)
{
}

Si l'application est lancée (launching) Application_Activated n'est pas exécuté. L'inverse est vrai.
Idem pour Deactivated et Closing.

Utilisez les événements Deactivated et Activated de l'application pour les données et objets de l'application.

On utilise dans ce cas l'application Setting.
Bien se souvenir que le stockage dans l'application Setting est temporaire et ne dure que le temps pendant lequel l'application est tombstoned. Quand l'application est totalement arrêtée, on perd l'application Setting. Si on veut un enregistrement persistant il faut utiliser l'IsolatedStorage.

Exemple simple utilisant l'ApplicationSettings :

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
PhoneApplicationService.Current.State["MonObjet"] = MonObjet ;
}

Pour la réactivation on peut utiliser « private void Application_activated ».

private void Application_Activated(object sender, DeactivatedEventArgs e)
{

if (PhoneApplicationService.Current.State.ContainsKey("MonObjet"))
{
MonObjet = (string)PhoneApplicationService.Current.State["MonObjet"];

}
}

B - OnNavigatedFrom, OnNavigatedFrom de la page
On a dans la page les évènement OnNavigatedFrom et OnNavigatedTo que l'on peut surcharger.
OnNavigatedTo est exécuté lors de toutes les navigations vers la page (lorsque l'utilisateur arrive sur la page).
OnNavigatedFrom est exécuté lors de toutes les navigations sortant de la page (lorsque l'utilisateur quitte la page).
Le constructeur de page est appelé, lui, lors de la première navigation sur cette page (appelé une seule fois).

using Microsoft.Phone.Shell;
using System.Threading;

namespace CalculeTout
{
public partial class PageMath : PhoneApplicationPage
{

public PageMath()
{
//constructeur de la page, exécuté lors de la PREMIERE navigation vers la page
InitializeComponent();

}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
//exécuté lors de toutes les navigations vers la page
base.OnNavigatedTo(e);

}
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
//exécuté lors de toutes les navigations sortant de la page
base.OnNavigatedFrom(e);

}
}
}

On utilise OnNavigatedFrom et OnNavigatedTo pour l'état de page (contenu des contrôles).

Je l'utilise aussi pour les objets propres à la page.

Exemple :

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
if (phoneApplicationPage.State.ContainsKey("MyText"))
{
phoneApplicationPage.State.Remove("MyText");
}
// sauvegarder le contenu de textbox1
phoneApplicationPage.State.Add(MyText, textbox1.text);

base.OnNavigatedFrom(e);

}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
//Restaurer
if (phoneApplicationPage.State.ContainsKey("MyText"))
{
//Remettre le texte dans la textbox1
textbox1.text= phoneApplicationPage.State["MyText];
}

base.OnNavigatedTo(e);

}

Comment savoir si l'application désactivée a un état Tombstone ?
Avec la méthode IsApplicationInstancePreserved :

private void Application_Activated(object sender, ActivatedEventArgs e)
{
if (e.IsApplicationInstancePreserved)
{
// instance de l'application préservée, pas de tombstone, donc pas de récupération de données.
}
else
{
//Etat tombstone: il faut récupérer l'état
// Teste si une clé est dans State dictionary.
if (PhoneApplicationService.Current.State.ContainsKey("ApplicationDataObject"))
{
//
ApplicationDataObject = PhoneApplicationService.Current.State["ApplicationDataObject"] as string;
}
}

Dans le cas où on utilise un pattern MVVM, on peut sauvegarder et restaurer l'objet ViewModel lui-même.

public partial class MainPage : PhoneApplicationPage

{

public MainPageViewModel MainPageViewModel { get; set; }

private bool isFirstInstance;

// Constructor

public MainPage()

{

InitializeComponent();

if (!IsolatedStorageSettings.ApplicationSettings.Contains("ViewModel"))

{

MainPageViewModel=new MainPageViewModel();

DataContext = MainPageViewModel;

isFirstInstance = true;

}

}

protected override void OnNavigatedFrom(NavigationEventArgs e)

{

IsolatedStorageSettings.ApplicationSettings["ViewModel"] = MainPageViewModel;

base.OnNavigatedFrom(e);

}

protected override void OnNavigatedTo(NavigationEventArgs e)

{

if (isFirstInstance) { return; }

MainPageViewModel =

IsolatedStorageSettings.ApplicationSettings["ViewModel"] as MainPageViewModel;

DataContext = MainPageViewModel;

base.OnNavigatedTo(e);

}

}

Le site CodePlex fourni en open Source TombstoneHelper.dll qui permet de sauvegarder et restaurer automatiquement l'état de la page, le contenu des contrôles d'une page .

Voir : tombstonehelper

Il faut charger tombstonehelper.dll dans le projet. Comme elle vient d'Internet et que Windows interdit d'utiliser une DLL non sure, Visual Studio refuse de l'utiliser, il faut donc cliquer droit sur la DLL dans l'explorateur, choisir « propriétés » ; en bas il y a un bouton qui débloque la DLL à vos risques et périls (attention, il y a des copies de la DLL dans les répertoires bin et release du projet, il faut les débloquer aussi si on ne la pas fait au départ).

Ensuite il faut ajouter la référence au projet : clic droit sur « Références» dans l'explorateur de solution puis « Ajouter une référence » ; ajouter tombstonehelper.dll.

Enfin il faut ajouter l'espace de noms nécessaire :

using TombstoneHelper;

Pour sauver et restaurer la totalité des contrôles (TextBox, PasswordBoxe, CheckBoxe, RadioButton, Slider, ListBoxe et ScrollViewer, PAS les TextBlock !) c'est simple :

using TombstoneHelper;

protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
this.SaveState(e);  // <- first line
}

protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.RestoreState();  // <- second line
}

Pour un type de contrôle :

this.SaveState(typeof(ScrollViewer));
//ou
this.SaveState(typeof(TextBox), typeof(PasswordBox), typeof(CheckBox));
//ou
this.SaveState<TextBox, PasswordBox, CheckBox>();

Attention, les contrôles doivent avoir un nom (attribut « Name »).
On se souvient que les TextBlock ne sont pas sauvés (pour certains résultats j'ai préféré utiliser des TextBox avec l'attribut IsReadOnly à true plutôt que des TextBlock).

Microsoft.Phone.Shell.PhoneApplicationService.Current.ApplicationIdleDetectionMode = Microsoft.Phone.Shell.IdleDetectionMode.Disabled :
permet à votre application de ne pas se faire tombstonner quand le téléphone se met en veille, en clair votre application tourne derrière le lockscreen. C'est le comportement le plus proche d'un thread d'arrière-plan.
Microsoft.Phone.Shell.PhoneApplicationService.Current.UserIdleDetectionMode = Microsoft.Phone.Shell.IdleDetectionMode.Disabled :
permet de désactiver purement et simplement le lockscreen !

Pour tester dans l'émulateur l'effet Tombstone il faut passer par le menu « projet » puis « propriété de », onglet « déboguer ».

Si on ne coche pas, il y a désactivation sans Tombstone.

VII-C - Le pattern Model-View-ViewModel

Un pattern est un modèle de structure d'une application.

Le pattern M-V-VM (Model-View-ViewModel) permet une séparation entre
la couche dédiée aux objets métiers (Model),
à l'interface graphique (View),
à la logique (ViewModel).

C'est ce pattern qu'il est conseillé d'utiliser sur WP.

Il y a de très bon article sur le Web :

http://www.aymericlagier.com/2010/12/20/wp7-bien-commencer-a-developper-pour-windows-phone-7/.

Je dois vous avouer que, pour moi, cela semble très obscur, complexe et difficile à gérer. Mais cela n'engage que moi !

VII-D - Orientation

Le WP peut fonctionner :
en Portrait ou en LandScape (paysage).

Dans le code XAML de la page, on a les orientations supportées (Portrait, LandScape, PortraitOrLandscape).
Il y a aussi l'orientation initiale(Portrait, LandScape).

SupportedOrientations="Portrait"
Orientation="Portrait"

Il est possible de détecter un changement d'orientation en surchargeant la méthode OnOrientationChanged et en regardant l'OrientationChangedEventArgs.

public partial class MainPage :
PhoneApplicationPage
{
// Constructeur
public MainPage()
{
InitializeComponent();
}
protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
OrientationTextBlock.Text = "On passe en mode
" + e.Orientation ;
base.OnOrientationChanged(e);
}
}

e.Orientation a une des valeurs de l'énumération PageOrientation (Bit Flags) :
- PageOrientation.PortraitUp ;
- PageOrientation.PortraitDown ;
- PageOrientation.LandscapeLeft ;
- PageOrientation.LandscapeRight.

L'énumération comporte aussi les valeurs PageOrientation.Portrait et PageOrientation.Landscape.
En utilisant un « ou » entre e.Orientation et PageOrientation.Landscape on determine si on est en LandscapeLeft ou LandscapeRight.

if ((e.Orientation & PageOrientation.Landscape) != 0)
{
}

C'est l'équivalent de :

if (e.Orientation == PageOrientation.Landscape ||
e.Orientation == PageOrientation.LandscapeLeft ||
e.Orientation == PageOrientation.LandscapeRight)
{
}

C'est l'endroit parfait pour modifier les dimensions et positions des contrôles en fonction de l'orientation.

protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{

// Landscape
if ((e.Orientation & PageOrientation.Landscape) != 0)
{
//Positionner en mode paysage
}
// Portrait
else
{
//Positionner en mode portrait
}
base.OnOrientationChanged(e);
}

Le plus simple pour positionner les contrôles dans les deux orientations est de les mettre dans un StackPanel qui lui-même sera dans un ScrollViewer. Comme cela, en mode paysage, les contrôles peuvent déborder en bas.

VII-E - Lanceur d'applications internes

Impossible de lancer, à partir de votre application, une de vos autres applications ou même une application présente sur votre WP comme Internet Explorer (sécurité oblige).

Seules exceptions : Les Launchers (lanceurs) et Choosers (sélecteurs) permettent d'accéder aux Windows Phone applications. Par exemple, si vous voulez chercher un contact à partir de votre application, il faut utiliser EmailAddressChooserTask.

On peut ainsi, à partir de votre application, envoyer un mail ou un coup de téléphone, prendre une photo, ouvrir une page Web.

La différence entre Launchers et Choosers est que les Launchers ne retournent pas de données contrairement aux Choosers. L' EmailComposeTask Launcher par exemple démarre l'email application qui se fermera simplement. La CameraCaptureTask Chooser démarre l'application « camera », à sa sortie cette application fournit la photo qui à été prise.

Il faut ajouter :

using Microsoft.Phone.Tasks;

Launchers (lanceur). Voici les lanceurs :

EmailComposeTask permet d'envoyer un mail ;
MarketplaceDetailTask permet de lancer le « Windows Phone Marketplace client application » et affiche le détail d'un produit du Market Place ;
MarketplaceHubTask Permet de lancer le « Windows Phone Marketplace client application » ;
MarketplaceSearchTask permet de lancer le « Windows Phone Marketplace client application » et de voir le résultat d'une recherche sur un thème ;
MediaPlayerLauncher permet de lancer le media player ;
PhoneCallTask permet de lancer la « Phone application » et de faire un appel téléphonique ;
SearchTask permet de lancer le « web search application » pour rechercher sur le Web ;
SmsComposeTask permet de lancer le « Messaging application » et de faire un nouveau message SMS ;
WebBrowserTask permet de lancer le browser Internet.

Il faut instancier le lanceur, renseigner certaines propriétés puis appliquer la méthode Show.

Exemple 1 : appeler un numéro à partir de votre application.

// Instancier le launcher.
PhoneCallTask phoneCallTask = new PhoneCallTask();
// Renseigner les propriétés.
phoneCallTask.PhoneNumber = "01234567";
phoneCallTask.DisplayName = "Philippe";
// Lancer la 'phone dialer application'.
phoneCallTask.Show();

On a l'écran :

Puis la page habituelle du téléphone.

Exemple 2 : faire une recherche sur le Web.

// Instancier le launcher.
SearchTask searchTask = new SearchTask();
// Renseigner la propriété SearchQuery avec le texte à chercher.
searchTask.SearchQuery = "Cours Windows Phone";
// Lancer la recherche.
searchTask.Show();

On a l'écran :

On a ensuite les réponses à la recherche "cours Windows Phone" sur le Web :

Choosers (sélecteurs). Voici les Choosers

CameraCaptureTask lance la « Camera application ». Permet de récupérer la photo prise ;
EmailAddressChooserTask lance la « Contacts application ». On récupère l'adresse du contact sélectionné par l'utilisateur ;
PhoneNumberChooserTask lance la « Contacts application ». On récupère le numéro de téléphone du contact sélectionné par l'utilisateur ;
PhotoChooserTask lance la « Photo Chooser application ». Retourne la photo sélectionnée ;
SaveEmailAddressTask permet de sauvegarder une adresse mail ;
SavePhoneNumberTask permet de sauvegarder un numéro de téléphone ;
GameInviteTask permet d'accepter un joueur dans un jeu multi joueur ;
SaveRingtoneTask utilise le « save ringtone task » pour permettre à l'utilisateur de sauvegarder un fichier audio file dans le « system ringtones » list (les sonneries).

Il faut instancier le Chooser, instancier un EventHandle sur l'évènement Completed du Chooser, renseigner certaines propriétés puis appliquer la méthode Show.
Il faut récupérer la valeur retournée par le Chooser dans la fonction évènement déclenchée par Completed (Completed survient quand l'application Chooser est terminée).

Exemple 1 : récupérer un numéro de téléphone dans les contacts.

public partial class MainPage : PhoneApplicationPage
{
// Instanciation d'un PhoneNumberChooserTask
PhoneNumberChooserTask numberChooser;
// Constructor
public MainPage()
{
InitializeComponent();
numberChooser = new PhoneNumberChooserTask();
// Instanciation d'un EventHandler sur l'évènement Completed du Chooser
numberChooser.Completed +=
new EventHandler<PhoneNumberResult>(numberChooser_Completed);
}

void numberChooser_Completed(object sender, PhoneNumberResult e)
{
// Est exécuté lorsque l'évènement Completed sur le Chooser survient.
// Check if TaskResult is a success
if (e.TaskResult == TaskResult.OK)
{
// Affiche le numéro de téléphone dans le TextBlock txtPhoneNumber
txtPhoneNumber.Text += e.PhoneNumber;//Récupérer le numéro de téléphone du contact
}
}

//Lancer la liste des contacts pour récuperer un numéro de téléphone
private void ButtonChooser_Click(object sender, RoutedEventArgs e)
{
try
{
// Démarre le Chooser.
numberChooser.Show();
}
catch (System.InvalidOperationException ex)
{
// Catch l' exception, pas besoin de traitement.
}
}
}

Exemple 2 : à partir de mon application, prendre une photo puis dans mon application, l'afficher.

//Instanciation d'un CameraCaptureTask
CameraCaptureTask PhotoCaptureTask;
PhotoCaptureTask = new CameraCaptureTask();
// Instanciation de l' EventHandler sur Completed
PhotoCaptureTask.Completed += new EventHandler<PhotoResult>(PhotoCaptureTask_Completed);

//Lancer la prise de photo.
try
{
PhotoCaptureTask.Show();
}
catch (System.InvalidOperationException ex)
{
MessageBox.Show("Erreur, impossible de prendre la photo.");
}

Maintenant, on a la fonction évènement qui survient quand la tache « photo » est terminée.

void PhotoCaptureTask_Completed(object sender, PhotoResult e)
{
if (e.TaskResult == TaskResult.OK)
{
//Message box indiquant la longueur de l'image
MessageBox.Show(e.ChosenPhoto.Length.ToString());

//Afficher l'image prise dans l'image controle nommé myImage.
System.Windows.Media.Imaging.BitmapImage bmp = new System.Windows.Media.Imaging.BitmapImage();
bmp.SetSource(e.ChosenPhoto);
myImage.Source = bmp;
}
}

Exemple 3 : à partir de mon application, enregistrer un contact dans les contacts.

//Instanciation d'un SaveContactTask
SaveContactTask saveContactTask;
saveContactTask = new SaveContactTask();
// Instanciation de l' EventHandler sur Completed
saveContactTask.Completed += new EventHandler<SaveContactResult>(saveContactTask_Completed);

//Lancer l'enregistrement du contact.
try
{
saveContactTask.FirstName = "Philippe";
saveContactTask.LastName = "Lasserre";
saveContactTask.MobilePhone = "012345678";

saveContactTask.Show();
}
catch (System.InvalidOperationException ex)
{
MessageBox.Show("Impossible d'enregister le contact .");
}

Maintenant, on a la fonction évènement qui survient quand la tache est terminée.

void saveContactTask_Completed(object sender, SaveContactResult e)
{
switch (e.TaskResult)
{
//contact enregistré
case TaskResult.OK:
MessageBox.Show("Contact enregistré.");
break;

//Annuler par l'utilisateur
case TaskResult.Cancel:
MessageBox.Show("Enregistrement annulé.");
break;

//Impossible d'enregistrer
case TaskResult.None:
MessageBox.Show("Impossible d'enregister.");
break;
}
}

Dans le chapitre « Utiliser un WebBrowser » il y a un exemple d'utilisation du WebBrowseTask pour visionner des pages du Web.

VII-F - Les thèmes

Dans les paramètres du Windows Phone on peut choisir :
l'arrière plan light ou dark (clair ou sombre),
la couleur d'accentuation (11 couleurs proposées).

Voici les 10 couleurs version 7.1 (en 7.0 magenta et lime sont légèrement différents), en plus il y a une onzième couleur réservée à l'opérateur ou au fabriquant (sur le mien c'est « Orange F »).

Ces couleurs se retrouvent dans les ressources Windows Phones : la couleur de fond est : PhoneBackgroundColor.
La couleur d'accentuation est : PhoneAccentColor.

Pour le texte : PhoneForegroundColor.

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).

Exemple :

<TextBlock Height="434" Name="TextLong" Foreground="{StaticResource PhoneAccentBrush} />"

On peut aussi utiliser les styles :
PhoneTextNormalStyle qui utilise judicieusement une couleur (PhoneForegroundBrush) qui contraste bien avec la PhoneBackgroundColor du thème. Si le background est dark, PhoneTextNormalStyle donnera du texte blanc (FontSize : 20).
Il y a aussi PhoneTextTitle1Style (FontSize : 72, PhoneForegroundBrush), PhoneTextTitle2Style (FontSize : 32, PhoneForegroundBrush)…

<TextBox Name="resultat" Style="{StaticResource PhoneTextNormalStyle}" />

On se rend compte qu'à partir de la couleur d'accentuation et du fond, les styles s'adaptent pour être lisibles et cohérents.

Sélecteur de ressource : dans la fenêtre « Propriétés », pour donner une valeur ressource à la propriété d'un contrôle (BackGround par exemple), on peut ouvrir le 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.

Comment utiliser les couleurs du thème choisi par l'utilisateur ?
On va créer une page avec un fond dégradé à partir des deux couleurs PhoneBackgroundColor et PhoneAccentColor.
Le titre de la page utilisera le style PhoneAccentColor. Le texte affiché aura le Style PhoneTextNormalStyle.

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

<!--TitlePanel contains the name of the application and page title-->
<Rectangle Stroke="Black" Grid.RowSpan="2">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="{StaticResource PhoneBackgroundColor}" Offset="0"/>
<GradientStop Color="{StaticResource PhoneAccentColor}" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>

<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="Windows Phone" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="Les thèmes" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}">
<TextBlock.Foreground>
<SolidColorBrush Color="{StaticResource PhoneAccentColor}"/>
</TextBlock.Foreground>
</TextBlock>
</StackPanel>

<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Height="601" TextWrapping="Wrap" HorizontalAlignment="Left" Margin="0,6,0,0" x:Name="textBlock1"
Style="{StaticResource PhoneTextNormalStyle}"
Text="Il y a le 'designer' à gauche qui permet de dessiner l'interface que verra l'utilisateur.
Le designer génère le code XAML correspondant, on le voit à droite, il décrit l'interface en XAML.
On peut aussi écrire directement du XAML à droite, ce qui modifie l'interface dans le designer.."
VerticalAlignment="Top" Width="468" />
</Grid>
</Grid>

Voici le résultat : avec un fond foncé et une couleur d'accentuation marron puis avec un fond clair et une couleur d'accentuation bleue.

Le résultat est parfait, le texte toujours lisible quelque soit le choix du fond et de la couleur d'accentuation.

Si on n'indique rien comme style ou couleur, par défaut le thème courant est utilisé : texte blanc si le fond est « dark » et texte noir si le fond est « light » plus couleur d'accentuation.
Si on indique des couleurs, pour le fond par exemple, il faut bien veiller à ce que la couleur du texte soit lisible et tester son application avec les différents fonds et couleurs d'accentuation.

Comment savoir si on a un Background Dark ou Light ?

// Determine la visibilité du dark background.
Visibility darkBackgroundVisibility =
(Visibility)Application.Current.Resources["PhoneDarkThemeVisibility"];

// Afficher dans un textbox le theme background .
if (darkBackgroundVisibility == Visibility.Visible)
{
textBlock1.Text = "background = dark";
}
else
{
textBlock1.Text = "background = light";
}

Comment connaître la couleur d'accentuation ?

Color currentAccentColorHex =
(Color)Application.Current.Resources["PhoneAccentColor"];

VII-G - Vibreur

Il faut inclure l'espace de noms Microsoft.Devices et instancier un VibrateControler avec la méthode static Default.

using Microsoft.Devices;

VibrateController vc = VibrateController.Default;
vc.Start(TimeSpan.FromMilliseconds(100));

Il y a une exception si l'argument de Start est négatif ou supérieur à cinq secondes.

On a aussi la méthode Stop.

Combien de temps faire vibrer ?
100 ms pour signaler la pression d'une touche.
300 ms pour attirer l'attention de l'utilisateur.
2 s : non !

VII-H - Localisation

Le Windows Phone utilise trois moyens pour se localiser :

1. avec les relais émetteurs cellulaires ;

2. avec la Wi-Fi ;

3. avec les satellites GPS.

Avec les relais c'est rapide mais peu précis. Avec le GPS c'est précis mais lent et cela ne marche pas à l'intérieur. Avec la WiFi, il faut être en ville.
Les informations des relais et de la Wi-Fi arrivent en quelques secondes ; pour le GPS c'est quelques minutes.
Le WP utilise les trois (il faut, dans les paramètres du WP, que la localisation soit activée).

Comment trouver sa localisation (latitude, longitude, vitesse, orientation, altitude) ?

Il faut charger la référence (la DLL) System.Device (clic droit sur « Références » dans l'explorateur de projet puis « Ajouter une référence »).

Puis ajouter l'espace de noms System.Device.Localisation.
Il faut instancier un GeoCoordinateWatcher, créer un EventHandler sur l'évènement PositionChanged.
À chaque fois que la position du WP change, l'évènement PositionChanged est exécuté et donc la fonction « gcw_PositionChanged ».
Il faut démarrer la localisation avec la méthode Start.
On affiche ensuite la latitude, la longitude, la vitesse, la direction en degrés par rapport au nord géographique, l'altitude (dans cinq TextBlock).

using System.Device.Location;
namespace localisation
{
public partial class MainPage : PhoneApplicationPage
{
GeoCoordinateWatcher GeoCoordinater;
// Constructeur
public MainPage()
{
InitializeComponent();

GeoCoordinater= new GeoCoordinateWatcher();
GeoCoordinater.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(gcw_PositionChanged);
GeoCoordinater.Start();
}
void gcw_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
//Coordonnées
latitude.Text = e.Position.Location.Latitude.ToString();
longitude.Text = e.Position.Location.Longitude.ToString();

//Vitesse
vitesse.Text= e.Position.Location.Speed.ToString("0.0") + " métres par seconde";

//Direction en degrés par rapport au nord géographique.
course.Text = e.Position.Location.Course.ToString("0.0");

//Altitude
altitude.Text = e.Position.Location.Altitude.ToString("0.0")+ " métres";
}
}
}

Pour tester l'application dans l'émulateur, il faut ouvrir la fenêtre suivante grâce au bouton « >> » situé à droite de l'émulateur, puis cliquer sur l'onglet « Localisation » :

Dans la zone de recherche, en haut à gauche, il faut saisir un lieu, cela affiche une carte. Le fait de cliquer sur la carte affiche en bas les coordonnées du lieu.
Ces coordonnées seront utilisées dans l'émulateur (pas de vitesse, direction et altitude).

Afin d'éviter les plantages, on peut gérer le Status du GeoCoorditaneWatcher afin de savoir si la localisation est activée, si le WP reçoit des données :
Il faut créer un évènement :

// Ajouter en dessous du précédent EventHandler
GeoCoordinater.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(watcher_StatusChanged);

//
static void watcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
{
switch (e.Status)
{
case GeoPositionStatus.Initializing:
System.Diagnostics.Debug.WriteLine("Localisation en cours");
break;

case GeoPositionStatus.Ready:
System.Diagnostics.Debug.WriteLine("Location effectuée");
break;

case GeoPositionStatus.NoData:
System.Diagnostics.Debug.WriteLine("Pas de données");
break;

case GeoPositionStatus.Disabled:
System.Diagnostics.Debug.WriteLine("Localisation désactivée");
break;
}
}

On peut ajouter un paramètre lors de l'initialisation pour forcer à plus de précision :

GeoCoordinateWatcher GeoCoordinater;

GeoCoordinater = new GeoCoordinateWatcher(GeoPositionAccuracy.High);

La valeur par défaut est « Default ».
La précision High peut nuire aux performances.

On peut aussi indiquer la distance de déplacement pour déclencher PositionChanged :

GeoCoordinater.MovementThreshold = 10.0f;

VII-I - Accéléromètre

Windows Phone contient un espace de noms dédié à la gestion et l'exploitation en temps réel des données de l'accéléromètre du téléphone. L'accéléromètre mesure l'intensité et la direction de la force d'accélération appliquée sur le téléphone. La valeur de cette force suivant les axes X, Y, Z, se retrouve dans une variable décimale (valeurs de -1.0 à 1.0), l'unité étant le « g » (1g = attraction de la force terrestre).

Voici le sens des vecteurs X, Y, Z :

L'attraction terrestre ayant la valeur 1, voici les valeurs x y z pour les divers positions du phone.

Dans le code il faut inclure l'espace de noms nécessaire :

using Microsoft.Devices.Sensors;

Il faut ensuite instancier l'accéléromètre et ajouter un EventHandler sur l'évènement :

public partial class MainPage : PhoneApplicationPage
{
// Constructor
Accelerometer monaccelerometer = new Accelerometer();

monaccelerometer.Start();
monaccelerometer.ReadingChanged += new
EventHandler<AccelerometerReadingEventArgs>(myHandler);

void myHandler(object sender, AccelerometerReadingEventArgs e)
{
Deployment.Current.Dispatcher.BeginInvoke(() => updateMyScreen(e));
}

void updateMyScreen(AccelerometerReadingEventArgs e)
{
// updates the textblocks
xreadout.Text = e.X.ToString("0.00");
yreadout.Text = e.Y.ToString("0.00");
zreadout.Text = e.Z.ToString("0.00");
}

Pour tester l'application dans l'émulateur, il faut ouvrir la fenêtre suivante grâce au bouton'>>" situé à droite de l'émulateur puis cliquer sur l'onglet « Accéléromètre » :

En déroulant la liste « Orientation » en bas à gauche, on peut choisir différentes valeurs de X, Y, Z.

VII-J - Internet, WebBrowser, WebClient

Le Windows Phone est-il connecté à internet ?

using Microsoft.Phone.Net.NetworkInformation;
/...
MessageBox.Show(NetworkInterface.NetworkInterfaceType.ToString());

NetworkInterfaceType retourne :
None si pas de connexion ;
Wireless80211 si connexion Wi-Fi ;
Ethernet ;
MobileBroadbandGSM ou MobileBroadbandCDMA si réseau téléphone mobile.

Comment charger dans un Browser une page qui est sur le Web ?

On peut utiliser un lanceur de tache pour utiliser l'application « WebBrowser ». Il faut ajouter un espace de noms :

using Microsoft.Phone.Tasks;

Ensuite il faut utiliser un WebBrowserTask :

WebBrowserTask webBrowserTask = new WebBrowserTask();

webBrowserTask.Uri = new Uri("http://msdn.microsoft.com", UriKind.Absolute);

webBrowserTask.Show();

On peut mettre un contrôle « WebBrowser » dans la page.
En allant le chercher dans la boîte à outils :

<phone:WebBrowser Height="426" HorizontalAlignment="Left" Margin="26,49,0,0"
Name="webBrowser1" VerticalAlignment="Top" Width="407" />

En ajoutant la source :

<phone:WebBrowser Height="426" HorizontalAlignment="Left" Margin="26,49,0,0"
Name="webBrowser1" VerticalAlignment="Top" Width="407"
Source="http://www.bing.com"  />

ou en instanciant un WebBrowser dans le code C# :

using Microsoft.Phone.Controls;

WebBrowser webBrowser1 = new WebBrowser();

Ensuite on peut naviguer vers une page.

webBrowser1.Navigate(new Uri("http://msdn.microsoft.com", UriKind.Absolute));

Pas de GoBack, GoFoward...

Dans ce WebBrowser on peut aussi afficher du HTML grâce à NavigateToString :

webBrowser1.NavigateToString("<html><head><meta name='viewport' content='width=480, user-scalable=yes' />
</head><body>Du texte en HTML</body></html>");

SaveToString retourne une string contenant le code HTML de la page.

Pour charger un fichier situé sur Internet, on peut utiliser un WebClient.
Un WebClient a des méthodes pour l'envoi de données à une ressource locale, intranet ou Internet identifiée par un URI ou pour la réception de données en provenance de cette ressource.

Ici on va charger une image :

{
WebClient MywebClient = new WebClient();
MywebClient.OpenReadCompleted += OnWebClientOpenReadCompleted;
MywebClient.OpenReadAsync(new Uri("http://www.mysite.com/Media/myimage.jpg"));
}

void OnWebClientOpenReadCompleted(object sender, OpenReadCompletedEventArgs args)
{
if (!args.Cancelled && args.Error== null)
{
BitmapImage bmp = new BitmapImage();
bmp.SetSource(args.Result);
image.Source = bmp;
}
}

VII-K - Radio FM

Simple pour mettre en marche la radio FM.

using Microsoft.Devices.Radio;

puis

FMRadio.Instance.PowerMode=  RadioPowerMode.On;
FMRadio.Instance.Frequency= 100.3;

VII-L - Minuterie

On peut utiliser une minuterie grâce à la classe DispatcherTimer de espace de noms System.Windows.Threading du framework.
Elle déclenche un évènement Tick tous les « Interval » de temps.

Créons une application qui affiche date, heure, minute, seconde et se met à jour toutes les secondes.

using System.Windows.Threading;
//..

//Instanciation d'un timer.
DispatcherTimer dt = new DispatcherTimer();

//On indique l'intervalle de temps entre les évènements Tick, ici 1 s.
dt.Interval = TimeSpan.FromSeconds(1);

//On indique quelle fonction exécuter quand le Timer déclenche Tick.
dt.Tick += OnTimerSeconde;

//On démarre le Timer
tmr.Start();

void OnTimerSeconde(object sender, EventArgs args)
//Fonction exécutée toutes les secondes
{
//On affiche dans le textBlock nommé 'Clock' la date et heure, minute, seconde
//Mise à jour toutes les secondes donc.
Clock.Text = DateTime.Now.ToString();
}

Les opérations DispatcherTimer sont placées dans la file d'attente Dispatcher. Le moment d'exécution de l'opération DispatcherTimer dépend des autres travaux de la file d'attente et de leur priorité ; ainsi l'évènement Tick se produira peut-être pas exactement durant l'intervalle de temps défini, au moins après un intervalle, parfois plus.
Il s'exécute dans le thread interface utilisateur (UI).

Il existe un autre Timer dans l'espace de noms System.Threading :
Il fonctionne dans un thread d'arrière-plan.

using System.Threading;

Timer tm = new Timer(TimerCallBack);
// TimerCallBack est la méthode de rappel à exécuter

tm.Change(100 , 1000);
//Premier paramètre :
//Délai d'attente, en millisecondes ou en TimeSpan, avant l'appel de la méthode de rappel
//spécifiée au moment de la construction de Timer.
//Spécifiez Timeout.Infinite pour empêcher le redémarrage de la minuterie.
//Spécifiez zéro (0) pour redémarrer la minuterie immédiatement.
//Second paramètre:
//Intervalle de temps, en millisecondes ou en TimeSpan, entre les appels de la méthode de rappel

//...

public void TimerCallBack(object state)
{ //méthode de rappel exécutée toutes les secondes
this.Dispatcher.BeginInvoke(delegate() { OnTimer(); });

}

void OnTimer()
{
// Méthode affichant l'heure
Clock.Text = DateTime.Now.ToString();
}

Si on avait voulu afficher l'heure dans la méthode TimerCallBack, il y aurait eu levée d'une exception « Invalid cross-thread access » car le Timer est dans un thread différent du thread de l'interface. Le thread du Timer doit déléguer le travail au Dispatcher associé au thread d'interface utilisateur grâce à BeginInvoke.

System.Threading.Timer n'est pas recommandé pour les scénarios dans lesquels l'interface d'utilisateur doit être actualisée, parce que ses callback ne se produisent pas sur le thread d'interface d'utilisateur. Système.Windows.DispatcherTimer est un meilleur choix dans ces scénarios, parce que ses événements sont levés sur le thread d'interface d'utilisateur.

VII-M - Applications en tâche de fond : alarme, rappel

La classe « Alarme » de l'espace de noms « Microsoft.Phone.Scheduler » permet de créer une alarme.
On va créer une alarme nommée « MonAlarme » ; elle se déclenchera dans 20 s et affichera "Debout".

using Microsoft.Phone.Scheduler;

Alarm alarm = new Alarm("MonAlarme");
alarm.BeginTime = DateTime.Now.AddSeconds(20);
alarm.RecurrenceType = RecurrenceInterval.Daily;
alarm.Content = "Debout.";
alarm.Sound = new Uri("sound.wav", UriKind.Relative)
ScheduledActionService.Add(alarm);

RecurrenceInterval peut être Daily, Weekly, Monthly, Yearly, None, EndOfMonth.
ExpirationTime indique à quel moment arrêter le service.
Sound permet d'ajouter l'URI d'un fichier son qui sera exécuté lors de l'alarme. Le fichier son doit être dans le répertoire source et chargé dans l'application comme contenu.

Si on relance l'application une seconde fois et qu'on veut créer de nouveau une alarme nommée « MonAlarme », il y a levée d'une exception car elle existe toujours (même si on a quitté l'application).
Il est possible de détruire cette alarme :

ScheduledActionService.Remove("MonAlarme");

On peut aussi utiliser un Reminder ou rappel : chaque 2 juin à 21 h 40 rappeler que c'est l'anniversaire de « Mimi ».

Reminder reminder = new Reminder("Anniverssaire")
{
BeginTime = new DateTime (2012,6,2,21,40,0) ,
Content = "Anniverssaire de Mimi",
Title = "Anniversaire",
RecurrenceType = RecurrenceInterval.Yearly,
NavigationUri = new Uri("/MainPage.xaml", UriKind.Relative)
};
ScheduledActionService.Add(reminder);

VII-N - Informations diverses

Grâce à DeviceExtendedProperties de l'espace de noms Microsoft.Phone.Info on peut lire des informations sur le téléphone : fabricant, nom, version du Firmware et du Hardware, mémoire totale, mémoire utilisée par l'application, « ApplicationPeakMemoryUsage », ID du téléphone.

using Microsoft.Phone.Info;

//Fabriquant du téléphone
MessageBox.Show ( DeviceExtendedProperties.GetValue("DeviceManufacturer").ToString());
//Nom du téléphone
MessageBox.Show ( DeviceExtendedProperties.GetValue("DeviceName").ToString());
//Version du Firmware
MessageBox.Show ( DeviceExtendedProperties.GetValue("DeviceFirmwareVersion").ToString());
//Version du Harsware
MessageBox.Show ( DeviceExtendedProperties.GetValue("DeviceHardwareVersion").ToString());
//Mémoire total du portable
MessageBox.Show ( DeviceExtendedProperties.GetValue("DeviceTotalMemory").ToString());//retourne un long
//Mémoire utilisée par l'application
MessageBox.Show ( DeviceExtendedProperties.GetValue("ApplicationCurrentMemoryUsage").ToString());//retourne un long

object tryValue;

if (DeviceExtendedProperties.TryGetValue("ApplicationPeakMemoryUsage", out tryValue))
{
MessageBox.Show ( tryValue.ToString());
}

//retourne un tableau de 20 bytes correspondant à l'ID unique du téléphone
MessageBox.Show ( DeviceExtendedProperties.GetValue("DeviceUniqueId").ToString());
//A faire : caster le tableau pour le rendre affichable

Il existe aussi DeviceStatus (à partir de WP 7.1) qui permet de voir toutes les informations précédentes avec une syntaxe plus simple.
Mais en plus :
PowerSource qui retourne Battery ou External (secteur) ;
ApplicationMemoryUsageLimit (mémoire que l'application peut utiliser) ;
IsKeyboardPresent IsKeyboardDeployed.

//On retrouver le précédentes information avec une syntaxe plus simple
MessageBox.Show(DeviceStatus.ApplicationCurrentMemoryUsage.ToString());

//En plus
MessageBox.Show(DeviceStatus.PowerSource.ToString());
MessageBox.Show(DeviceStatus.ApplicationMemoryUsageLimit.ToString());
MessageBox.Show(DeviceStatus.IsKeyboardPresent.ToString()); //et IsKeyboardDeployed

Le portable supporte-t-il le smooth streaming des multi-resolution encoded video ?

MessageBox.Show(MediaCapabilities.IsMultiResolutionVideoSupported.ToString());

VIII - Comment faire

VIII-A - SplashScreen

C'est une fenêtre qui s'ouvre au démarrage d'un programme, qui montre simplement une belle image, ensuite la fenêtre « Splash » disparaît et la fenêtre principale apparaît.

En WP c'est simple :

Il y a dans l'explorateur de solution un fichier nommé SplashScreenImage.jpg, il fait 480*800 pixels. Il suffit de double-cliquer dessus pour se retrouver dans Paint et modifier le SplashScreen.
Dans ses propriétés, « Action de génération » doit être à « contenu ».

S'il n'y a pas ce fichier, il n'y a pas de SplashScreen.
Si on veut que le SplashScreen reste plus longtemps il faut, dans le constructeur de la page (sous InitializeComponent) mettre une Thread.Sleep (voir chapitre « perdre du temps »).

using System.Threading;

namespace customcontrol
{
public partial class MainPage : PhoneApplicationPage
{
// Constructeur
public MainPage()
{
InitializeComponent();
Thread.Sleep(500);

VIII-B - Image de fond de page, icône

Dans le projet il y a déjà par défaut :
ApplicationIcon.png : icône de l'application (62*62 pixels) qui sera affichée dans la liste des applications ;
Background.png : icône qui sera affichée lorsque l'application est "épinglée" (pinned) sur la page d'accueil du téléphone (173*173 pixels).

Dans les propriétés du projet (menu "Projet" puis "Propriétés de") on peut modifier cela.

Dans l'explorateur de solution à droite, il faut double-cliquer sur le nom d'une de ces deux images pour se retrouver dans Paint (ou Paint.Net ici) et ainsi la modifier.

Pour ses deux icônes si on veut un fond ayant la couleur du thème il faut, au lieu de mettre un fond coloré, mettre un fond transparent, un avant plan blanc et sauvegarder votre image en .PNG avec support de la transparence (sauvegarde en 32 bits). J'utilise Paint.Net pour cela.

Ici on a la liste des applications avec les icônes (ApplicationIcon.png de 63x63 px), WP ajoute le nom de l'application à droite.

Ici, pour l'icône épinglée (Background.png 173x173 px) WP ajoute sur l'image le nom de l'application ('My app' ici) avec des caractères de 20 px de hauteur, à 7 px du bord gauche, et 7 px du bas.

Comment mettre une image de fond dans une page Panorama ou Pivot ?

<controls:Panorama  Title="mon application" >

<controls:Panorama.Background>
<ImageBrush ImageSource="Panoramafond.png"/>
</controls:Panorama.Background>

Pour un panorama, si l'image à une largeur de deux pages mais que le panorama a quatre pages, le fond se déroulera lentement et s'affichera sur les quatre pages.

Comment mettre un fond sur une page simple ?
Il faut mettre l'image dans le background de la grille racine :

<!--LayoutRoot est la grille racine où tout le contenu de la page est placé-->
<Grid x:Name="LayoutRoot" >
<Grid.Background>
<ImageBrush ImageSource="PanoramaBackground.png" Stretch="UniformToFill" />
</Grid.Background>

Le problème est que parfois on a (si on utilise un thème avec arrière-fond clair) une bande blanche en haut.

Dans ce cas il ne faut pas afficher le SystemTray et il ne faut pas mettre de taille dans la Grid LayoutRoot (ainsi elle remplira la totalité de l'espace).
Si on a modifié la Grid à la souris, il apparaît la propriété Height, ensuite on a du mal à gérer, il vaut donc mieux enlever « Height=" " ».

shell:SystemTray.IsVisible="False"

<!--LayoutRoot est la grille racine où tout le contenu de la page est placé-->
<Grid x:Name="LayoutRoot" >

Comment mettre en fond de page une couleur aléatoire :

ContentPanel.Background =
new SolidColorBrush(Color.FromArgb(255, (byte)rand.Next(256),
(byte)rand.Next(256),
(byte)rand.Next(256)));

VIII-C - Positionnement dynamique des contrôles

Layout Absolu
Dans un positionnement en Layout Absolu, les éléments sont placés dans un canvas à une position exacte absolue relative à leur élément parent.
Cela ne prend pas en compte les dimensions de l'écran.

Layout dynamique
Permet un positionnement quel que soit la résolution de l'écran.
On utilise un StackPanel ou surtout une Grid.
Dans les cellules, on met des contrôles avec les propriétés Width et Height= Auto. Ils occupent toute la cellule (Margin permet de laisser un espace autour).
S'il y a un texte à afficher en totalité on donne une valeur à MinHeight et MinWidth pour que le texte ne soit pas tronqué.
On donne des dimensions relatives pour les colonnes et les lignes de la Grid (dans RowDefinition et ColumnDefinition) à l'aide des *.

L'utilisateur utilise son doigt pour toucher les éléments visuels ; il est conseillé d'agrandir la zone de touché bien au-delà de la zone visuelle. Les éléments trop rapprochés les uns des autres introduisent un risque pour l'utilisateur d'appuyer sur le mauvais bouton. Les contrôles de Silverlight sont déjà optimisés par nature, en ayant naturellement une Margin importante.
Il y a des contraintes de taille et Microsoft donne les conseils suivants :
une cible doit être au moins un carré de 34 px avec un espacement d'au moins 8 px. L'agrandissement de la zone de touché doit être d'au moins 60 % de la taille de l'élément. Pour les éléments textuels touchables, la taille de police doit être d'au moins 15 pts.

VIII-D - Navigation entre pages, transfert de paramètres

La page de démarrage est par défaut « MainPage.xaml ».
Si on veut en mettre une autre, il faut dans l'explorateur de solution, cliquer sur « Properties » puis double-cliquer sur « WMAppManifest.xml » et modifier la ligne :

<DefaultTask  Name ="_default" NavigationPage="MainPage.xaml"/>

Comment dans une page ouvrir une autre page nommée « PageConvert.xaml » ?

En C# :

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

La Classe NavigationService contient ce qu'il faut pour naviguer. Les méthodes Navigate mais aussi GoBack, GoForward…

En XAML : on peut aussi utiliser un hyperlinkButton :

<HyperlinkButton NavigateUri="/PageConvert.xaml" Content="Aller à pageconvert" />

Exemple complet
On met trois boutons dans une page, cliquer sur un des boutons ouvre la page correspondante.

Code XAML des boutons :

<Button x:Name="ButtonRouge" Content="Rouge" Click="Button_Click" Width="200" Height="75" />
<Button x:Name="ButtonVert" Content="Vert" Click="Button_Click" Width="200" Height="75" />
<Button x:Name="ButtonBleue" Content="Bleue" Click="Button_Click" Width="200" Height="75" />

Code C# :

private void Button_Click(object sender, RoutedEventArgs e)
{
Button clickedButton = sender as Button;

switch(clickedButton.Name)
{
case "ButtonRouge":
NavigationService.Navigate(new Uri("/PageRouge.xaml", UriKind.Relative));
break;
case "ButtonVert":
NavigationService.Navigate(new Uri("/PageVerte.xaml", UriKind.Relative));
break;
case "ButtonBleue":
NavigationService.Navigate(new Uri("/PageBleue.xaml", UriKind.Relative));
break;
}
}

Comment passer un texte ?
Quand on clique sur button1, ouvrir SecondPage.xaml. Passer « montexte » de la page actuelle à la page SecondPage.xaml.

On va utiliser les QueryString : pour l'appel d'une nouvelle page avec NavigationService.Navigate, on ajoute le texte à l'URI (querystring= 'msg', valeur="montexte"). C'est comme pour une page Web, on utilise le point d'interrogation suivi d'une clé et de sa valeur. Dans la page qui s'ouvre on récupère le texte par NavigateContext.QueryString :

private void button1_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new Uri("/SecondPage.xaml?msg="+"montexte",UriKind.Relative));
}

Dans SecondPage :

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
string msg = "";
if (NavigationContext.QueryString.TryGetValue("msg", out msg))
//msg contient montexte.
}

La méthode OnNavigatedTo de la page s'exécute lorsque la page devient active. Elle est préférable à la méthode « Loaded » qui peut s'exécuter plusieurs fois.
On surcharge donc cette méthode pour récupérer le QueryString.

On peut mettre plusieurs textes séparés par '&'.

private void button1_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new Uri("/SecondPage.xaml?msg=montexte&msg2=montexte2",UriKind.Relative));
}

Comment passer un objet ?

En utilisant le dictionnaire d'état temporaire avec PhoneApplicationService.Current.State permettant de stocker les données de l'application, un objet en particulier.

Il faut au préalable inclure l'espace de noms Microsoft.Phone.Shell.

using Microsoft.Phone.Shell;

Dans la page de départ :

Personne MyPersonne; // soit un objet MyPersonne de type Personne
PhoneApplicationService.Current.State["ItemEnCours"] = MyPersonne;

Ouvrir la seconde page et surcharger OnNavigateTo dans celle-ci. Récupérer l'objet dans les données de l'application.

using Microsoft.Phone.Shell;

namespace CalculeTout
{
public partial class PageConvert : PhoneApplicationPage
{
//Variable
ElementDeListe currentCalcul;
Boolean IsInverse;
public PageConvert()
{
InitializeComponent();

}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
//Recupération de l'objet de type personne
base.OnNavigatedTo(e);
MyLocalPersonne = (personne) PhoneApplicationService.Current.State["ItemEnCours"];

{

}

}

Comment retourner à la page précédente avec du code ?

NavigationService.GoBack();

Bien sûr il y a le bouton de gauche qui est le bouton « back ».

Comment détourner l'usage du « back Button », exécuter du code et annuler son effet ?
En surchargeant la méthode OnBackKeyPress :

protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
//votre code.
e.Cancel = true;  //annule l'effet 'back'.
}

VIII-E - Validation de caractère dans un TextBox

Problème récurrent.
Je veux permettre la saisie des chiffres de 0 à 9 (uniquement) et permettre l'effacement (back) dans un TextBox nommé « entree1 », comment faire ?

Premièrement, il faut bien choisir le clavier virtuel (InputScope). On utilise « Digit ».

<TextBox Name="entree1"  KeyUp="TesteTouche">
<TextBox.InputScope>
<InputScope>
<InputScopeName NameValue="Digit" />
</InputScope>
</TextBox.InputScope>
</TextBox>

Quand le TextBox à le focus, le clavier suivant apparaît :

Ce clavier ne permet de saisir que des chiffres mais parfois il y a d'autres caractères possible qu'il faut éliminer (le point ici).
On va donc intercepter l'évènement KeyUp (KeyUp="TesteTouche" dans le XAML précédent) qui se déclenche quand on appuie sur une touche, cela exécutera « TesteTouche » ; cette routine va accepter les seules touches permises.

private void TesteTouche(object sender, KeyEventArgs e)
{ //Controle validité des touches
//touches permisses
string keys;
keys = "1234567890";
TextBox TextBoxControl= (TextBox) sender;
for (int j = 0; j < TextBoxControl.Text.Length; j++)
{
if ( ! (keys.Contains(TextBoxControl.Text.Substring(j,1))))
{TextBoxControl.Text=TextBoxControl.Text.Remove(j,1);
TextBoxControl.SelectionStart = TextBoxControl.Text.Length;
}
}
}

keys est une string qui contient les touches permises.
Je caste le sender en TextBox.
Je regarde si chaque caractère du TextBox fait partie des caractères autorisés ; sinon je l'élimine.
Enfin je remets le curseur à la fin.

Si la saisie doit permettre les nombres fractionnaires, il faut accepter le bon séparateur décimal. Une méthode est de lire ce séparateur ('.' ou ',') dans la currentCulture et de l'ajouter à keys.

//touches permisses
string keys;
keys = "1234567890";
if (Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator == ",")
{
keys = keys + ",";
}
else
{
keys = keys + ";";

}

Comment remplacer un caractère par un autre au cours de la saisie ?
Ici on va utiliser TextChanged :

<TextBox Name="entree2" TextChanged="ChangeCar"  >
</TextBox>

Et dans le code behind :

private void ChangeCar(object sender, TextChangedEventArgs e)
{
if (entree2.Text.Contains("1"))
{
entree2.Text = entree2.Text.Replace("1", "2");

entree2.SelectionStart = entree2.Text.Length;
}
}

Une autre méthode se trouve sur Internet.
Dans la routine C# on déclare un tableau contenant les Key (touches du clavier) à accepter.
Ensuite on teste si la touche tapée (qui est dans e.Key) est dans le tableau.
Si IndexOf retourne -1 , la touche n'y est pas.
Dans ce cas on indique d'annuler le traitement de la touche (e.Handled= true) :

private void entree1_KeyDown(object sender, KeyEventArgs e)
{ //Controle validité des touches
//touches permisses
Key[] numeric = new Key[] {Key.Back, Key.NumPad0, Key.NumPad1,
Key.NumPad2, Key.NumPad3, Key.NumPad4,
Key.NumPad5, Key.NumPad6, Key.NumPad7, Key.NumPad8, Key.NumPad9  };
// handles non numeric
if (Array.IndexOf(numeric, e.Key) == -1)
{
e.Handled = true;
}

}

Elle ne fonctionne pas si on utilise les codes touche keyD1, KeyD2… Il faut utiliser Key.NumPad1, KeyNumPad2… (e.Key retourne la touche mais avec les InputScope, le même code est retourné pour plusieurs touches ! Cela fonctionne avec l'exemple précédent mais pas avec toutes les touches).

Autre exemple plus complexe :
L'utilisateur tape du texte dans une TextBox nommée entree.
Eliminer '*', '#', " ".
Si le séparateur décimal est '.' , remplacer ',' par '.' et vice versa.

private void ChangeCar(object sender, TextChangedEventArgs e)
{
string entr =entree.Text;

// Le séparateur est point ou virgule? Remplacer dans la saisie ; par , ou l'inverse
//Pour L'affichage  ToString affiche suivant la culture
if (Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator == ",")
{
if (entr.Contains(".") || entr.Contains("*") || entr.Contains("#") || entr.Contains(" "))
{
entr = entr.Replace(".", ",");
entr = entr.Replace("*", "");
entr = entr.Replace("#", "");
entr = entr.Replace(" ", "");
entree.Text = entr;
entree.SelectionStart = 500;
}
}
else
{
if (entr.Contains(",") || entr.Contains("*") || entr.Contains("#") || entr.Contains(" "))
{
entr = entr.Replace(",", ".");
entr = entr.Replace("*", "");
entr = entr.Replace("#", "");
entr = entr.Replace(" ", "");
entree.Text = entr;
entree.SelectionStart = 500;
}
}
}

VIII-F - Créer ses propres contrôles

Contrôle étendu

On peut utiliser un contrôle et étendre ses possibilités, on peut aussi créer son propre contrôle.

On va créer un contrôle étendu nommé « TextBoxNumerique » qui est une TextBox qui n'accepte que les chiffres de 0 à 9 et permet l'effacement.

On va créer une classe.
Menu « Projet » puis « Ajouter une classe ».

Notre classe va hériter de TextBox :

public class TextBoxNumerique : TextBox
{}

On va surcharger la méthode OnKeyDown
(qui est exécutée quand l'évènement KeyDown va se produire) :

public class TextBoxNumerique : TextBox
{
protected override void OnKeyDown(KeyEventArgs e)
{
}
}

On y teste la touche appuyée, si elle n'est pas dans le tableau des touches valides (0 à 9 et Back), on annule la touche.

Voici le code complet :

using System;
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 customcontrol
{
public class TextBoxNumerique : TextBox
{
private readonly Key[] KeyValide = new Key[] {Key.Back, Key.NumPad0, Key.NumPad1,
Key.NumPad2, Key.NumPad3, Key.NumPad4,
Key.NumPad5, Key.NumPad6, Key.NumPad7, Key.NumPad8, Key.NumPad9 };

public TextBoxNumerique()
{

this.InputScope = new InputScope();
this.InputScope.Names.Add(new InputScopeName() { NameValue = InputScopeNameValue.TelephoneLocalNumber });
}

protected override void OnKeyDown(KeyEventArgs e)
{
if (Array.IndexOf(KeyValide, e.Key) == -1)
{
e.Handled = true;
}
base.OnKeyDown(e);
}
}
}

Dans le constructeur on indique l'InputScope qui est « TelephoneLocalNumber » ce qui ouvre le clavier téléphone pour l'utilisateur.

On peut maintenant ajouter le contrôle dans l'UI.
Menu « Build » puis « Générer la solution » et on retrouve le contrôle dans la boîte à outils en haut.
Il faut inclure l'espace de noms (ici avec un alias nommé « local ») puis mettre le customcontrol « TextBoxNumerique » dans le code XAML.

xmlns:local="clr-namespace:customcontrol"
<!--.............-->

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

<local:TextBoxNumerique x:Name="MonTB" Margin="0,43,183,470" />

</Grid>
</Grid>

On peut utiliser les champs et méthodes hérités de TextBox dans le code C# ( MonTB.Text, par exemple).

Le Custom Control hérite d'un contrôle ; il hérite aussi des propriétés et méthodes de ce contrôle.
On peut surcharger les méthodes pour modifier le comportement ou ajouter des champs et de nouvelles méthodes.

UserControl : contrôle utilisateur.

Là on va créer un UserControl qui est un contrôle composite contenant plusieurs contrôles et réutilisable.
Il dérive de la classe UserControl ; il a une propriété « Content » où on mettra les contrôles.

On va créer un UserControl contenant deux TextBox permettant de saisir un nom, un mot de passe, un bouton « ok » qui déterminera si le mot de passe est valide.

Créons le UserControl : menu « Projet » puis « Ajouter un nouvel élément ».

Choisir « Contrôle utilisateur Windows Phone » puis en bas donner le nom « MyControl », puis cliquer sur ajouter.
Il s'ouvre un onglet nommé « MyControl.xaml » ; dans le concepteur il n'y a même pas de bord.
À l'aide de la boîte à outils ajouter les contrôles nécessaires :

Cela donne le code XAML correspondant suivant :

<UserControl x:Class="Test.Mycontrol"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="480" d:DesignWidth="480">

<Grid x:Name="LayoutRoot">
<StackPanel HorizontalAlignment="Center">

<StackPanel.Resources>
<!--Create a Style for a TextBlock.-->
<Style TargetType="TextBlock" x:Key="TextBlockStyle">
<Setter Property="Foreground" Value="Aquamarine"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>

<!--Create a Style for a TextBlock.-->
<Style TargetType="TextBox" x:Key="TextBoxStyle">
<Setter Property="Width" Value="200"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Margin" Value="4"/>
<Setter Property="FontSize" Value="10"/>
<Setter Property="Background" Value="Beige"/>
</Style>
</StackPanel.Resources>

<TextBlock FontSize="18" Text="Identification."/>
<StackPanel Orientation="Vertical" Height="74" Width="207">
<TextBlock Style="{StaticResource TextBlockStyle}">
Nom:
</TextBlock>
<TextBox Name="_Nom" Style="{StaticResource TextBoxStyle}" Height="50" FontSize="15" />
</StackPanel>
<StackPanel Orientation="Vertical" Height="75">
<TextBlock Style="{StaticResource TextBlockStyle}">
Mot de passe:
</TextBlock>
<TextBox Name="_MotdePasse" Style="{StaticResource TextBoxStyle}"
Margin="6,4,4,4" Height="50" FontSize="15" />
</StackPanel>
<Button Name="Bouton" Width="108" Content="Ok"  Height="70" Click="Bouton_Click" />

</StackPanel>
</Grid>
</UserControl>

Il y a un TextBox nommé « _Nom », un autre nommé « _MotDePasse » et un bouton « Bouton ».

Maintenant on va créer le code propre au UserControl, dans les fonctions privées du UserControl.
Le code contenu dans les propriétés et méthodes privées va piloter le ou les contrôles internes. Ce code fournit un comportement propre au UserControl.
On va déclarer une variable bool nommée « _IsValid ». La fonction Bouton_Click (exécutée lorsque l'utilisateur clique sur « Ok ») contrôle la validité du nom et du mot de passe et si tout est valide cela entraîne _IsValid= true. Cela donne dans MyControl.xaml.cs.

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;

namespace Test
{
public partial class Mycontrol : UserControl
{
public bool _IsValid;

public Mycontrol()
{
InitializeComponent();
_IsValid = false;
}

private void Bouton_Click(object sender, RoutedEventArgs e)
{
//Validation mot de passe

if (_Nom.Text=="toto" & _MotdePasse.Text=="pass")
{ _IsValid = true; }
}

}

Notez bien que la validation est triviale ; ce n'est pas notre propos ici.

Bien, le UserControl fonctionne mais pour le moment il n'y a pas de liaison avec l'extérieur.

Les contrôles qui sont dans le UserControl sont privés au UserControl et leurs propriétés ou méthodes ne sont pas accessibles à l'extérieur. On ne peut donc pas avoir accès à la propriété « Text » du contrôle « _Nom » par exemple. Il va falloir rajouter dans le UserControl des membres et des méthodes public qui seront accessibles à l'utilisateur du composant. Ces champs public (avec get et set) et ces méthodes publiques créeront une interface.

Ajoutons donc l'interface du UserControl
On va donc créer une propriété « public » « IsValid » pour que l'utilisateur du contrôle puisse savoir si « nom/mot de passe » sont valides, une propriété public « Nom » cela peut être utile.
Enfin une méthode Clear « public » pour réinitialiser le UserControl :

public bool IsValid
{
get
{
return _IsValid;
}
set
{
if (value != _IsValid)
{
_IsValid = value;

}
}
}

public string Nom     // Variable public avec son get et son set
{
get
{
return _Nom.Text;
}
set
{
if (value != _Nom.Text)
{
_Nom.Text = value;

}
}
}

public void Clear()
{
_Nom.Text = "";
_MotdePasse.Text = "";
_IsValid = false;
}

Le UserControl est terminé, un coup de menu « Build » puis « Générer » ; maintenant on va pouvoir utiliser le UserControl.

Utilisons le UserControl « MyControl ».
Dans la page MainPage, j'ouvre la boîte à outils ; le UserControl « MyControl » est là, on peut j'ajouter à la page :

Cela donne le code XAML suivant :

<phone:PhoneApplicationPage
x:Class="test.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"
xmlns:my="clr-namespace:test">

<!--LayoutRoot est la grille racine où tout le contenu de la page est placé-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--...........-->
<my:Mycontrol x:Name="mycontrol1" Height="260" HorizontalAlignment="Left"  VerticalAlignment="Top" Width="262" />
<Button Content="Button" Height="72" HorizontalAlignment="Left" Margin="112,379,0,0" Name="button1"
VerticalAlignment="Top" Width="160" Click="button1_Click_1" />

</Grid>

</phone:PhoneApplicationPage>

On note que cela a ajouté la ligne « xmlns:my="clr-namespace:test » qui ajoute le namespace « test » (l'application), ce qui permet ensuite d'utiliser « my:Mycontrol ».

Dans le code C# de MainPage on peut accéder à la méthode Clear et aux propriétés IsValid et Nom du contrôle « mycontrole1 »

MessageBox.Show(mycontrol1.IsValid.ToString());

Affiche true si la saisie Nom/Mot de passe est valide.

VIII-G - Gérer le BackButton

On peut simuler l'appui du bouton « back » par code :

private void BtnBack_Click(object sender, RoutedEventArgs e)
{
NavigationService.GoBack();
}

Si un texte a été tapé dans le TextBox « MonTexte », demander confirmation avant de quitter la page si l'utilisateur appuie sur le bouton « Back ».

Pour cela, on surcharge OnBackKeyPress

protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
base.OnBackKeyPress(e);
if(MonTexte.Text != string.Empty)
{
var result = MessageBox.Show("Vous étes sur le point de perdre votre saisie. Continuer?",
"Attention", MessageBoxButton.OKCancel);
if (result != MessageBoxResult.OK)
{
e.Cancel = true;
}
}
}

On remarque qu'on peut annuler l'effet de la touche « Back » par e.Cancel = true.

Quand on veut forcer un « back » par code, on peut tester avant si c'est possible :

private void BtnBack_Click(object sender, RoutedEventArgs e)
{
if (NavigationService.CanGoBack)
NavigationService.GoBack();
}

Enfin, on peut interdire l'action de la touche « Back ».

this.goBackButton.IsEnabled = false;

VIII-H - Saisie tactile, gesture

Rappel des différents gestures (dessin de Microsoft) :

Un événement Tap a lieu lorsqu'un doigt touche l'écran et le quitte avant 1,1 secondes sans se déplacer beaucoup. Si deux événements Tap se succèdent rapidement, le deuxième est traité comme un événement DoubleTap. Un événement Hold (tenir) a lieu lorsqu'un doigt appuie sur l'écran et reste au même endroit pendant environ 1,1 secondes. L'événement Hold est généré à la fin de cet intervalle de temps sans attendre que le doigt se soulève.
Note : l'émulateur ne supporte pas le multitouche.

VIII-H-1 - Événements souris

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) 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)
{

}

VIII-H-2 - Événements de bas niveau

La classe System.Windows.Input.Touch fournit un service au niveau de l'application qui traite l'entrée tactile et déclenche l'événement FrameReported.
L'argument TouchFrameEventArgs de l'événement a la méthode GetPrimaryTouchPoint qui retourne le point tactile (TouchPoint) actuel par rapport à l'élément spécifié (null pour l'écran).
TouchPoint.Action obtient la dernière action qui s'est produite à cet emplacement. TouchPOint.TouchDevice obtient le périphérique tactile qui a généré ce TouchPoint.

Voici l'exemple d'un tap sur un TextBlock nommé « txBlock » :

public partial class MainPage : PhoneApplicationPage
{
// Constructeur
public MainPage()
{
InitializeComponent();
// On utilise Touch.FrameReported l'événement de bas niveau
Touch.FrameReported += OnTouchFrameReported;
}

void OnTouchFrameReported(object sender, TouchFrameEventArgs args)
// Méthode exécutée lors d'un événement sur l'écran
{
TouchPoint MyTouchPoint = args.GetPrimaryTouchPoint(null);
if (MyTouchPoint != null && MyTouchPoint.Action == TouchAction.Down)
{
if (MyTouchPoint.TouchDevice.DirectlyOver == txBlock)
{
txBlock.Text = "Clicker";

txBlock.Foreground = new SolidColorBrush( Colors.Green);

}

}

}

}

VIII-H-3 - Événements de haut niveau

Ces événements de haut niveau sont exécutés sur les objets dérivés de UIElement ; il y a les événements de gesture : Tap, DoubleTap, Hold.

Exemple avec Tap.

<Ellipse Height="109" Fill="red" Tap="buttonTap"  Name="ellipse1" Stroke="Black" StrokeThickness="1"  Width="326" />

private void buttonTap(object sender, System.Windows.Input.GestureEventArgs e)
{
Point p = e.GetPosition(null);// Coordonnées dans l'écran
Point p1 = e.GetPosition(ellipse1);// Coordonnées relative à l'ellipse1
MessageBox.Show(p.ToString());
}

On peut utiliser l'appuie prolongé avec « Hold ».

<Ellipse Height="109" Fill="red" Hold="ellipse2_Hold"  Name="ellipse2" Stroke="Black" StrokeThickness="1"  Width="326" Margin="58,409,72,89" />

On peut aussi utiliser les événements de manipulation :

ManipulationStarted
intervient quand la manipulation démarre.
ManipulationCompleted
intervient quand la manipulation s'arrête.
ManipulationDelta
intervient quand le doigt change de position et survient de multiples fois au cours de la manipulation.

Par exemple, pour effectuer une action si l'utilisateur pose le doigt sur un contrôle comme une ellipse.

<Ellipse ManipulationStarted="Manip" Height="109" Fill="red"  Name="ellipse1"  />

private void Manip(object sender, ManipulationStartedEventArgs e)
{
Point p = e.ManipulationOrigin; // Coordonnées de la manipulation dans l'objet
MessageBox.Show(p.ToString());
}

Le second paramètre est de type ManipulationStartedEventArgs.

L'événement ManipulationDelta permet de « lire » les mouvements de « Translation » et de « Scale » (pas de rotation en WP).
Ici un glissé horizontal du doigt permet d'élargir ou de réduire l'ellipse.

<Ellipse  Height="109" Fill="red" ManipulationDelta="ManipDelta"  Name="ellipse1" Stroke="Black" StrokeThickness="1"  Width="326" />

Pour une événement ManipulationDelta le second paramètre est de type ManipulationDeltaEventArgs.
La propriété DeltaManipulation contient les modifications qui se sont produites depuis le précédent événement ManipulationDelta.

private void ManipDelta(object sender, ManipulationDeltaEventArgs e)
{
Point p = e.DeltaManipulation.Translation;
ellipse1.Width = ellipse1.Width  + p.X;
}

Dans le code C# on peut surcharger la méthode OnManipulationStarted (et les autres événements de manipulation) de la page :
Si l'utilisateur pose le doigt sur l'écran.

// Dans le code de la page
protected override void OnManipulationStarted(ManipulationStartedEventArgs args)
{
// Code a effectuer quand l'utilisateur tape sur l'écran.
args.Complete();
base.OnManipulationStarted(args);
}

VIII-H-4 - Gestures du toolkit

Le Toolkit fournit des fonctionnalités très puissantes.
Il y a dans le Toolkit la classe GestuleService qui permet d'attacher un GestureListener à un élément.
Cela permet de gérer les Tap, DoubleTap, Hold, PinchStarted, PinchDelta, PinchCompleted, Flick, DragStarted, DragDelta, DragCompleted, GestureBegin, GestureCompleted.

Il faut installer le Toolkit, ajouter dans les références « Microsoft.Phone.Controls.Toolkit.dll ».

Dans le code XAML, dans la balise d'ouverture de la page, il faut taper :

xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"

Maintenant on peut utiliser les gestures du toolkit : on retrouve Tap DoubleTap et Hold.
Exemple simple avec Tap ; on va appliquer un GestureService.GestureListener à la grille principale « ContentPanel » et on va « écouter » l'événement Tap.

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

<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
Tap="OnGestureListenerTap"/>
</toolkit:GestureService.GestureListener>

<Ellipse  Height="109" Fill="red"   Name="ellipse1" Stroke="Black" StrokeThickness="1"  Width="326" />
<Button  Content="Button" Height="86" HorizontalAlignment="Left" Name="button1" Width="280" Click="button1_Click" />

<Ellipse Height="109"  Fill="red"   Name="ellipse2"  Width="326" Margin="58,409,72,89" />

</Grid>

private void OnGestureListenerTap(object sender, System.Windows.Input.GestureEventArgs e)
{
Point p = e.GetPosition(ellipse2);
MessageBox.Show(p.X.ToString() + "  " + p.Y.ToString());
}

Un Tap exécute OnGestureService qui ici ouvre une simple MessageBox.
Noter que le Tap fonctionne sur les ellipses et le bouton, pas sur le fond autour.
GetPosition retourne un Point ici par rapport à ellipse2 (qui a été donné en argument), l'origine étant le coin supérieur gauche d'un rectangle contenant l'ellipse. Pour que Tap soit actif même sur le fond, il faut un Background sur la grille.

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

On peut écrire cela en C# :
Ici on a un rectangle, quand on clique dessus, il devient blanc.

Rectangle rectg;

public MainPage()
{
InitializeComponent();
this.rectg = new Rectangle();
rectg.Height = 100;
rectg.Width = 100;
rectg.Fill = new SolidColorBrush(Colors.Cyan);
this.ContentPanel.Children.Add(rectg);
GestureListener gl = GestureService.GetGestureListener(rectg);
gl.Tap += new EventHandler<Microsoft.Phone.Controls.GestureEventArgs>(GestureListener_Tap);
}
private void GestureListener_Tap(object sender, Microsoft.Phone.Controls.GestureEventArgs e)
{
this.rectg.Fill = new SolidColorBrush(Colors.White);
}

Passons rapidement sur GestureBegin et GestureCompleted qui permettent de voir l'objet qui a déclenché la gesture (OriginalSource) et les coordonnés (GetPosition).

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" Background="Transparent">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
GestureBegin="OnGestureBegin"/>
</toolkit:GestureService.GestureListener>

<Ellipse  Height="109" Fill="red"   Name="ellipse1" Stroke="Black" StrokeThickness="1"  Width="326" />
</Grid>

private void OnGestureBegin(object sender, Microsoft.Phone.Controls.GestureEventArgs e)
{
Object  o = e.OriginalSource;
Point p = e.GetPosition(ellipse1);
MessageBox.Show("Taptc");
}

Une séquence Drag (traîner) est constituée d'un événement DragStarted, de zéro ou plusieurs événements DragDelta et d'un événement DragCompleted (lorsqu'un doigt touche l'écran, se déplace, puis quitte l'écran).

DragStarted permet, outre l'objet et les coordonnées, de voir la direction de départ (retourne une « Orientation » qui est une enum ayant deux valeurs : Horizontal et Vertical).

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" Background="Transparent">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
DragStarted="OnGestureDrag"/>

</toolkit:GestureService.GestureListener>

private void OnGestureDrag(object sender, Microsoft.Phone.Controls.DragStartedGestureEventArgs e)
{
Object  o =e.OriginalSource;
Point p = e.GetPosition(rectg);
Orientation orient =e.Direction;
MessageBox.Show (orient.ToString()); // Horizontal ou Vertical
}

DragDelta, outre les informations précédentes, permet de voir le déplacement horizontal et vertical (positif si le déplacement se fait à droite et en bas).

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" Background="Transparent">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
DragDelta="OnGestureDragDelta"/>

</toolkit:GestureService.GestureListener>

private void OnGestureDragDelta(object sender, Microsoft.Phone.Controls.DragDeltaGestureEventArgs e)
{
Object  o =e.OriginalSource;
Point p = e.GetPosition(rectg);
Orientation or =e.Direction;
MessageBox.Show (or.ToString()); //Horizontal ou Vertical

double x = e.HorizontalChange;
double y = e.VerticalChange;
MessageBox.Show(x.ToString() + " " + y.ToString());
}

DragCompleted présente les mêmes informations (sur quel objet on lâche) mais avec en plus HorizontalVelocity et VerticalVelocity.

L'événement Flick a lieu lorsqu'un doigt quitte l'écran alors qu'il se déplace encore, ce qui suggère que l'utilisateur souhaite obtenir une inertie ; je le fais lorsque j'enlève vers le haut l'écran de démarrage. Les arguments comprennent une valeur Angle (de 0 à 360 °, mesuré dans le sens des aiguilles d'une montre à partir de l'axe des X positifs horizontal à droite), ainsi que des valeurs HorizontalVelocity et VerticalVelocity (mesurées en pixels par seconde).
On va l'appliquer à une ellipse.

<Ellipse  Height="109" Fill="red"   Name="ellipse1" Stroke="Black" StrokeThickness="1"  Width="326">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
Flick="OnFlick"/>

</toolkit:GestureService.GestureListener>
</Ellipse>

private void OnFlick(object sender, FlickGestureEventArgs e)
{
double x = e.HorizontalVelocity;
double y = e.VerticalVelocity;
double a = e.Angle;

MessageBox.Show(x.ToString() + " " + y.ToString() + " " + a.ToString());

}

La séquence Pinch (pincement) a lieu lorsque deux doigts touchent l'écran ; elle est généralement interprétée pour développer ou réduire un objet. Pinch correspond aux deux doigts qui s'écartent ou se rapprochent.
La position actuelle d'un doigt (le doigt principal) est toujours disponible avec la méthode GetPosition.

Il y a PinchStarted.

<Ellipse  Height="109" Fill="red"   Name="ellipse1" Stroke="Black" StrokeThickness="1"  Width="326">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
PinchStarted="PinchS"/>

</toolkit:GestureService.GestureListener>
</Ellipse>

private void PinchS(object sender, PinchStartedGestureEventArgs e)
{
double a = e.Angle;
double d = e.Distance;

}

Il y a aussi PinchDelta.

<Ellipse  Height="109" Fill="red"   Name="ellipse1" Stroke="Black" StrokeThickness="1"  Width="326">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
PinchDelta="PinchD"/>

</toolkit:GestureService.GestureListener>
</Ellipse>

On peut voir le « DistanceRatio » rapport de la distance actuelle entre les deux doigts et la distance de départ. Il y a aussi le « TotalAngleDelta » différence de l'angle actuel entre les deux doigts et l'angle de départ.

private void PinchD(object sender, PinchGestureEventArgs e)
{
double ratio = e.DistanceRatio;
double a = e.TotalAngleDelta;
}

Il existe aussi PinchCompleted qui n'apporte rien de plus.

VIII-I - Perdre du temps

Parfois on a besoin de perdre du temps, suspendre l'exécution durant 1/2 s par exemple.
Il faut inclure l'espace de nom System.Threading puis utiliser la méthode Sleep du Thread (avec en paramètre 500 ms).

using System.Threading;

Thread.Sleep(500);

VIII-J - Sortir de l'application

Logiquement, le fait d'utiliser le bouton « Back » quand on est sur la première page de l'application permet de sortir de l'application.
Si on quitte l'application avec le bouton central, elle se met à l'état dormant et se fermera automatiquement si on s'y revient pas.
Il n'y a donc pas besoin de méthode pour quitter l'application.

Si on veut vraiment, Application.Exit() n'existant pas, il faut déclencher une exception non gérée ! Ce n'est pas élégant !

private void Exit(object sender, RoutedEventArgs e)
{
throw new Exception("Exit");
}

VIII-K - Application démonstration ou payée

On peut distribuer une application gratuite en démonstration ; elle pourra ensuite être achetée et ainsi donner accès à des niveaux ou fonctions supplémentaires.
La Méthode IsTrial de LicenceInformation indique si on est en mode démo.

//Ajouter en haut
using Microsoft.Phone.Marketplace;

//instancier une licenceinformation
LicenseInformation licInfo = new LicenseInformation();

//Tester si on est en version démo ou payée
if (!licInfo.IsTrial())
{
//Faire seulement pour les users ayant payé.
}
else
{
//Faire pour tous.
}

Code non testé.

VIII-L - Mes trucs

Sans prétention !

Attention au nom de l'application !

Si je crée une application se nommant « calcul » puis une variable « calcul » et si je me fais aider pour l'écriture (IntelliSense), je peux avoir des confusions entre espace de noms « calcul » et variable « calcul » :

De même si le nom de l'application a le nom d'une classe.

L'enfer des accolades.

Dans le code C#, quand on a plusieurs classes dans un espace de noms ou si on a des if imbriqués, il y a plein d'accolades ouvrantes et fermantes.
C'est l'horreur pour savoir où ajouter une fonction ou une classe ou un else ; si on se trompe et qu'on met une fonction une accolade trop haut, les messages d'erreur ne sont pas toujours clairs.
Le plus simple pour s'y retrouver est de cliquer sur une accolade ouvrante cela surligne l'accolade fermante correspondante.

Groupe de contrôles

Avant l'univers .NET, en VB6 par exemple, on pouvait créer des groupe de contrôles, les contrôles d'un groupe avaient les mêmes fonctions évènements et pour un clic sur différents boutons on exécutait la même fonction.
Pour simuler les « groupes de contrôles » qui n'existent pas en WP on a un moyen simple : en XAML, pour que plusieurs boutons exécutent la même fonction, on peut indiquer Click="MyButtonClick" pour plusieurs boutons.

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

Ensuite dans la fonction MyButtonClick il suffit de regarder le « sender » pour voir quel bouton a été cliqué (il faut le caster en bouton avant de tester son nom).

On peut aussi utiliser un StackPanel « parent » pour gérer l'évènement Click de tous les boutons enfants dans le StackPanel (comme il n'y a pas de « Click= » dans les boutons, l'évènement Click remonte dans le StackPanel) :

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

IX - Compilation, test, distribution

IX-A - Compilation

La compilation c'est transformer un code source (le code XAML et le code C# que vous avez tapé), en code exécutable et distribuable.

En haut on peut choisir Windows Phone Emulator ou Windows Phone Device pour lancer l'application sur l'émulateur ou le portable connecté (si on est enregistré) :

Puis on peut choisir Debug (permettant le débogage) ou Release (pour la distribution).

Si on clique sur le bouton d'exécution (ou F5) on lance le débogage.

Par le menu « Build » puis « Générer la solution » ou F6, on compile l'application.

Cela crée un répertoire « Bin » avec un sous répertoire « Debug » ou « Release » contenant un fichier .xap (archive compressée contenant l'exécutable, les DLL, les ressources, images…).
Le fichier .xap est en fait un fichier .zip (vous pouvez renommer ce fichier en .zip et ensuite aller voir ce qu'il y a dedans).

Pour tester le .xap passer par le menu « Build» puis « Déployer » ; il s'exécutera dans l'émulateur.

On se souvient où se trouvent les sources des applications.
Pour mon application qui se nomme « CalculTout » voir dans 'C:\Users\Philippe\Documents\Visual Studio 2010\Projects\CalculTout'.
Il y a les fichiers « CalculTout.sln » et « CalculTout.suo ».

Il y a dessous un répertoire nommé aussi « CalculTout » qui contient les fichiers « .xaml » et « .xaml.cs » et les répertoires « Bin » et « Obj » qui contiennent chacun les répertoires « Debug » et « Release ».

Quand on compile il y a création des fichiers « .g.cs » et « .g.i.cs » en C# pour chaque page XAML. On n'a théoriquement pas besoin de s'en occuper sauf quand il y a un bogue dans le code XAML et que VS s'arrête sur une instruction de ces fichiers ; lire l'erreur donne une indication sur la correction nécessaire dans le code XAML.

IX-B - Test sur un Windows Phone

Il est possible de voir tourner votre application dans l'émulateur.
Impossible de charger simplement sur votre Windows Phone une application que vous venez de développer.
Impossible donc de tester une application sur votre portable ou de faire une application uniquement pour les membres de votre entreprise.
La solution : enregistrez-vous comme développeur chez Microsoft (99 $) et là vous aurez la possibilité de charger sur votre portable une application pour la tester.

Pour déboguer, il faut choisir l'émulateur en haut au milieu :

Si on clique sur le bouton d'exécution (ou F5) on compile l'application, on charge l'émulateur WP7 et on exécute l'application dans l'émulateur.

La souris permet de simuler le doigt sur l'écran.

À sa gauche il y a une barre d'outils qui apparaît quand le curseur de la souris s'approche.
Les boutons de haut en bas :

- fermer l'émulateur ;

- réduire l'émulateur ;

- faire pivoter l'émulateur pour passer en mode paysage ;

- faire pivoter mais dans l'autre sens.

- ajuster l'émulateur à la taille de l'écran ;

- définir le zoom de l'émulateur ;

- autres.

- accéléromètre ;

- localisation ;

- copie d'écran.

Sur la droite de l'écran, il y a des compteurs :

En détails :

De haut en bas ils indiquent

- Thread de composition (Render Thread).
3 chiffres. En nombre de frames par seconde (maximum : 120)
Bonne valeur = 60.
Mauvaise valeur si inférieur à 30 (devient rouge).

- Thread UI (User Interface Thread).
3 chiffres. En nombre de frames par seconde (si la valeur est faible, la réaction de l'UI est moins bonne).
Bonne valeur si supérieur à 15.
Devient rouge si inférieur à 15.

- Textures (mémoire utilisée par les textures)
6 chiffres.

- Compteur de surfaces
Nombre de surfaces passées au chip graphique.

- Compteur IRT (Intermediate Texture Count).

- Screen Fill rate (nombre d'écrans dessinés par frame).
Un écran représente 480 x 800 pixels.
si> 3 mauvaises performances.
si inférieur à 2, bonnes performances.

Dans le constructeur de la classe App de votre application (App.xaml.cs), vous pouvez faire disparaître ces compteurs.

Application.Current.Host.Settings.EnableFrameRateCounter = false;

C'est bien pour faire des copies d'écran.

Un profiler dédié au projet WP7 a été ajouté, il n'est pas à l'heure actuelle dans sa version définitive, mais il permet de pouvoir cibler certains problèmes de frame rate et d'utilisation de thread sur la CPU/GPU.
Passez par le menu « déboguer » puis « Démarrer l'analyse des performances de Windows Phone ».

IX-C - La distribution

Il faut préparer des images.

Trois images pour le MarketPlace :
Large Mobile App Tile : 173*173 px 96 DPI, fichier .PNG ;

Small Mobile App Tile : 99*99 px 96 DPI, fichier .PNG ;

Large PC App Tile : 200*200 px 96 DPI, fichier .PNG.

Une (minimum) à huit captures d'écran (Screenshots) 480*800 px 96 DPI, fichier .PNG ;

Une image en option (Background art) utilisée si l'application devient l'application du jour 1000*800 px 96 DPI, fichier .PNG.

On peut charger à partir du site CodePlex un logiciel gratuit (http://wpiconmaker.codeplex.com/) permettant, très simplement, de créer les icônes à partir d'une image.

On se souvient que dans l'application il y a les 2 icônes :
Background.png, 173×173 px (utilisé sur l'écran de démarrage).
ApplicationIcon.png, 62x62 px (utilisée dans la liste des applications installées).

La distribution se fait uniquement par le Marquet Place.

Merci à Tomlev pour sa relecture ses corrections et ses conseils sur une partie de ce document, il m'a été d'un grand secours.
Merci à jacques jean pour sa relecture orthographique d'une grosse partie du document. Merci aux autres.
A suivre…
Philippe LASSERRE.