Programmation Windows en C# avec l’architecture .NET (dotNET) les premiers pas ... Outil de développement : Visual Studio 2005 standard edition .NET , pointNET , dotNET, c'est quoi ? Le nom exact est .NET Framework, c'est une bibliothèque de classes intégrées dans un environnement d'exécution . La version utilisée ici est .NET Framework 2.x intégrée dans les systèmes d'exploitation Xp et Server 2003. La version 3.0 est intégrée dans Vista. Un premier programme Génération de l’ossature de l’application par VS2005 / Nouveau Projet / Visual C# / Application Windows (décocher l'option "Créer le répertoire pour la solution"), on obtient deux classes essentielles (voir les classes avec la fenêtre Affichage de classes, voir les fichiers la fenêtre Explorateur de solutions): o un programme principal dans la classe Program (fichier Program.cs) o un formulaire principal dans la classe Form1 (fichier Form1.cs) o La fenêtre principale est constituée par un formulaire : o un objet de la classe Form1 héritée de la classe Form o la classe Form appartient au paquetage System.Windows.Forms le code généré est équivalent à : => using System.Windows.Forms Form form1 = new Form(); o o en dotNET, les paquetages prennent le nom de namespace : espace de nommage, en C#, on référence un namespace par la directive using (analogue à la directive import de Java) qui permet au compilateur de localiser une classe dans son espace sans avoir besoin de qualifier complètement le nom de la classe (namespace.classname) noter la diffrence entre Forms et Form : Forms est le sous-espace de noms de System.Windows, tandis que Form est une classe de l'espace de noms System.Windows.Forms modifications des caractéristiques du formulaire: modification des propriétés du formulaire form1 dans l’éditeur de ressources intégré (fenêtre Design et fenêtre Propriétés) ou par programmation dans la méthode Main() de la classe Program : => form1.Text = "Titre de la fenêtre"; form1.BackColor = Color.BlanchedAlmond; la classe Color est issue du paquetage System.Drawing (ajouter using System.Drawing;) et offre un grand nombre de couleurs sous forme de constantes prédéfinies. Dans ces 2 instructions, on peut noter que Text et BackColor sont des propriétés C# BlanchedAlmond est une constante, c'est à dire un attribut statique de la classe Color. Daniel Vielle 1 Programmation DotNET L'application encapsule la boucle de messages dans l'instruction : Application.Run( new Form1()); o la classe Application est issue du paquetage System.Windows.Forms o elle fournit des méthodes statiques permettant de démarrer et d'arrêter des applications et des threads o la méthode statique Run() démarre une boucle de messages dans le thread courant et, éventuellement, affiche un formulaire o des méthodes permettent d'intervenir éventuellement dans la boucle de messages. En C# comme en Java, tout est classe/objet, l'objet formulaire form1 est instancié dans la méthode statique main() de la classe Program. le paquetage Drawing, c'est à dire en .NET l'espace de noms System.Drawing et ses sous-espaces de noms, constituent la bibliothèque graphique de .NET sur Windows, baptisée GDI+ pour Graphical Device Interface, le signe + indiquant des extensions pour un modèle objet ainsi que des fonctionnalités telles que la transparence, les transformations géométriques, etc. Son utilisation repose sur le contexte de périphérique mis en oeuvre par un objet de la classe Graphics (System.Drawing.Graphics). Gestion des événements Comme dans tout environnement graphique, la programmation d'une application Windows avec le framework .NET est de type événementielle : l'utilisateur agit sur le clavier et la souris, ces actions génèrent des interruptions matérielles prises en compte par le système d'exploitation, celui-ci génère des événements sous forme de messages placés dans un FIFO propre à chaque application graphique (plus précisément, chaque thread pilotant une interface graphique). La boucle de messages consiste à venir retirer un à un chaque message du FIFO et à l'interpréter en appelant une séquence de code prévue pour traiter l'événement. Ce fonctionnement est résumé ainsi : objet graphique source d'événement + action utilisateur (ou événement système comme la fin de décompte d'une minuterie par exemple) IT événement message dépôt dans FIFO retrait du FIFO appel séquence de traitement retour d'un code de traitement Les 3 dernières actions sont réalisées par la boucle de messages de chaque application. Dans l'API Win32, la boucle de messages est explicitement écrite dans la fonction WinMain() de l'application, elle est modifiable. Les séquences de traitement sont décrites dans la procédure de fenêtre associée à chaque fenêtre WndProc(), dans laquelle une instruction de type switch aiguille vers la séquence de traitement de l'événement. Avec les MFC, la boucle de messages est encapsulée dans une classe générée par un assistant. Les séquences de traitement sont rangées dans des fonctions que l'on associe aux événements par une table utilisée pour l'aiguillage (MESSAGE_MAP). En Java, la boucle de messages est réalisée dans la machine virtuelle Java, les séquences de traitement sont décrites dans les méthodes d'une classe qui implémente une interface propre à chaque événement. Les objets correspondants, que l'on nomme écouteurs (listeners), sont abonnés aux objets source des événements. Daniel Vielle 2 Programmation DotNET Avec les Windows Forms du framework .NET, la boucle de messages est encapsulée dans la méthode Application.Run(). Chaque séquence de traitement est décrite dans une fonction. Cette fonction est enregistrée dans les propriétés de l'objet source d'événement par un intermédiaire qui porte le nom de délégué. Un délégué dans le langage C# est analogue à un pointeur de fonction. Un exemple : gestion de l'événement Paint L'événement Paint est généré par le système Windows à chaque fois que tout ou partie de la zone client d'une fenêtre (d'un formulaire) doit être redessinée. La séquence de traitement est standard pour tous les composants graphiques disponibles (boutons, listes, …) elle est donc dans ce cas facultative sauf si on souhaite "retoucher" le composant. Pour personnaliser un formulaire, on spécifie la séquence d'instructions de dessin dans une méthode que l'on enregistre par l'intermédiaire d'un "délégué" de l'événement Paint. L'événement Paint est un objet de classe System.Windows.Forms.Control.Paint. Chaque composant graphique (formulaire, controle, ...) possède un événement Paint accessible par une propriété C#. Comme tous les autres événements Windows Forms, il dispose de la surcharge d'opérateur += qui permet d'ajouter un nouveau gestionnaire pour traiter cet événement. La syntaxe générique qui permet d'enregistrer (d'abonner) une méthode gestionnaire à un événement, est : objetSource.événement += new délégué( méthode); Le nom du délégué est imposé, le nom de la méthode est libre (c'est une différence avec les écouteurs d'événements du langage Java). Un délégué en C# correspond à un pointeur de fonction en C ou en C++ et n'existe pas en Java, il fait le lien avec la véritable méthode de traitement de l'événement, appelée gestionnaire d'événement (un écouteur en Java). On utilise l'opérateur += parce que, de manière générale, on peut abonner plusieurs gestionnaires à un même événement : lors du déclenchement de l'événement, l'appel du délégué par toute la chaîne Windows répercute l'appel à toutes les fonctions vers lesquelles il pointe. On utilise l'opérateur new car un délégué est un objet. Le délégué et la méthode gestionnaire ont le même prototype (sauf le nom de la méthode qui est libre). Pour l'événement Paint, le délégué se nomme PaintEventHandler : delegate void PaintEventHandler( Object source, PaintEventArgs pea); static void méthode( Object source, PaintEventArgs pea); Le second paramètre du délégué ou de la méthode de traitement de l'événement est un objet contenant les caractéristiques de l'événement (un PaintEventArgs dans cet exemple). Pour redéfinir, par exemple, la méthode de dessin d'un formulaire principal form1 par la méthode PaintPrincipal(), on écrit : form1.Paint += new PaintEventHandler( PaintPrincipal); public static void PaintPrincipal( Object form, PaintEventArgs pea) { Graphics g = pea.Graphics; g.Clear(Color.Coral); } form1.Paint est la propriété Paint du formulaire, c'est un objet de type Control.Paint on l'appelle tout simplement l'événement Paint PaintEventHandler est le délégué de l'événement Paint (nom et prototype imposés) Daniel Vielle 3 Programmation DotNET PaintPrincipal est la méthode de traitement de l'événement choisie par nous, son nom est quelconque, son prototype est imposé le paramètre formel form est une référence vers l'objet source de l'événement; ici, c'est le formulaire form1 le paramètre formel pea est de type PaintEventArgs, c'est l'objet événement de type plus général EventArgs qui a été instancié et transmis à la méthode de traitement, il contient entre autres des attributs spécifiques de l'événement, en particulier une référence vers un objet de type Graphics qui correspond au Contexte de Périphérique (Device Context) de l'API Win32. Un objet Graphics dispose de toutes les méthodes de dessin; ici, la méthode Clear() efface le formulaire en changeant la couleur du fond (pour référencer les classes Graphics et Color, ajouter using System.Drawing;) Remarque : pour avoir une idée du moment où sont générés les événement Paint, on peut rajouter, dans le gestionnaire : Console.WriteLine("Evénement Paint"); qui laissera une trace du traitement de chaque événement Paint dans la fenêtre "Sortie" de Visual Studio pendant l'exécution. Peindre par héritage Une façon plus élégante de dessiner dans un formulaire consiste à redéfinir la méthode de dessin de la classe de base. Il s'agit de la méthode onPaint() de la classe Control (plus précisément System.Windows.Forms.Control) dont hérite la classe Form (System.Windows.Forms.Form). A partir d'une application Windows fraîchement générée, on modifie le code du formulaire dans Form1.cs en ajoutant dans sa classe (après le code du constructeur par exemple) la méthode : protected override void OnPaint( PaintEventArgs e) { base.OnPaint(e); Graphics g = e.Graphics; nbPaints++; g.DrawString("Formulaire repeint " + nbPaints + " fois", Font, Brushes.Chocolate, new PointF(0, 0)); Console.WriteLine("Evénement Paint n° " + nbPaints + " dans OnPaint()"); } Le mot-clé override indique que l'on redéfinit la méthode correspondante de la classe parente. L'encapsulation protected permet de prolonger éventuellment l'héritage vers d'autres classes qui seront dérivées du formulaire en cours dans des développements futurs éventuels. Dans la méthode, il est recommandé d'appeler la méthode parente grâce au mot-clé base (analogue au mot-clé super de Java) de façon à assurer la continuité des traitements. La méthode DrawString() est une variante plus élaborée de la méthode DrawText() de l'API Win32. Etape 1 : Liens entre les événements Paint et Size Nouveau Projet : Grille1, on renomme la classe de formulaire par défaut Form1 en FormPrincipal Objectif : dessiner la grille redimensionnable du projet Agenda Daniel Vielle 4 Programmation DotNET Code : redéfinir la méthode OnPaint() et dessiner avec les méthodes de l'objet Graphics Méthodes utilisées : - Rectangle( position, taille) DrawRectangle( couleur, Rectangle) DrawString ( texte, fonte, couleur, position, alignement) Autres classes utiles : - Point dispose de 2 attributs-propriétés X et Y Size comme Point, 2 attributs-propriétés Width et Height StringFormat et StringAlignment pour DrawString Brushes et Pens pour colorier Constantes : qualifier avec le mot-clé const les grandeurs géométriques de la grille : - nombre de jours - nombre de créneaux horaires - nombre de pixels en haut et en bas - nombre de pixels à gauche et à droite initialiser ces constantes dans le constructeur du formulaire FormPrincipal Redimensionnement de la fenêtre : on constate que, par défaut, contrairement à Win32, un événement Size ne provoque pas un événement Paint. Il faut rajouter, dans le constructeur du formulaire : ResizeRedraw = true; Daniel Vielle // génère Paint à chaque Size 5 Programmation DotNET