📚 Notes Complètes — Programmation 2 (420-2A6-VI) Hiver 2026
⚠️ Important : C# est sensible à la casse. Les règles du prof sont strictes — chaque section est à connaître.
TABLE DES MATIÈRES
- Conventions de nommage (TRÈS IMPORTANT)
- Types de base et structures de contrôle
- Passage par valeur ou référence
- Énumérations (Enum)
- Structure d'une solution multi-projets
- Debug.WriteLine
- Création de classes
- Les exceptions
- Tests unitaires MSTest
- L'héritage — Concepts de base
- L'héritage — Concepts intermédiaires
- Les interfaces : IEquatable et IComparable
- Patron de conception : Singleton
- Windows Forms — Bases et Contrôles
- Windows Forms — Événements
- Windows Forms — Menus, Dialogues et Fichiers
- Cycle de vie d'un formulaire & ComboBox
- abstract, virtual, sealed, interface (Semaine 11)
- Architecture MVC — Étude de cas
- IEquatable / IComparable — Exemple complet (exemple du prof)
1. Conventions de nommage
Le prof est très pointilleux là-dessus. Ces règles s'appliquent à TOUT le code.
Règles générales
- Approche K.I.S.S. (Keep It Simple, Stupid) — code simple et clair
- Toujours mettre les
<summary>sur chaque classe et méthode - Toujours commencer une méthode par une lettre majuscule (PascalCase)
- Toujours commencer une variable membre par
_ - Privé par défaut, public seulement si nécessaire
- Toujours en français lorsque possible
Noms des variables
| Type | Convention | Exemple |
|---|---|---|
| Variable membre | Commence par _ + casse chameau |
_nomEtudiant |
| Variable standard | Casse chameau, pas de _ |
nomEtudiant |
| Classe | PascalCase, pas de _ |
Etudiant |
| Méthode | PascalCase | CalculerMoyenne() |
Ordre dans une classe (STRICT)
- Déclaration de la classe
- Déclaration des variables membres
- Déclaration des constructeurs
- Déclaration des méthodes
- Déclaration des implémentations d'interface
- Déclaration des overrides
- Déclaration de la méthode
ToString() - Déclaration des accesseurs get/set
- Variables static avec préfixe
s_
Définition des classes
- Commence par un
<summary> - Commence par une majuscule
- Doit avoir une visibilité définie (ex:
public) - Aucun
_dans le nom
Modélisation UML
- Utiliser
«create»pour les constructeurs - Utiliser
«get»et«set»pour les accesseurs - Utiliser
+«get»-«set»pour un getter public avec setter privé
2. Types de base
Entiers
| Type | Octets | Min | Max |
|---|---|---|---|
short |
2 | -32 768 | 32 767 |
ushort |
2 | 0 | 65 535 |
int |
4 | -2 147 483 648 | 2 147 483 647 |
uint |
4 | 0 | 4 294 967 295 |
long |
8 | très grand négatif | très grand positif |
Virgules flottantes
| Type | Octets | Précision |
|---|---|---|
float |
4 | ~6-9 chiffres |
double |
8 | ~15-17 chiffres |
decimal |
16 | 28-29 chiffres |
Autres types courants
| Type | Octets | Notes |
|---|---|---|
bool |
1 | true / false |
char |
2 | 1 seul caractère |
string |
variable | jusqu'à ~2 milliards de caractères |
byte |
1 | 0 à 255 |
Structures de contrôle
// if / else if / else
if (condition)
{
// ...
}
else if (condition2)
{
// ...
}
else
{
// ...
}
// for
for (int i = 0; i < 10; i++)
{
// ...
}
// while
while (condition)
{
// ...
}
// do while — s'exécute au moins 1 fois
do
{
// ...
} while (condition);
// foreach — lecture seule, ne pas modifier la collection
foreach (LeType nomVariable in laCollection)
{
// ...
}
3. Passage par valeur ou référence
Passage par copie (défaut pour les types simples)
// a reste à 0 dans Main — M() reçoit une COPIE
public static void Main(string[] args)
{
int a = 0;
M(a);
Console.WriteLine(a); // affiche 0
}
public static void M(int a) { a = 8; }
Tableaux et objets → copie de la référence
// a[0] devient 8 dans Main car on partage la même zone mémoire
public static void M(int[] a) { a[0] = 8; }
Attention : si on fait
a = new int[10]dans M(), on crée un nouveau tableau — le tableau original dans Main n'est pas modifié.
Mot-clé ref — modifier directement une variable simple
// a sera 5 dans Main
public static void Main(string[] args)
{
int a = 3;
M(ref a); // ref à l'appel
Console.WriteLine(a); // affiche 5
}
public static void M(ref int a) // ref dans la signature
{
a = 5;
}
refexige que la variable soit déjà initialisée avant l'appel.
Mot-clé out — initialiser dans la méthode
// Exemple connu : int.TryParse
int a; // non initialisée
bool test = int.TryParse("42", out a);
Console.WriteLine(a); // affiche 42
outpermet d'initialiser la variable à l'intérieur de la méthode appelée.
Résumé
| Mécanisme | Variable initialisée avant l'appel ? | Peut modifier la variable originale ? |
|---|---|---|
| Copie (défaut) | N/A | Non (sauf tableaux/objets via référence) |
ref |
Oui | Oui |
out |
Non | Oui (obligatoire) |
4. Énumérations (Enum)
Un enum est un type qui représente un choix parmi une liste fixe de valeurs nommées. Plus lisible que d'utiliser des chiffres.
Déclaration de base (valeurs automatiques : 0, 1, 2...)
public enum JourSemaine
{
Lundi, // = 0
Mardi, // = 1
Mercredi, // = 2
// ...
}
Déclaration avec valeurs spécifiques
public enum CodeErreur
{
Aucun = 0,
Inconnu = 100,
AccesRefuse = 403,
NonTrouve = 404
}
Règle d'or : un fichier par Enum
Si l'enum est utilisé dans plusieurs classes → créer un fichier NomEnum.cs dédié.
// Fichier : Saison.cs
namespace MonApplication.Modeles
{
/// <summary>
/// Représente les quatre saisons de l'année.
/// </summary>
public enum Saison
{
Printemps,
Ete,
Automne,
Hiver
}
}
Utilisation
if (SaisonIdeale == Saison.Hiver)
{
Console.WriteLine("N'oubliez pas vos skis !");
}
Conversion (Casting)
// Enum vers int
int valeur = (int)Saison.Ete; // valeur = 1
// int vers enum
Saison maSaison = (Saison)2; // maSaison = Saison.Automne
5. Structure d'une solution multi-projets
Une solution contient généralement 3 types de projets :
- Projet(s) d'interface — Console ou WinForms (ce qui parle à l'utilisateur)
- Bibliothèque de classes — la logique métier (le "cerveau")
- Projet de tests unitaires — pour valider sans lancer l'interface
Règle : Ne pas mettre de
Console.WriteLinedans la bibliothèque de classes. Séparer la logique de l'affichage.
// Dans Program.cs du projet Console
using MaBibliotheque; // le namespace de la bibliothèque
6. Debug.WriteLine
Pour tracer sans afficher dans la console utilisateur :
- Activer la fenêtre Sortie dans Visual Studio (menu Débogage → Fenêtres → Sortie)
- Utiliser
Debug.WriteLine("ma trace")→ visible seulement pendant le débogage, invisible pour l'utilisateur - Recommandé : mettre la fenêtre Sortie avec les espions (Watches)
7. Création de classes
Étapes clés (avec Visual Studio)
- Renommer la classe avec
Ctrl+R, Ctrl+R(renommage global) - Variables membres :
private, préfixe_, casse chameau - Accesseurs : utiliser "Actions rapides" (
Ctrl+.) → "Encapsuler le champ" - Validation dans le
set(avant l'assignation au champ) - Constructeur : cocher les Propriétés (majuscules) et non les champs
_— pour que les validations s'exécutent
Structure type d'une classe
/// <summary>
/// Description de la classe.
/// </summary>
public class Etudiant
{
// 1. Variables membres
private string _nom;
private int _numero;
// 2. Constructeur
/// <summary>
/// Initialise un étudiant avec un nom et un numéro.
/// </summary>
/// <param name="nom">Le nom de l'étudiant.</param>
/// <param name="numero">Le numéro de l'étudiant.</param>
/// <exception cref="ArgumentException">Si le nom est invalide.</exception>
public Etudiant(string nom, int numero)
{
Nom = nom; // passe par la Propriété pour valider
Numero = numero;
}
// 3. Accesseurs
/// <summary>
/// Obtient ou définit le nom de l'étudiant.
/// </summary>
public string Nom
{
get => _nom;
private set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("Le nom ne peut pas être vide.");
}
_nom = value;
}
}
/// <summary>
/// Obtient le numéro de l'étudiant.
/// </summary>
public int Numero
{
get => _numero;
private set => _numero = value;
}
}
Types nullables
Ajouter ? après le type pour indiquer qu'une valeur peut être null :
private string? _nomOptionnel; // peut être null
8. Les exceptions
Lever une exception
throw new ArgumentException("Message clair pour le développeur.");
Lever une exception signifie : arrêter l'exécution et remonter l'erreur jusqu'à un catch.
Exceptions standards à utiliser (ne pas utiliser Exception directement)
| Exception | Quand l'utiliser |
|---|---|
ArgumentException |
Argument invalide |
ArgumentNullException |
Argument null non autorisé |
ArgumentOutOfRangeException |
Valeur hors plage |
InvalidOperationException |
État de l'objet invalide |
NotSupportedException |
Fonctionnalité non supportée |
Créer ses propres exceptions (pour des erreurs métier)
public class SoldeInsuffisantException : Exception
{
public SoldeInsuffisantException(string message) : base(message)
{
}
}
Relancer une exception : throw; et NON throw ex;
try
{
TraitementCritique();
}
catch (Exception ex)
{
// faire un log, nettoyage...
throw; // ✅ conserve la pile d'appels originale
// throw ex; ← ❌ INTERDIT - efface la pile d'appels
}
Où gérer les exceptions ?
- Bas niveau (modèle) : on laisse remonter
- Frontière applicative (Vue, API) : on intercepte, on affiche, on log
// Dans la Vue (WinForms ou Console)
try
{
_controleur.AjouterItem(item);
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message);
}
catch (Exception ex)
{
MessageBox.Show("Erreur inattendue : " + ex.Message);
}
Bonnes pratiques récapitulatives
- Ne pas utiliser
Exceptiondirectement - Lever des exceptions pour des cas vraiment exceptionnels, pas pour du contrôle de flux normal
- Fournir des messages clairs
- Ne jamais masquer une exception sans raison (pas de
catchvide) - Gérer les exceptions aux frontières du système (Vue)
9. Tests unitaires MSTest
Créer le projet de test
Placer le curseur sur le nom de la classe → Ctrl+. → "Créer des tests unitaires".
Pattern AAA (obligatoire)
Chaque test suit trois sections claires et commentées :
- Arranger — préparer les données
- Agir — appeler la méthode
- Affirmer — vérifier le résultat
Les Asserts principaux
| Méthode | Utilité |
|---|---|
Assert.AreEqual(attendu, actuel) |
Vérifie l'égalité |
Assert.IsTrue(condition) |
Vérifie que c'est vrai |
Assert.IsNull(objet) |
Vérifie que c'est null |
CollectionAssert.AreEqual(col1, col2) |
Même éléments, même ordre |
CollectionAssert.AreEquivalent(col1, col2) |
Même éléments, peu importe l'ordre |
CollectionAssert.Contains(col, item) |
Élément présent dans la liste |
Assert.ThrowsException<T>(() => ...) |
Vérifie qu'une exception est lancée |
Test simple (Happy Path)
/// <summary>
/// Vérifie qu'un prix standard sans condition de rabais reste inchangé.
/// </summary>
[TestMethod]
public void CalculerPrixFinal_PrixStandard_RetourneMemePrix()
{
// Arranger
var moteur = new MoteurPromotion();
double prixInitial = 50;
// Agir
double resultat = moteur.CalculerPrixFinal(prixInitial, 1, false);
// Affirmer
Assert.AreEqual(prixInitial, resultat);
}
Tests multiples avec DataRow
Quand une méthode a plusieurs conditions, utiliser [DataRow] pour éviter de dupliquer les méthodes :
/// <summary>
/// Teste les différentes combinaisons de rabais.
/// </summary>
[TestMethod]
[DataRow(1200, 1, false, 1080.0, "Rabais : Prix > 1000")]
[DataRow(100, 15, false, 90.0, "Rabais : Quantité >= 10")]
[DataRow(100, 1, true, 90.0, "Rabais : Membre VIP")]
[DataRow(500, 2, false, 500.0, "Aucun rabais")]
public void CalculerPrixFinal_Combinaisons_RetournePrixAttendu(
double prixInitial, int quantite, bool estMembre,
double prixAttendu, string nomScenario)
{
// Arranger
var moteur = new MoteurPromotion();
// Agir
double prixActuel = moteur.CalculerPrixFinal(prixInitial, quantite, estMembre);
// Affirmer
Assert.AreEqual(prixAttendu, prixActuel,
$"\nÉCHEC SCÉNARIO : {nomScenario}");
}
Tester les exceptions (avec vérification du message)
Préférer
Assert.ThrowsExceptionà l'ancien[ExpectedException]— on peut vérifier le message.
/// <summary>
/// Valide qu'un prix négatif provoque une ArgumentException avec message.
/// </summary>
[TestMethod]
public void CalculerPrixFinal_PrixNegatif_LanceArgumentExceptionAvecMessage()
{
// Arranger
var moteur = new MoteurPromotion();
// Agir & Affirmer
var ex = Assert.ThrowsException<ArgumentException>(() =>
moteur.CalculerPrixFinal(-10, 1, false)
);
StringAssert.Contains(ex.Message, "ne peut être négatif",
$"Message reçu : {ex.Message}");
}
Tester les collections
/// <summary>
/// Vérifie que la liste des catégories est correctement initialisée.
/// </summary>
[TestMethod]
public void Categories_AuDemarrage_ContientLesCategoriesParDefaut()
{
// Arranger
var moteur = new MoteurPromotion();
var attendu = new List<string> { "Électronique", "Mode" };
// Agir
var actuel = moteur.Categories;
// Affirmer
CollectionAssert.AreEqual(attendu, actuel, "Ordre attendu non respecté.");
CollectionAssert.Contains(actuel, "Mode", "La catégorie 'Mode' devrait être présente.");
}
Astuce : Si l'ordre n'importe pas → utiliser
CollectionAssert.AreEquivalent.
10. L'héritage — Concepts de base
L'héritage permet de créer une classe dérivée à partir d'une classe de base. La dérivée reçoit automatiquement tout ce que la base a.
Atome 1 : Le lien "est un" avec :
public class Vehicule
{
private string _marque;
public string Marque { get => _marque; set => _marque = value; }
}
// Voiture "est un" Vehicule
public class Voiture : Vehicule
{
private int _nbPortes;
public int NbPortes { get => _nbPortes; set => _nbPortes = value; }
}
Une Voiture possède automatiquement la propriété Marque sans la réécrire.
Atome 2 : Redéfinir un comportement avec virtual / override
public class Vehicule
{
// "virtual" = j'autorise les classes dérivées à changer ce comportement
public virtual void Klaxonner()
{
Console.WriteLine("Bip bip !");
}
}
public class Camion : Vehicule
{
// "override" = je prends la parole et change le comportement
public override void Klaxonner()
{
Console.WriteLine("POUÊÊÊÊÊT !");
}
}
Atome 3 : base — appeler le constructeur parent
Quand la classe de base a un constructeur avec paramètres, la classe dérivée doit l'appeler avec : base(...).
public class Vehicule
{
private string _marque;
public Vehicule(string marque)
{
Marque = marque; // passe par la propriété pour valider
}
public string Marque
{
get => _marque;
private set
{
ArgumentNullException.ThrowIfNullOrEmpty(value);
_marque = value;
}
}
}
public class Voiture : Vehicule
{
private int _nbPortes;
// On appelle le constructeur de Vehicule avec ": base(marque)"
public Voiture(string marque, int nbPortes) : base(marque)
{
NbPortes = nbPortes;
}
public int NbPortes
{
get => _nbPortes;
private set
{
if (value < 2 || value > 5)
throw new ArgumentOutOfRangeException(nameof(value), "Entre 2 et 5 portes.");
_nbPortes = value;
}
}
}
Atome 4 : Polymorphisme — "l'étiquette vs la boîte"
// L'étiquette dit "Vehicule", mais la boîte contient une "Voiture"
Vehicule monAuto = new Voiture("Toyota", 4);
monAuto.Klaxonner(); // C# regarde DANS la boîte → exécute le Klaxonner() de Voiture
// monAuto.NbPortes; ← ERREUR : l'étiquette "Vehicule" ne connaît pas NbPortes
Atome 5 : Collection polymorphique
List<Vehicule> garage = new List<Vehicule>();
garage.Add(new Voiture("Toyota", 4));
garage.Add(new Camion("Volvo", 18));
foreach (Vehicule v in garage)
{
v.Klaxonner(); // chaque objet klaxonne selon sa vraie nature
}
Résumé des mots-clés de base
| Mot-clé | Rôle |
|---|---|
: |
Établir le lien "est un" |
virtual |
Donner la permission de changer un comportement |
override |
Changer le comportement |
base |
Envoyer des données au constructeur de la classe parent |
11. L'héritage — Concepts intermédiaires
protected — accès hiérarchique
protected est entre public et private : visible par la classe et ses dérivées, invisible du reste.
public class Vehicule
{
private bool _estEnEtatDeMarche;
// Tout le monde peut LIRE (public get)
// Seules les classes dérivées peuvent MODIFIER (protected set)
public bool EstEnEtatDeMarche
{
get => _estEnEtatDeMarche;
protected set => _estEnEtatDeMarche = value;
}
}
public class Voiture : Vehicule
{
public void SubirAccident()
{
EstEnEtatDeMarche = false; // ✅ AUTORISÉ (on est dans une classe dérivée)
}
}
// Dans Program.cs :
// maVoiture.EstEnEtatDeMarche = false; ← ❌ ERREUR (protected set)
sealed — verrouiller
- Sur une classe : personne ne peut en hériter
- Sur une méthode : les classes plus basses dans la hiérarchie ne peuvent plus la redéfinir
// Classe scellée : impossible d'en hériter
public sealed class SystemeSecurite { }
// Méthode scellée dans une dérivée
public class Camion : Vehicule
{
public sealed override void Klaxonner()
{
Console.WriteLine("POUÊÊÊÊÊT !");
}
// Les classes qui héritent de Camion ne pourront plus redéfinir Klaxonner
}
Résumé intermédiaire
| Mot-clé | Rôle |
|---|---|
protected |
Visible uniquement dans la hiérarchie (classe + dérivées) |
sealed |
Verrouille une classe ou une méthode (plus de redéfinition possible) |
12. Les interfaces : IEquatable et IComparable
Une interface est un contrat : elle force la classe qui l'implémente à avoir certaines méthodes.
- Le nom d'une interface commence toujours par
Imajuscule - On déclare uniquement des signatures (pas de code) dans l'interface
public interface IUneInterface
{
void Methode();
int Calculer(int variable);
}
Les 4 cas à implémenter (pour compatibilité complète .NET)
Pour que vos objets fonctionnent correctement dans des listes, tris, recherches, il faut implémenter ces 4 cas :
Cas 1 : IEquatable<T> — égalité avec un type précis
public bool Equals(Etudiant other)
{
if (other is null) return false;
return _nom == other._nom && _moyenne == other._moyenne;
}
Cas 2 : override bool Equals(object obj) — égalité avec n'importe quel objet
public override bool Equals(object obj)
{
// "as" = 3 effets en 1 :
// 1. Transtypage
// 2. Retourne null si ce n'est pas le bon type
// 3. Retourne null si obj est déjà null
Etudiant temp = obj as Etudiant;
if (temp is null) return false;
return Equals(temp); // relance le Cas 1
}
Cas 3 : IComparable<T> — comparaison avec un type précis (pour les tris)
public int CompareTo(Etudiant other)
{
if (other is null) return 1; // on est "plus grand" que null
return _moyenne.CompareTo(other._moyenne); // négatif, 0, ou positif
}
Cas 4 : IComparable — comparaison avec n'importe quel objet (compatibilité)
public int CompareTo(object obj)
{
Etudiant temp = obj as Etudiant;
if (temp is null) return 1;
return CompareTo(temp); // relance le Cas 3
}
Déclaration complète de la classe
public class Etudiant : IEquatable<Etudiant>, IComparable<Etudiant>, IComparable
{
// ...
public override int GetHashCode() => HashCode.Combine(_nom, _moyenne);
}
GetHashCode()est obligatoire quand on redéfinitEquals.
StringComparison.Ordinal pour les comparaisons de chaînes
Utiliser pour des tris techniques (plus rapide, cohérent partout) :
return string.Compare(_nom, other._nom, StringComparison.Ordinal);
20. Exemple complet du prof : IEquatable + IComparable
Voici l'exemple envoyé par le prof, avec la mécanique de transtypage (as vs cast direct).
Mécanique de transtypage — as vs cast direct
// ✅ SÉCURITAIRE — avec "as"
// Si obj n'est pas un Etudiant → retourne null (pas d'exception)
// Si obj est null → retourne null
public int MethodeQuelconque(object obj)
{
return MethodeQuelconque(obj as Etudiant);
}
// ❌ DANGEREUX — avec cast direct
// Si obj n'est pas un Etudiant → lance InvalidCastException
public int AutreMethodeQuelconque(object obj)
{
return MethodeQuelconque((Etudiant)obj); // peut exploser!
}
Classe EtudiantStupefait — implémentation complète
using System;
using System.Collections.Generic;
using System.Text;
namespace exampleCode
{
/// <summary>
/// Représente un étudiant avec numéro et nom, comparable et égalable.
/// </summary>
public class EtudiantStupefait : IComparable, IComparable<EtudiantStupefait>, IEquatable<EtudiantStupefait>
{
private int _numero;
private string nom;
public EtudiantStupefait(int numero)
{
Nom = "Etudiant " + numero;
Numero = numero;
}
public int Numero { get => _numero; private set => _numero = value; }
public string Nom { get => nom; private set => nom = value; }
/// <summary>
/// Réimplémentation de Equals de la classe object.
/// </summary>
public override bool Equals(object? obj)
{
if (obj is null) return false;
if (obj is EtudiantStupefait)
{
// Cast standard (syntaxe classique pour la majorité des langages)
// ⚠️ Sensible à InvalidCastException si obj n'est pas EtudiantStupefait
EtudiantStupefait autreEtudiant = (EtudiantStupefait)obj;
return Equals(autreEtudiant); // relance le Cas 1
}
return false;
}
/// <summary>
/// Implémentation de IEquatable — version générique.
/// </summary>
public bool Equals(EtudiantStupefait? other)
{
if (other is null) return false;
return this.Nom == other.Nom && this.Numero == other.Numero;
}
/// <summary>
/// Implémentation de IComparable générique — compare par numéro.
/// </summary>
public int CompareTo(EtudiantStupefait? other)
{
if (other is null) return 1;
return Numero - other.Numero;
}
/// <summary>
/// Implémentation de IComparable (object) — version non-générique.
/// </summary>
public int CompareTo(object? obj)
{
if (obj is null) return 1;
EtudiantStupefait autreEtudiant = (EtudiantStupefait)obj;
if (autreEtudiant is null) return 1;
return CompareTo(autreEtudiant); // relance la version générique
}
}
}
13. Patron de conception : Singleton
Le Singleton garantit qu'une classe n'a qu'une seule instance dans toute l'application, accessible globalement.
Quand l'utiliser : configuration centrale, connexion à une BD, contrôleur partagé entre plusieurs formulaires.
Implémentation
/// <summary>
/// Singleton de démonstration.
/// </summary>
public class ExempleSingleton
{
private static ExempleSingleton instance;
// Constructeur PRIVÉ → interdit l'instanciation externe
private ExempleSingleton() { }
// Propriété publique pour accéder à l'unique instance
public static ExempleSingleton Instance
{
get
{
if (instance == null)
{
instance = new ExempleSingleton();
}
return instance;
}
}
/// <summary>
/// Exemple de méthode du singleton.
/// </summary>
public void FaireQuelquechose()
{
Console.WriteLine("Singleton actif !");
}
}
Version condensée (syntaxe lambda) — souvent vue dans .NET
// Version courte avec l'opérateur ?? (si null, crée l'instance)
public static ExempleSingleton Instance => instance ?? (instance = new ExempleSingleton());
Utilisation dans un formulaire WinForms
public class FormClient : Form
{
// On stocke la référence au singleton dans une variable membre
private ControlleurClient _controlleurClient = ControlleurClient.Instance;
private void btnEnregistrer_Click(object sender, EventArgs e)
{
_controlleurClient.EnregistrerClient(txtNomClient.Text);
}
}
14. Windows Forms — Bases et Contrôles
Créer un projet WinForms
Choisir Windows Forms App → .NET (version la plus récente).
Renommer le formulaire
- Clic droit sur
Form1.cs→ Renommer →FormPrincipal.cs - Répondre Oui pour renommer toutes les références
Partial Class — deux fichiers pour un formulaire
FormPrincipal.cs→ votre code (événements, logique)FormPrincipal.Designer.cs→ code généré automatiquement (ne jamais modifier manuellement)
public partial class FormPrincipal : Form
{
public FormPrincipal()
{
InitializeComponent(); // appelle le Designer.cs
}
}
Propriétés d'un bouton (important)
- Name : le nom de la variable dans le code — ne jamais laisser
button1→ renommer enbuttonValider(nom complet du composant + action) - Text : le texte affiché sur le bouton pour l'utilisateur
- Location : position (X, Y) en
Point - Size : largeur et hauteur en
Size - TabIndex : ordre de navigation avec la touche Tab (commence à 0)
15. Windows Forms — Événements
Les événements permettent de réagir aux actions de l'utilisateur. On les configure dans la fenêtre Propriétés (icône éclair ⚡).
Événements courants
- Click : clic sur un contrôle
- MouseEnter / MouseMove : souris qui survole
Afficher la position de la souris
private void FormPrincipal_MouseMove(object sender, MouseEventArgs e)
{
this.Text = $"X: {e.X}, Y: {e.Y}";
}
Bouton qui se déplace aléatoirement
private void buttonDeplacer_Click(object sender, EventArgs e)
{
Random rand = new Random();
int maxX = this.ClientSize.Width - buttonDeplacer.Width;
int maxY = this.ClientSize.Height - buttonDeplacer.Height;
buttonDeplacer.Location = new Point(rand.Next(0, maxX), rand.Next(0, maxY));
}
16. Windows Forms — Menus, Dialogues et Fichiers
TextBox multi-lignes
Multiline:trueScrollBars:BothDock:Fill
MenuStrip
- Toujours nommer :
menuStripPrincipal &Fichier→ la lettre F sera soulignée et accessible viaAlt+FShortcutKeys→ raccourcis clavier (ex:Ctrl+O)
Lire un fichier (StreamReader)
private void menuOuvrir_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialogCharger = new OpenFileDialog();
openFileDialogCharger.Filter = "Fichiers texte (*.txt)|*.txt";
if (openFileDialogCharger.ShowDialog() == DialogResult.OK)
{
StreamReader reader = new StreamReader(openFileDialogCharger.FileName);
textBoxContenu.Text = reader.ReadToEnd();
reader.Close(); // TOUJOURS fermer le flux
}
}
Écrire dans un fichier (StreamWriter)
private void menuEnregistrer_Click(object sender, EventArgs e)
{
SaveFileDialog saveFileDialogEnregistrer = new SaveFileDialog();
saveFileDialogEnregistrer.Filter = "Fichiers texte (*.txt)|*.txt";
if (saveFileDialogEnregistrer.ShowDialog() == DialogResult.OK)
{
StreamWriter writer = new StreamWriter(saveFileDialogEnregistrer.FileName);
writer.AutoFlush = true; // force l'écriture immédiate
writer.Write(textBoxContenu.Text);
writer.Flush(); // vide le buffer vers le disque
writer.Close(); // TOUJOURS fermer — sinon le fichier est verrouillé
}
}
ColorDialog
private void menuCouleur_Click(object sender, EventArgs e)
{
ColorDialog colorDialogSelection = new ColorDialog();
if (colorDialogSelection.ShowDialog() == DialogResult.OK)
{
this.BackColor = colorDialogSelection.Color;
}
}
17. Cycle de vie d'un formulaire & ComboBox
Ordre des événements d'un Form
- Constructeur →
InitializeComponent() - HandleCreated (rarement utilisé)
- Load → charger les données, initialiser l'interface (avant affichage)
- Shown → une seule fois, quand le formulaire devient visible
- [Interaction utilisateur] → clics, saisies...
- Closing → avant fermeture (
e.Cancel = truepour annuler) - FormClosed → après fermeture, nettoyages finaux
private void FormClient_Load(object sender, EventArgs e)
{
ChargerClientsDansComboBox(); // remplir les listes au chargement
}
private void FormClient_Closing(object sender, CancelEventArgs e)
{
// e.Cancel = true; // pour annuler la fermeture
}
Lier une List\<T> à un ComboBox
La classe doit avoir un ToString() ou on définit DisplayMember :
public class Client
{
public string Nom { get; set; }
public override string ToString()
{
return Nom;
}
}
private void ChargerClientsDansComboBox()
{
List<Client> listeClients = _controlleurClient.RecupererListeClient();
comboBoxClient.DataSource = listeClients;
comboBoxClient.DisplayMember = "Nom"; // propriété à afficher
}
18. abstract, virtual, sealed, interface
abstract — modèle obligatoire
- Une classe
abstractne peut pas être instanciée directement - Une méthode
abstractdoit être redéfinie dans chaque classe dérivée
public abstract class Animal
{
public abstract void FaireDuBruit(); // pas de corps ici
}
public class Chien : Animal
{
public override void FaireDuBruit()
{
Console.WriteLine("Wouf!");
}
}
virtual — comportement personnalisable
- Fournit un comportement par défaut qu'on peut choisir de redéfinir ou non
public class Animal
{
public virtual void FaireDuBruit()
{
Console.WriteLine("Un bruit animal");
}
}
public class Chat : Animal
{
public override void FaireDuBruit()
{
Console.WriteLine("Miaou");
}
}
sealed — verrouillage (rappel)
public sealed class Utilitaire { } // personne ne peut hériter de cette classe
Interface vs Classe abstraite
| Critère | Classe abstraite | Interface |
|---|---|---|
| Héritage multiple | ❌ Une seule | ✅ Plusieurs interfaces |
| Code dans la classe | ✅ Oui | ❌ Non (sauf défaut C#8+) |
| Constructeurs | ✅ Oui | ❌ Non |
| Champs membres | ✅ Oui | ❌ Non |
Quand utiliser quoi ?
| Besoin | Solution |
|---|---|
| Classes sans lien hiérarchique qui partagent un comportement | Interface |
| Classes liées par un concept commun avec du code partagé | Classe abstraite |
| Comportement obligatoire à redéfinir | Méthode abstraite |
| Comportement par défaut modifiable | Méthode virtual |
| Empêcher toute extension | sealed |
Exemple interface
public interface IImprimable
{
void Imprimer();
}
public class Document : IImprimable
{
public void Imprimer() => Console.WriteLine("Impression du document...");
}
public class Photo : IImprimable
{
public void Imprimer() => Console.WriteLine("Impression de la photo...");
}
Exemple classe abstraite
public abstract class Employe
{
public string Nom { get; set; }
public abstract decimal CalculerSalaire(); // chaque type d'employé calcule différemment
}
public class Salarie : Employe
{
public decimal SalaireMensuel { get; set; }
public override decimal CalculerSalaire() => SalaireMensuel;
}
public class Contractuel : Employe
{
public int HeuresTravaillees { get; set; }
public decimal TauxHoraire { get; set; }
public override decimal CalculerSalaire() => HeuresTravaillees * TauxHoraire;
}
19. Architecture MVC — Étude de cas
L'analogie du Restaurant
| Rôle MVC | Dans le code | Ce qu'il fait |
|---|---|---|
| Vue (le serveur) | Console, WinForms | Parle à l'utilisateur. Montre les boutons, capture les erreurs avec try-catch. Ne sait pas cuisiner. |
| Contrôleur (le chef) | FactureController |
Reçoit les ordres de la Vue. Vérifie si l'action est logique. Donne les ordres au Modèle. |
| Modèle (la recette) | Client, Facture |
Données brutes. Valide qu'il est bien formé. Ne sait pas qui l'utilise. |
Structure physique (3 projets dans la solution)
Solution
├── MonProjet.Lib ← Bibliothèque : Modèles + Contrôleurs
├── MonProjet.Console ← Vue Console (utilise Lib)
├── MonProjet.WinForms ← Vue WinForms (utilise Lib)
└── MonProjet.Tests ← Tests unitaires (utilise Lib)
Avantage : Si on change l'interface (Console → WinForms), le cerveau (Lib) ne change pas.
Protection des données : IReadOnlyList
Le contrôleur ne donne jamais sa List interne directement — il donne une vue en lecture seule :
// Dans le contrôleur
private List<Facture> _historique; // le coffre-fort (privé)
public IReadOnlyList<Facture> HistoriqueReadOnly
{
get { return _historique.AsReadOnly(); } // photo des données (public)
}
La Vue peut regarder, mais ne peut pas modifier la liste directement.
Workflow — Voyage d'une donnée
Initialisation :
// Vue (WinForms) — au chargement
cmbClients.DataSource = _clientCtrl.ListeClientsReadOnly;
cmbClients.DisplayMember = "Nom";
Créer une facture :
// Vue
Client? client = cmbClients.SelectedItem as Client;
if (client != null)
{
_factureCtrl.CreerNouvelleFacture(client);
}
// Contrôleur
public void CreerNouvelleFacture(Client client)
{
_factureEnCours = new Facture(client);
}
Ajouter un item :
// Vue
_factureCtrl.AjouterItem(itemSelectionne);
// Contrôleur — vérifie que l'action est logique avant de parler au modèle
public void AjouterItem(Item item)
{
if (_factureEnCours == null)
throw new InvalidOperationException("Pas de facture ouverte !");
_factureEnCours.AjouterItem(item);
}
// Modèle — crée un snapshot (figer le prix au moment de la vente)
public void AjouterItem(Item item)
{
_lignes.Add(new LigneFacture(item));
}
public LigneFacture(Item item)
{
this.Description = item.Description;
this.PrixUnitaire = item.Prix; // copie du prix ACTUEL
}
Pourquoi un snapshot ? Si le prix d'un produit change demain, la facture d'aujourd'hui ne doit pas changer.
Gestion des erreurs dans la Vue
try
{
_factureCtrl.AjouterItem(item);
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message);
}
catch (Exception ex)
{
MessageBox.Show("Erreur inattendue : " + ex.Message);
}
Tests unitaires dans MVC
Le cerveau (Lib) est séparé → on peut tester sans ouvrir une interface :
[TestMethod]
public void CalculerTotal_DoitAdditionnerLesPrix()
{
// Arranger
Facture f = new Facture(new Client(1, "Test"));
f.AjouterItem(new Item("A", 10.50m));
f.AjouterItem(new Item("B", 5.25m));
// Agir
decimal total = f.CalculerTotal();
// Affirmer
Assert.AreEqual(15.75m, total);
}
Méthodologie pour ajouter une fonctionnalité
- Modèle — mes données sont-elles prêtes ?
- Contrôleur — est-ce que j'ai une méthode pour ça ?
- Test — est-ce que je peux vérifier sans ouvrir une fenêtre ?
- Vue — ajouter le bouton +
try-catch
Récapitulatif des bonnes pratiques (ce que le prof surveille)
| Pratique | Détail |
|---|---|
<summary> partout |
Classes, méthodes, constructeurs |
Variables membres avec _ |
_monChamp, casse chameau |
| Passer par les Propriétés dans le constructeur | Pour que les validations s'exécutent à l'instanciation |
throw; et non throw ex; |
Pour conserver la pile d'appels |
Assert.ThrowsException |
Préféré à [ExpectedException] |
| Pattern AAA dans les tests | Sections Arranger / Agir / Affirmer commentées |
obj as MonType et non (MonType)obj |
Plus sécuritaire (pas d'exception si mauvais type) |
IReadOnlyList dans le contrôleur |
La Vue ne doit pas modifier les listes internes |
is null et non == null |
Recommandé en C# moderne |
| Un fichier par classe/enum | Clarté et maintenabilité |
private par défaut |
public seulement si nécessaire |
| Noms de composants WinForms | Jamais button1 → toujours buttonValider |
reader.Close() / writer.Close() |
Toujours fermer les flux de fichiers |
Document généré à partir du site du cours : https://tousignantsimon-p2.github.io/