Notes Complètes — Programmation 2 (420-2A6-VI) Hiver 2026

📚 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

  1. Conventions de nommage (TRÈS IMPORTANT)
  2. Types de base et structures de contrôle
  3. Passage par valeur ou référence
  4. Énumérations (Enum)
  5. Structure d'une solution multi-projets
  6. Debug.WriteLine
  7. Création de classes
  8. Les exceptions
  9. Tests unitaires MSTest
  10. L'héritage — Concepts de base
  11. L'héritage — Concepts intermédiaires
  12. Les interfaces : IEquatable et IComparable
  13. Patron de conception : Singleton
  14. Windows Forms — Bases et Contrôles
  15. Windows Forms — Événements
  16. Windows Forms — Menus, Dialogues et Fichiers
  17. Cycle de vie d'un formulaire & ComboBox
  18. abstract, virtual, sealed, interface (Semaine 11)
  19. Architecture MVC — Étude de cas
  20. 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)

  1. Déclaration de la classe
  2. Déclaration des variables membres
  3. Déclaration des constructeurs
  4. Déclaration des méthodes
  5. Déclaration des implémentations d'interface
  6. Déclaration des overrides
  7. Déclaration de la méthode ToString()
  8. Déclaration des accesseurs get/set
  9. 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;
}

ref exige 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

out permet 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 :

  1. Projet(s) d'interface — Console ou WinForms (ce qui parle à l'utilisateur)
  2. Bibliothèque de classes — la logique métier (le "cerveau")
  3. Projet de tests unitaires — pour valider sans lancer l'interface

Règle : Ne pas mettre de Console.WriteLine dans 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)

  1. Renommer la classe avec Ctrl+R, Ctrl+R (renommage global)
  2. Variables membres : private, préfixe _, casse chameau
  3. Accesseurs : utiliser "Actions rapides" (Ctrl+.) → "Encapsuler le champ"
  4. Validation dans le set (avant l'assignation au champ)
  5. 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 Exception directement
  • 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 catch vide)
  • 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 :

  1. Arranger — préparer les données
  2. Agir — appeler la méthode
  3. 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 I majuscule
  • 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éfinit Equals.

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

  1. Clic droit sur Form1.cs → Renommer → FormPrincipal.cs
  2. 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 en buttonValider (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 : true
  • ScrollBars : Both
  • Dock : Fill

MenuStrip

  • Toujours nommer : menuStripPrincipal
  • &Fichier → la lettre F sera soulignée et accessible via Alt+F
  • ShortcutKeys → 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

  1. ConstructeurInitializeComponent()
  2. HandleCreated (rarement utilisé)
  3. Load → charger les données, initialiser l'interface (avant affichage)
  4. Shown → une seule fois, quand le formulaire devient visible
  5. [Interaction utilisateur] → clics, saisies...
  6. Closing → avant fermeture (e.Cancel = true pour annuler)
  7. 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 abstract ne peut pas être instanciée directement
  • Une méthode abstract doit ê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é

  1. Modèle — mes données sont-elles prêtes ?
  2. Contrôleur — est-ce que j'ai une méthode pour ça ?
  3. Test — est-ce que je peux vérifier sans ouvrir une fenêtre ?
  4. 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/

Partager :
Retourner vers page d'accueil

Prêt à multiplier vos clics par 3 à 5 ?

Rejoignez 50 000+ créateurs qui utilisent Bio.ax pour booster leur présence digitale.

Créer mon Bio.ax gratuit