La POO appliquée aux winforms

Comme vous le savez tous, dotnet est une plateforme dont les langages sont 100 % orientés objet. Pas une ligne de code ne peut être écrite en dehors d'une classe.

Conceptuellement parlant, et ceci est peut-être moins connu, nous pouvons appliquer les mêmes mécanismes de POO au niveau de nos formulaires qu'au niveau de nos composants. C'est ce que nous allons démontrer en utilisant un exemple simple.

Cet exemple a été réalisé avec Microsoft Visual C# Express Edition. Il est donc basé sur le framework 2. Pour une bonne compréhension du tutoriel, le lecteur doit être familiarisé avec des notions telles que les windows forms, les DLL et l'orienté objet.

Je remercie les différentes personnes de l'équipe dotnet qui se sont chargées de la relecture de ce tutoriel. Parmi elles, nequib, Xo et Piotrek.

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : La POO appliquée aux winforms

- Parution : 13 mars 2006

- Licence : Copyright ® 2006 Developpez. 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. Énoncé de l'exemple

- I-A. Réalisation de notre composant.

- I-B. Vue logique de notre composant

- II. Scénario d'implémentation de nos windows forms

- II-A. Extraits de code importants

- II-B. Conclusion

- II-C. Téléchargement

I - Énoncé de l'exemple

Pour démontrer la POO appliquée aux formulaires, nous allons réaliser une petite application simpliste gérant une liste de formations à la carte dispensées par une école. Imaginons qu'il y ait deux types de formations :

- les formations dont le coût de l'inscription est fixe ;

- les formations dont le coût de l'inscription est variable en fonction du nombre de participants.

L'exercice consiste juste à fournir à l'utilisateur une interface grâce à laquelle il pourra encoder par exemple ceci :

Intitulé de formation

Nombre d'heures

Identifiant professeur

Oracle

125

130

Dotnet

128

150

Une seule variante sera gérée à l'encodage, soit le coût de la formation sera fourni de manière forfaitaire par l'utilisateur, soit ce coût variera en fonction du nombre de participants. Dans ce cas, l'utilisateur fournira à la fois le coût par participant et le nombre de participants. L'application gardera en mémoire les instances de formations encodées par l'utilisateur. Nous ne stockerons rien en base de données.

I-A - Réalisation de notre composant.

Nous savons que nous devons gérer des formations. La première chose que nous faisons est de chercher les points communs de toutes les formations. Parmi ceux-ci, nous pouvons établir la liste (non exhaustive) suivante :

- le libellé (toutes les formations disposent d'un libellé) Dotnet, Oracle… ;

- l'identifiant du formateur (tous les cours sont dispensés par un formateur) ;

- le nombre d'heures.

Nous pourrions bien sûr trouver plus de points communs et étoffer notre composant pour gérer tous les cas de figure, mais ce n'est pas le but de cet article. Concentrons-nous sur les mécanismes POO impliqués.

Identifions à présent, tout en restant fidèle à notre énoncé, les points qui diffèrent parmi les formations que nous désirons gérer :

- le coût de la formation qui peut être soit forfaitaire, soit dépendant du nombre de participants.

Nous pouvons dès lors conclure que nous avons à gérer de manière spécifique la manière dont le coût de la formation sera calculé. Pour certaines formations, le nombre de participants ainsi que le coût par participant devront être connus, alors que pour d'autres, le prix forfaitaire suffira. Sachant cela, nous pouvons désormais élaborer notre composant de la manière suivante…

I-B - Vue logique de notre composant

Notre composant est composé de quatre classes.

- La classe « formation » qui est une super classe abstraite contenant les propriétés communes et obligeant les classes dérivées à implémenter la méthode « cout_formation ». Cette méthode oblige les classes dérivées à implémenter leur propre mécanisme de calcul de coût.

- La classe « formation_cout_fixe » est une classe dérivée de « formation ». Elle implémente son propre mécanisme de coût de formation consistant à simplement recevoir le coût forfaitaire en paramètre.

- La classe « formation_cout_variable » est une classe dérivée de « formation ». Elle implémente son propre mécanisme de coût de formation consistant à multiplier le nombre de participants par le coût par participant.

- La classe « gestion_formation » qui permet de stocker des instances de « formation_cout_fixe » et « formation_cout_variable » dans une ArrayList. C'est cette instance qui sera le lien entre le composant et l'interface qui va l'utiliser.

Vous pourriez bien sûr créer d'autres classes dérivées qui répondraient à d'autres besoins en matière de coût de formation. Je le répète, le but de cet article n'est pas de créer un composant gérant les formations, mais bien de comprendre la logique objet que l'on peut appliquer au niveau des formulaires windows.

Notre DLL étant créée, on peut désormais l'utiliser avec tout type d'interface (web, windows forms, console…). Nous allons à présent voir comment il est possible d'appliquer la même logique au niveau d'une interface windows forms.

II - Scénario d'implémentation de nos windows forms

Dans cette section, je vais décrire les étapes principales que j'ai suivies pour créer mon application. Vous pourrez visualiser le détail en téléchargeant l'exemple.

Les étapes

- Création d'un projet « Windows Form en C# ».

- Ajout de la référence vers notre composant.

- Le formulaire principal créé par défaut par le projet doit être transformé en formulaire MDI. Pour ce faire, il faut modifier la propriété « IsMdiContainer » et la mettre à « true ».

- Ajouter les menus « Fichier -> quitter l'application » et "Formations -> Formation forfaitaire, Formation Variable, Liste. Ces menus servent à ouvrir les fenêtres pour l'encodage des formations et pour les lister.

Maintenant que notre formulaire MDI et nos menus sont prêts, nous allons créer nos autres formulaires en suivant la logique de notre composant. Nous allons d'abord commencer par ajouter le formulaire qui correspond à la classe « formation » de notre composant. Il faut donc ajouter un formulaire à notre projet.

Lorsque ce formulaire est ajouté, nous devons créer les zones de texte communes, à savoir :

- l'intitulé de la formation ;

- l'identifiant du professeur ;

- le nombre d'heures.

Il faut donc ajouter trois contrôles de type « label » et trois contrôles de type « textbox ». Ne perdons pas de vue le fait que ces contrôles représentent les caractéristiques communes de nos formations. Dès lors, nous allons modifier leur portée et les basculer en « protected ».

Il faut modifier la propriété « Modifiers » de vos contrôles et la basculer en « Protected ». Par défaut, le « Modifier » est positionné sur « Private ». Le contrôle n'est donc disponible que pour le formulaire courant. Le fait d'avoir modifié cette propriété rend ce contrôle utilisable par une classe dérivée.

Comme vous vous en doutez sûrement, nous devons à présent rajouter deux autres formulaires :

- l'un correspondant à la classe « formation_cout_variable » de notre composant ;

- l'autre correspondant à la classe « formation_cout_fixe » de notre composant.

Pour ce faire, il est nécessaire d'ajouter tour à tour ces formulaires. Selon l'IDE que vous utilisez, il vous sera soit possible d'ajouter les formulaires en utilisant le template « Inherited windows form », soit vous devez ajouter un formulaire standard « Windows Form ». Si vous devez opter pour la deuxième solution, veillez à changer l'héritage par défaut qui est effectué lorsque vous créez un formulaire.

Vous devez en effet faire hériter vos deux nouveaux formulaires du formulaire « formation » que vous venez de créer. Par défaut, un formulaire hérite automatiquement de la classe « Form », remplacez « Form » par le nom de votre formulaire « formation ». Si tout se passe bien, lorsque vous basculerez en mode « design », vous devriez apercevoir les zones de textes crées au niveau du formulaire formation.

Voici un exemple de code mettant en exergue l'héritage du formulaire.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Formations;
namespace InterfaceFormations
{
// "InterfaceFormations.formation" a remplacé "Form". Cet héritage permet de bénéficier des zones de textes
// que l'on a basculé préalablement en mode "protected" au niveau de notre formulaire "formation"
public partial class formation_variable : InterfaceFormations.formation
{
public formation_variable()
{
InitializeComponent();
}

private void ajoutFormation_Click(object sender, EventArgs e)
{
try
{
gest.ajout_formation(intitule.Text,
int.Parse(id_prof.Text),
int.Parse(nb_heures.Text),
int.Parse(nb_part.Text),
float.Parse(cout_part.Text));
}
catch (gestion_formation_exception ajoutFrmEx)
{
MessageBox.Show(ajoutFrmEx.Message);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}

}
}
}

Note: le formulaire « formation » étant supposé jouer le rôle de la classe « formation » de notre DLL, il doit logiquement être déclaré comme « abstract ». Selon l'IDE que vous utilisez, vous pourriez rencontrer un problème lorsque vous basculerez en mode « design » l'un de vos formulaires dérivés. Ceci est clairement un « bug » de l'IDE. Il est donc vivement souhaitable de déclarer le formulaire en « abstract » à la fin du développement pour éviter ces désagréments. J'ai rencontré ce problème dans Visual C# Expression edition, mais aussi dans Visual Studio .NET 2003.

Nous avons à présent « recréé » la même logique de programmation orientée objet que celle que nous avions adoptée lors de la réalisation de notre DLL, car, si nous répertorions nos formulaires actuels et les liens qui les unissent, voici, schématiquement, ce que nous obtenons :

Les mécanismes d'héritage, de portée de membre au niveau des formulaires sont exactement les mêmes qu'au niveau d'une quelconque autre application. Ils sont très intéressants à connaître, car, comme nous l'avons vu, nous pouvons créer un « squelette » graphique de notre application contenant tous les champs communs à divers formulaires d'encodage et/ou d'affichage. Chaque formulaire d'encodage ou d'affichage pourra ensuite se spécialiser selon ses propres besoins.

Ceci nous permet d'avoir une approche graphique structurée.

II-A - Extraits de code importants

Dans cette section sont affichées certaines portions de code importantes par rapport au type d'exemple mis en œuvre.

Lorsque l'on charge un formulaire à partir du menu, dans un contexte MDI comme le nôtre, il faut charger la feuille comme suit :

formation_fixe f = new formation_fixe();
//Dire quel est le conteneur MDI. This étant le formulaire MDI depuis lequel on charge la feuille.
f.MdiParent = this;
f.Show();

Voici la classe formation de notre DLL.

using System;
using System.Collections.Generic;
using System.Text;

namespace Formations
{
public abstract class formation
{
protected string intitule;
protected int identifiant_prof;
protected int heures;
//Classe déclarée abstraite
public formation(string arg_intitule,int arg_identifiant_prof,int arg_heures)
{
intitule=arg_intitule;
identifiant_prof=arg_identifiant_prof;
heures=arg_heures;
}
public string get_fiche()
{
return intitule + " " + identifiant_prof + " " + heures;
}

//méthode que les classes dérivées doivent implémenter
public abstract float cout_formation();
}
}

Et voici la classe « formation_cout_variable », le lien entre « formation_cout_fixe » et « formation » étant le même, je ne l'inclurai pas ici.

using System;
using System.Collections.Generic;
using System.Text;

namespace Formations
{
public class formation_cout_variable : formation //On hérite de "formation"
{

private int nb_participants = 0;
private float cout_par_participant = 0.0F;

public formation_cout_variable(string arg_intitule, int arg_identifiant_prof, int arg_heures,
int arg_nb_part, float arg_cout_part)
//on appelle le constructeur de formation avec les éléments partagés.
: base(arg_intitule, arg_identifiant_prof, arg_heures)
{
nb_participants = arg_nb_part;
cout_par_participant = arg_cout_part;
}
//On implémente "cout_formation"
public override float cout_formation()
{
return nb_participants * cout_par_participant;
}

}
}

Je ne parlerai pas dans ce tutoriel de la manière dont les informations sont mémorisées. Je vous laisse découvrir cela au niveau du code.

II-B - Conclusion

Nous avons vu qu'il est possible d'appliquer les mécanismes orientés objet au niveau de nos formulaires. Ceci apporte à mon sens un élément essentiel : la possibilité de créer un squelette graphique de son application et par la même, éviter la redondance de contrôles.

II-C - Téléchargement

Exemple d'application