Chapitre 417 minIntermédiaire
Formulaires et boîtes de dialogue : MessageBox, ShowDialog
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- afficher un message et lire la réponse de l'utilisateur avec MessageBox ;
- ouvrir un formulaire modal ou non modal et récupérer son résultat ;
- passer des données d'un formulaire à un autre sans variable globale ;
- enchaîner une fenêtre de connexion et la fenêtre principale ;
- exporter une liste en CSV avec une boîte de dialogue d'enregistrement.
4 exercices corrigés
Une application de gestion enchaîne plusieurs fenêtres : une liste, une fiche de saisie, une confirmation, une boîte d'enregistrement de fichier. Ce chapitre montre comment afficher un message avec MessageBox, ouvrir un formulaire en mode modal (ShowDialog) ou non modal (Show), et faire circuler les données d'une fenêtre à l'autre sans variable globale.
Afficher un message avec MessageBox
MessageBox.Show affiche une petite fenêtre modale et renvoie le bouton cliqué, de type DialogResult.
MessageBox.Show("Patient enregistré.");
DialogResult reponse = MessageBox.Show(
$"Supprimer le patient {patient} ?", // le message
"Confirmation", // le titre de la fenêtre
MessageBoxButtons.YesNo, // les boutons
MessageBoxIcon.Question, // l'icône
MessageBoxDefaultButton.Button2); // le bouton par défaut : Non
if (reponse == DialogResult.Yes)
{
await _service.SupprimerAsync(patient.NumPatient);
}
| Paramètre | Valeurs courantes |
|---|---|
MessageBoxButtons | OK, OKCancel, YesNo, YesNoCancel, RetryCancel |
MessageBoxIcon | Information, Question, Warning, Error |
MessageBoxDefaultButton | Button1, Button2, Button3 |
Résultat DialogResult | OK, Cancel, Yes, No, Retry, Abort, Ignore |
Choisis l'icône selon la gravité : Information pour un succès, Warning pour une règle non respectée (« ce numéro de sécurité sociale existe déjà »), Error pour une panne (« serveur injoignable »). Un message d'erreur utile dit ce qui s'est passé et quoi faire ; il ne montre jamais une pile d'appels à l'utilisateur.
Pour des boîtes plus riches (titre en gras, texte détaillé, case « Ne plus afficher »), .NET propose aussi TaskDialog.ShowDialog, qui affiche la boîte de dialogue moderne de Windows.
Modal ou non modal : ShowDialog et Show
Un formulaire s'ouvre de deux façons.
ShowDialog() | Show() | |
|---|---|---|
| Nom | Modal | Non modal |
| La fenêtre appelante | est bloquée tant que la nouvelle est ouverte | reste utilisable |
| Le code après l'appel | attend la fermeture | continue tout de suite |
| Valeur renvoyée | un DialogResult | rien |
| Usage typique | fiche de saisie, connexion, confirmation | fenêtre d'outil, consultation à côté du travail |
Pour une fiche de saisie, c'est presque toujours ShowDialog : l'utilisateur termine la saisie (ou l'annule) avant de revenir à la liste, et ton code lit le résultat juste après.
private async void btnNouveau_Click(object? sender, EventArgs e)
{
using var fiche = new FrmPatientEdition();
if (fiche.ShowDialog(this) != DialogResult.OK) return;
await EnregistrerAsync(fiche.Resultat!);
}
using varlibère les ressources de la fenêtre quand la méthode se termine. Un formulaire affiché parShowDialogn'est pas libéré automatiquement à sa fermeture : sansusing, il reste en mémoire.ShowDialog(this)désigne la fenêtre courante comme propriétaire : la fiche s'affiche au-dessus d'elle et au centre siStartPosition = CenterParent.
Une fenêtre ouverte par Show() est libérée à sa fermeture ; ne l'entoure pas d'un using, sinon elle disparaîtrait aussitôt affichée.
DialogResult : faire répondre un formulaire
Un formulaire modal se ferme dès que sa propriété DialogResult reçoit une valeur autre que None, et cette valeur devient le résultat de ShowDialog().
- Le bouton Annuler a sa propriété
DialogResultréglée surCanceldans le concepteur : un clic ferme la fenêtre, sans une ligne de code. - Le bouton Enregistrer, lui, doit d'abord valider la saisie. On laisse sa propriété
DialogResultàNoneet on la règle dans le code, seulement si tout est correct.
private void btnEnregistrer_Click(object? sender, EventArgs e)
{
if (!ValidateChildren()) return; // une erreur : la fenêtre reste ouverte
Resultat = new Patient { /* construit à partir des champs */ };
DialogResult = DialogResult.OK; // ferme la fenêtre, ShowDialog renvoie OK
}
Passer des données d'un formulaire à l'autre
C'est la question la plus posée sur les forums : « comment récupérer dans Form1 une valeur saisie dans Form2 ? ». La réponse tient en deux règles, qui respectent l'encapsulation vue en programmation objet : on donne les données par le constructeur, on les récupère par une propriété publique.
public partial class FrmPatientEdition : Form
{
private readonly int _numPatient;
// ce que la fiche renvoie, lisible après ShowDialog
public Patient? Resultat { get; private set; }
// ce que la fiche reçoit : rien pour un nouveau patient, le patient à modifier sinon
public FrmPatientEdition(Patient? patient = null)
{
InitializeComponent();
if (patient is null)
{
Text = "Nouveau patient";
return;
}
Text = $"Modifier {patient}";
_numPatient = patient.NumPatient;
txtNom.Text = patient.Nom;
txtPrenom.Text = patient.Prenom;
txtSecu.Text = patient.NumeroSecu;
dtpNaissance.Checked = patient.DateNaissance is not null;
if (patient.DateNaissance is DateTime naissance) dtpNaissance.Value = naissance;
}
}
Côté liste, la modification utilise le même formulaire :
private async void btnModifier_Click(object? sender, EventArgs e)
{
if (bsPatients.Current is not Patient patient) return; // aucune ligne sélectionnée
using var fiche = new FrmPatientEdition(patient);
if (fiche.ShowDialog(this) != DialogResult.OK) return;
await EnregistrerAsync(fiche.Resultat!);
}
La fiche ne modifie pas directement l'objet patient de la liste : elle construit un nouvel objet Resultat. Si l'utilisateur annule, ou si une règle refuse l'enregistrement, la liste n'a pas bougé.
Pour une fenêtre non modale qui doit prévenir la fenêtre principale, on utilise un événement personnalisé, comme vu au chapitre 3.
Une fenêtre de connexion avant la fenêtre principale
GSB Ordonnances demande un identifiant avant d'afficher quoi que ce soit. La fenêtre de connexion est modale et s'ouvre dans Program.cs, avant Application.Run :
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
// … lecture de la configuration et création des services (chapitres 8 et 12)
using var connexion = new FrmConnexion(authService);
if (connexion.ShowDialog() != DialogResult.OK) return; // fermée ou annulée : on quitte
Application.Run(new FrmPatients(patientService, connexion.Utilisateur!));
}
Si l'utilisateur ferme la fenêtre de connexion, Main se termine et l'application aussi. Sinon, la fenêtre principale devient le formulaire de Application.Run : la fermer quitte l'application. N'essaie pas l'inverse (lancer la fenêtre principale puis cacher la fenêtre de connexion avec Hide()) : l'application ne se fermerait plus quand on ferme la fenêtre principale, puisque la fenêtre de connexion, cachée, serait toujours celle de Application.Run.
Les boîtes de dialogue de fichiers : exporter en CSV
Windows fournit des boîtes standard : OpenFileDialog (choisir un fichier à ouvrir), SaveFileDialog (choisir où enregistrer), FolderBrowserDialog (choisir un dossier), ColorDialog, FontDialog, PrintDialog. Elles s'utilisent toutes de la même façon : on règle quelques propriétés, on appelle ShowDialog(), on lit le résultat.
L'export de la liste des patients au format CSV (pour un publipostage) :
using System.Text;
private void btnExporter_Click(object? sender, EventArgs e)
{
using var dialogue = new SaveFileDialog
{
Filter = "Fichier CSV (*.csv)|*.csv",
FileName = $"patients-{DateTime.Today:yyyy-MM-dd}.csv",
};
if (dialogue.ShowDialog(this) != DialogResult.OK) return;
var lignes = new List<string> { "Nom;Prénom;Naissance" };
foreach (Patient p in _affiches)
{
lignes.Add($"{Csv(p.Nom)};{Csv(p.Prenom)};{p.DateNaissance:dd/MM/yyyy}");
}
// UTF-8 avec BOM : Excel affiche correctement les accents
File.WriteAllLines(dialogue.FileName, lignes, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
MessageBox.Show($"{_affiches.Count} patients exportés.", "Export",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
// Une valeur qui contient ; ou " est entourée de guillemets, et ses guillemets sont doublés
private static string Csv(string? valeur) =>
valeur is null ? ""
: valeur.Contains(';') || valeur.Contains('"') || valeur.Contains('\n')
? $"\"{valeur.Replace("\"", "\"\"")}\""
: valeur;
Csv("Dupont") renvoie Dupont, Csv("Le \"Grand\"; Paul") renvoie "Le ""Grand""; Paul". Le point-virgule est le séparateur qu'Excel attend en français ; le format CSV est détaillé dans le cours API REST.
Imprimer
L'impression passe par un objet PrintDocument dont l'événement PrintPage dessine chaque page avec un objet Graphics. Le composant PrintPreviewDialog affiche un aperçu avant impression.
using System.Drawing.Printing;
private void btnImprimer_Click(object? sender, EventArgs e)
{
using var document = new PrintDocument();
document.PrintPage += (s, pe) =>
{
using var police = new Font("Segoe UI", 12);
pe.Graphics!.DrawString($"Ordonnance n° {_ordonnance.NumOrdonnance}", police, Brushes.Black, 50, 50);
pe.HasMorePages = false; // une seule page
};
using var apercu = new PrintPreviewDialog { Document = document };
apercu.ShowDialog(this);
}
Dessiner une ordonnance complète ligne à ligne est fastidieux. Dans un vrai projet, on génère plutôt un PDF avec une bibliothèque dédiée et on laisse l'utilisateur l'imprimer.
Plusieurs fenêtres ouvertes : MDI et onglets
Une application MDI (Multiple Document Interface) affiche ses fenêtres à l'intérieur d'une fenêtre mère : IsMdiContainer = true sur la fenêtre mère, MdiParent = this sur chaque fenêtre fille avant Show(). Ce modèle a vieilli ; on lui préfère aujourd'hui une fenêtre principale avec un menu et un TabControl, ou des fiches modales.
Exercices
Exercice 1 sur 4 La confirmation de suppression Réussi À revoir
Écris le gestionnaire btnSupprimer_Click de FrmPatients : s'il n'y a pas de patient sélectionné (bsPatients.Current n'est pas un Patient), ne rien faire ; sinon demander confirmation en nommant le patient, avec Non par défaut, puis appeler _service.SupprimerAsync(numPatient) et recharger la liste avec ChargerAsync().
Voir la réponseMasquer la réponse de l'exercice 1
private async void btnSupprimer_Click(object? sender, EventArgs e)
{
if (bsPatients.Current is not Patient patient) return;
DialogResult reponse = MessageBox.Show(
$"Supprimer le patient {patient} ?", "Confirmation",
MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
if (reponse != DialogResult.Yes) return;
await _service.SupprimerAsync(patient.NumPatient);
await ChargerAsync();
}
Au chapitre 9, tu ajouteras un try … catch : MySQL refuse de supprimer un patient qui a des ordonnances.
Tu avais trouvé ?
Exercice 2 sur 4 Modal ou non modal ? Réussi À revoir
Pour chaque fenêtre, choisis Show ou ShowDialog et justifie.
- La fiche de saisie d'un nouveau médecin.
- Une fenêtre « Référentiel des médicaments » que la secrétaire garde ouverte à côté de la liste des patients.
- La fenêtre de connexion.
- Une fenêtre « À propos ».
Voir la réponseMasquer la réponse de l'exercice 2
ShowDialog: on attend la fin de la saisie pour lire le résultat et rafraîchir la liste.Show: elle doit rester utilisable en même temps que la fenêtre principale.ShowDialog, dansProgram.cs, avantApplication.Run: rien ne doit être accessible sans connexion.ShowDialog: elle se ferme d'un clic, rien ne justifie de la laisser ouverte à côté.
Tu avais trouvé ?
Exercice 3 sur 4 Trouve le bug Réussi À revoir
Ce code doit modifier un médecin. L'utilisateur clique sur « Enregistrer » avec un nom vide : le message d'erreur s'affiche, puis la fenêtre se ferme quand même et la saisie est perdue. Le bouton btnEnregistrer a sa propriété DialogResult réglée sur OK dans le concepteur.
private void btnEnregistrer_Click(object? sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtNom.Text))
{
MessageBox.Show("Le nom est obligatoire.");
return;
}
Resultat = new Medecin { Nom = txtNom.Text.Trim() };
}
Voir la réponseMasquer la réponse de l'exercice 3
Avec DialogResult = OK sur le bouton, le formulaire se ferme après chaque clic, que le code ait fait return ou non. Il faut remettre la propriété DialogResult du bouton sur None dans le concepteur et fermer par le code, seulement quand la saisie est valide :
private void btnEnregistrer_Click(object? sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtNom.Text))
{
MessageBox.Show("Le nom est obligatoire.");
return;
}
Resultat = new Medecin { Nom = txtNom.Text.Trim() };
DialogResult = DialogResult.OK;
}
Autre correction possible : laisser OK sur le bouton et écrire DialogResult = DialogResult.None; avant le return, ce qui annule la fermeture. Elle est moins lisible.
Tu avais trouvé ?
Exercice 4 sur 4 Exporter en CSV Réussi À revoir
Que contient le fichier exporté par btnExporter_Click pour ces deux patients ? Dupont Marie, née le 12 mai 1990 ; Petit Léa, sans date de naissance connue.
Voir la réponseMasquer la réponse de l'exercice 4
Nom;Prénom;Naissance
Dupont;Marie;12/05/1990
Petit;Léa;
Pour Léa, p.DateNaissance vaut null : l'interpolation {p.DateNaissance:dd/MM/yyyy} produit une chaîne vide, la ligne se termine donc par le séparateur. Le fichier commence aussi par les trois octets de la marque UTF-8 (BOM), invisibles dans un éditeur.
Tu avais trouvé ?