Une remarque pertinente ?
Une critique impertinente ?
Un lynchage en règle ?
Une invitation sous les tropiques ?

Ecrivez-moi !

Conçu et enseigné tel qu'en lui même, avec pertes, fracas et humour de qualité supérieure            
 par Christophe Darmangeat dans le M2 PISE du Master MECI (Université Paris 7)            


 

 

 

Partie 12

Travailler avec plusieurs Form

Jusqu'à maintenant, nous n'avons jamais programmé d'application qui utilise plusieurs Form à la fois. Or, il faut quand même connaître quelques éléments à ce propos, car dès que les programmes grossissent, ils ont beaucoup de mal à tenir sur une Form unique.

Il y a deux grandes manières d'utiliser plusieurs form dans une application :

  • Les Form sont toutes sur le même plan ; l'une remplace l'autre au fur et à mesure que l'application avance, et on n'a jamais deux Form actives en même temps
  • Il existe une Form « mère » – celle de l'application proprement dite –, au sein de laquelle les autres Form occupent une position subordonnée (ce sont des Form filles). Ces Form filles sont ouvertes ou fermées par l'utilisateur, car chacune d'elles contient un document, ou une série de boutons s'il s'agit d'une barre d'outils.

Si la première architecture n'est pas très fréquente, on aura reconnu dans la seconde l'organisation classique de toutes les applications professionnelles de bureautique, où la fenêtre principale comprend les menus qui servent à gérer les différents documents (au besoin, plusieurs en même temps), qui seront ouverts.

1. Des form sans hiérarchie

1.1 Activer une Form depuis une autre Form

Le premier problème qui se pose est de comprendre le fonctionnement des activations de Form ‐ je rapelle qu'on se situe ici hors du cas où une Form principale contient des Form enfants. Nous avons donc conçu une série de Form, que j'appellerai par leur Name, en l'occurrence et par fainéantise : Form1, Form2, Form3, etc. Et je suppose donc qu'au cours de l'application, telle action sur un des contrôles de Form1 devra déclencher le passage à Form2, telle action sur un des contrôles de Form2 le passage à Form3, etc.

Nous savons déjà demander à la machine d'afficher une form et de la rendre active : il suffit pour cela d'employer la méthode Show (comme nous l'avons fait maintes fois avec les MessageBoxDialog). Oui mais voilà : il y a une petite subtilité supplémentaire. Impossible en effet d'écrire simplement :



Form2.Show();


...car la méthode n'est pas disponible pour la classe Form. Le truc, c'est qu'il faut commencer par instancier la Form avant de pouvoir se servir du résultat de cette instanciation. En fait, c'est assez logique, quand on y réfléchit un peu. Ce que nous avons fabriqué sous le nom de Form2 en mode design, ce n'est pas (encore) un objet ; c'est une classe-fille de la classe Form en général. C'est pour cela que le code associé à toute Form (par exemple, Form1) comprend cette ligne :

public partial class Form1 : Form

Ce qui signifie : « crée une classe du nom de Form1, qui héritera de la classe Form ».

Revenons donc à notre problème. Nous ne pouvons pas directement rendre active Form2, mais nous pouvons rendre active une instance de Form2. Il nous faut donc créer cette instance, pour l'activer ensuite :

var truc = new Form2();
truc.Show();

Et là, pas de problème, ça passe comme une lettre à la poste.

Au passage, profitons-en pour apprendre qu'une Form est dite modale si elle est obligatoirement la Form active tant qu'elle est présente (autrement dit si, tant qu'elle est là, on ne peut rendre aucune autre Form active). La méthode que nous avons employée affiche Form2 de manière non modale : l'utilisateur peut librement réactiver la Form de départ s'il le souhaite. Pour que Form2 soit modale, il suffit d'employer, en lieu et place de la méthode Show, la méthode ShowDialog.

1.2 Événements liés aux Form (rappel)

Au lancement de l'application, l'ensemble des Form est chargé en mémoire. Pour chacune de ces Form, l'évènement Load est donc déclenché, et si l'on a écrit une procédure liée à cette évémenent, elle est exécutée (a priori, une fois pour toutes).

Dans le cas où l'application emploie plusieurs Form, il faut donc bien faire la différence avec l'évement Activate : celui-ci se déclenchera à chaque fois qu'une Form sera rendue active, soit par l'exécution d'une méthode Show, soit parce qu'elle était disponible à l'écran et que l'utilisateur cliquera sur elle.

1.3 Transmettre des informations d'une Form à l'autre

Voilà un petit problème tout bête qui peut nous entraîner très loin... Voilà pourquoi, dans ce cours, on se contentera de quelques indications.

1.3.1 Passer par des variables globales

La méthode la plus stupide (mais la plus simple, et la plus efficace) consiste à ne pas s'embêter. Si nous avons besoin d'informations communes à toutes les Form de notre application, nous stockerons ces informations dans des variables accessibles depuis n'importe quelle Form. L'avantage de cette méthode est la simplicité de sa mise en œuvre.

Pour commencer, les variables seront déclarées à la fois publiques et statiques. Publiques, parce qu'elles devront être visibles depuis n'importe quelle Form. Statiques, parce qu'elles devront conserver leur valeur indépendamment des différents appels de procédures. Une telle déclaration s'effectuera de la manière suivante :

public static int toto = 0;

Mais il ne suffit pas de déclarer la variable ; encore faut-il la déclarer au bon endroit. En l'occurrence, tout dépend de la manière dont est organisée notre application et dont les différentes form se succèdent au cours de l'exécution.

  1. dans le cas le plus simple, il existe une Form (celle par laquelle se lance l'application) qui pilote toutes les autres. Viisible ou invisible, elle reste présente en mémoiretout au long de chaque exécution. En pareil cas, pas besoin de s'embêter : on déclarera les variables globales dans cette form.
  2. si aucune form ne joue ce rôle de pivot, et si les form sont chargées / déchargées de la mémoire au fur et à mesure, alors les variables globales devront être déclarées dans une classe publique, indépendante de l'activation de telle ou telle Form. Une telle classe doit être créée via la fenêtre « explorateur de solutions », en haut à droite de l'écran. Un clic droit sur le projet, et la commande Ajouter – Classe. Une fois la classe créée, il est possible (voire souhaitable) de changer son nom. Mais surtout il est impératif de préciser que cette classe sera (elle aussi, et pour les mêmes raisons) publique :

    public class maClasseGlobale

    À partir de là, on pourra dans n'importe quelle procédure du projet accéder à notre variable toto, par exemple en écrivant :

    maClasseGlobale.toto = maClasseGlobale.toto + 1

1.3.2 Manipuler directement les propriétés des contrôles externes

Un problème se pose lorsqu'on veut pouvoir agir sur un contrôle situé sur une form depuis une procédure située sur une autre form. Par défaut, les contrôles sont « privés », c'est-à-dire que leur propriété Modifiers vaut Private. Pour que le contrôle devienne accessible depuis n'importe quelle Form du projet, il suffit de passer cette propriété à Public. Ainsi, si l'on veut écrire « coucou » sur le bouton monBouton de Form2, on aura (après avoir mis la bonne valeur pour la propriété Modifiers de monBouton) quelque chose du genre :

var maForm = new Form2();
maForm.monBouton.Text = "coucou";

Cette technique possède l'avantage de la simplicité. Elle souffre néanmoins de deux inconvénients.

  1. elle n'est pas d'une grande sécurité. Nous avions déjà abordé cette question en cours d'algorithmique, à propos des fonctions et des procédures. Si l'on veut faire travailler ensemble des dizaines de sous-traitants (les procédures) sur un même chantier (l'application), il est tentant de construire un hangar ouvert dans lequel on entreposera tout ce que ces sous-traitants ont besoin de s'échanger. Mais alors, n'importe quel sous-traitant malhonnête ou simplement maladroit (comprenez : une procédure buggée) peut mettre le bazar dans toute l'application. C'est pourquoi, même si elles sont plus difficiles à mettre en œvre, on préfèrera des solutions où les échanges de matériaux et d'information entre les sous-traitants ne se font pas dans un hangar ouvert aux quatre vents, mais par des protocoles dûment formalisés et contrôlés – vous aurez reconnu les transmissions de paramètres. En l'occurrence, rendre un contrôle public, ce n'est pas en soi un problème, mais cela peut en devenir un...
  2. l'autre souci, beaucoup plus direct, est que cette technique ne peut fonctionner que si le contrôle modifié fait partie d'une form qu'on a explicitement instanciée par du code. Autrement dit, avec cette manière de procédern, on peut modifier un contrôle d'une form appelée depuis une form appelante, mais pas l'inverse. La form principale, celle qui lance l'application, a donc accès aux form qu'elle appelle, mais elle-même reste donc inaccessible pour ces form appelées. Si l'on veut contourner l'obstacle, le mieux reste donc de passer par les variables publiques évoquées précédemment.

1.3.3 Une technique plus sécurisée

Pour modifier des contrôles sur une form extérieure, plutôt que rendre les contrôles publics, on peut préférer utiliser le passage de paramètres (donc d'informations) via le constructeur, lorsqu'on instancie cette form.

Reprenons l'exemple de ma classe Form2, sur laquelle je souhaite pouvoir tripoter depuis Form1 le texte du bouton monBouton. Normalement, par défaut, le constructeur de Form2 (visible sur le code attaché à cette classe), possède la physionomie suivante :

public Form2()
{
   InitializeComponent();
}

Pour lui transmettre une (ou plusieurs) informations, il suffit d'introduire un paramètre (ou plusieurs) dans cette procédure, et de s'en servir pour modifier une (ou plusieurs) propriété(s) d'objets de la Form :

public Form2(string truc)
{
   InitializeComponent();
   monBouton.Text = truc;
}

Ce qui signifie : « lors de toute instanciation, on doit te fournir en paramètre un texte, qui deviendra illico ce qui est écrit sur le bouton monBouton. Dès lors, pour obtenir le résultat voulu, il suffira, depuis n'importe quelle Form, d'instancier Form2 avec ledit paramètre :

var maForm = new Form2("coucou");
Exercice

Exécutable

Sources

Le maillon faible

2. La structure hiérarchique (une mère, des filles)

Pour finir ce cours en beauté, quelques mots des applications poétiquement dites "MDI", à savoir : Multiple Document Interface. Il s'agit du lot commun des applications Windows bien connues, du genre Word, Excel, Photoshop et tutti quanti. Toutes ces applications, malgré leurs différences, ont en commun une interface gérée par Windows, dans laquelle :

  • il existe une fenêtre principale (celle de l'application proprement dite), appelée Form parente.
  • cette fenêtre parente peut contenir un nombre quelconque de fenêtres enfant. Chacune de ces Form enfant peut représenter un document  différent, ou plusieurs vues différentes d'un même document.
  • les menus existent exclusivement sur la Form parente, mais ils dépendent de la Form enfant active.
  • les Form enfants peuvent être minimisées, agrandies, maximisées, disposées notamment en cascade ou en mosaïque

La réalisation d'une application MDI en C# ne pose pas de difficultés exagérées, à condition, comme toujours, de procéder méthodiquement.

2.1 En mode design

La première chose à faire est de définir la Form principale (parente). Ceci s'effectue très simplement, en donnant la valeur True à sa propriété IsMdiContainer. Visuellement, le designer indique alors qu'il s'agit d'une form parente en rendant son fond gris foncé. A priori, la form parente, dans une application, est unique. On ne peut cependant pas exlure qu'il en existe plusieurs : mais là, ça devient quand même une interface un tantinet tordue.

Les form enfants, elles, ne nécessitent pas de manipulation particulière : il faut simplement qu'il en existe une par type de Form enfant voulu, sachant que les différents exemplaires d'un même type seront instanciés dynamiquement par du code. Par exemple, si l'on veut créer un traitement de textes, on aura une form enfant (qui contiendra du texte) et qui, lors de l'exécution, sera dupliquée en autant d'exemplaires qu'il y aura de documents différents à gérer en même temps.

2.2 Instanciation des Form enfants

Au cours de l'application, les différentes Form enfants seront toutes, par définition, des instanciations de celle(s) qu'on aura créées en mode design. Pour le dire autrement, en mode design, on fabrique le moule : il s'agit à présent d'écrire le code nécessaire pour cuisiner les gâteaux.

Sans surprise, ce code obéit aux règles générales de la création dynamique de contrôles. La seule nouveauté consiste à signifier que la nouvelle Form est une Form enfant de telle Form parent. Ceci s'effectue via la propriété MdiParent de la Form enfant, une propriété accessible seulement par le code.

Ainsi, en admettant que nous ayons baptisé monGateau la Form servant de modèle à nos Form enfants, voici le code qui crée une nouvelle instance de cette Form à partir d'un contrôle ou d'un menu de la Form principale :

Form monGateau = new Enfant();
monGateau.MdiParent = this;
monGateau.Visible = true;

2.3 Maniement des Form enfants

Ce point pose problème uniquement lorsqu'on doit manier les Form enfant à partir de la Form parent. Le maniement des Form enfants à partir d'elles-mêmes se fait tout naturellement : programmer une Form à partir d'elle-même, c'est très précisément ce que nous avons fait depuis le début de ce cours.

La seule difficulté consiste à savoir comment désigner une (ou plusieurs) form enfants à partir de la form principale. Pour cela, le moyen le plus simple consiste à partir du fait que cette dernière définit une collection de Form enfants : this.MdiChildren. On pourra ainsi balayer l'ensemble des Form enfant par une boucle parcourant cette collection, ou rechercher une Form particulière au sein de cette collection en fonction de l'une ou l'autre de ses propriétés.

On notera pour terminer que la Form parent possède la propriété ActiveMdiChild, qui renvoie la Form enfant actuellement active.

 

Exercice

Exécutable

Sources

Album Photo