Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Un événement est une action à laquelle vous pouvez répondre ou « gérer » dans le code. Les événements sont généralement générés par une action utilisateur, comme cliquer sur la souris ou appuyer sur une touche, mais ils peuvent également être générés par le code du programme ou par le système.
Les applications pilotées par les événements exécutent du code en réponse à un événement. Chaque formulaire et contrôle expose un ensemble prédéfini d’événements auxquels vous pouvez répondre. Si l’un de ces événements est déclenché et qu’il existe un gestionnaire d’événements associé, le gestionnaire est appelé et le code est exécuté.
Les types d’événements déclenchés par un objet varient, mais de nombreux types sont communs à la plupart des contrôles. Par exemple, la plupart des objets ont un Click événement déclenché lorsqu’un utilisateur clique dessus.
Remarque
De nombreux événements se produisent avec d’autres événements. Par exemple, au cours de l’événement DoubleClick se produisant, les événements MouseDown, MouseUpet Click se produisent.
Pour obtenir des informations générales sur la façon de déclencher et de consommer un événement, consultez Gestion et déclenchement d’événements dans .NET.
Délégués et leur rôle
Les délégués sont des classes couramment utilisées dans .NET pour générer des mécanismes de gestion des événements. Les délégués correspondent approximativement aux pointeurs de fonction, couramment utilisés dans Visual C++ et d’autres langages orientés objet. Toutefois à la différence des pointeurs de fonction, les délégués sont orientés objet, de type sécurisé et sûrs. En outre, lorsqu’un pointeur de fonction contient uniquement une référence à une fonction particulière, un délégué se compose d’une référence à un objet et de références à une ou plusieurs méthodes au sein de l’objet.
Ce modèle d'événement utilise les délégués pour lier les événements aux méthodes servant à les gérer. Le délégué permet à d’autres classes de s’inscrire à la notification d’événement en spécifiant une méthode de gestionnaire. Quand l'événement se produit, le délégué appelle la méthode liée. Pour plus d’informations sur la définition des délégués, consultez Gestion et déclenchement d’événements.
Les délégués peuvent être liés à une seule méthode ou à plusieurs méthodes, appelées multidiffusion. Lors de la création d’un délégué pour un événement, vous créez généralement un événement de multidiffusion. Une exception rare peut être un événement qui entraîne une procédure spécifique (par exemple, l’affichage d’une boîte de dialogue) qui ne se répète pas logiquement plusieurs fois par événement. Pour plus d’informations sur la création d’un délégué multidiffusion, consultez Comment combiner des délégués (délégués multidiffusion).
Un délégué de multidiffusion gère une liste d’appels des méthodes liées à celui-ci. Le délégué de multidiffusion prend en charge une méthode Combine pour ajouter une méthode à la liste d’appel et une méthode Remove pour la supprimer.
Lorsqu’une application enregistre un événement, le contrôle déclenche l’événement en appelant le délégué pour cet événement. Le délégué appelle à son tour la méthode liée. Dans le cas le plus courant (délégué multidiffusion), le délégué appelle à tour de rôle chaque méthode liée de la liste d'appel, ce qui produit une notification un à plusieurs. Cette stratégie signifie que le contrôle n’a pas besoin de conserver une liste d’objets cibles pour la notification d’événement : le délégué gère toutes les inscriptions et toutes les notifications.
Les délégués permettent également de lier plusieurs événements à la même méthode et par conséquent de produire une notification plusieurs-à-un. Par exemple, un événement de clic sur un bouton et un événement de clic sur une commande de menu peuvent tous deux faire appel au même délégué, qui appelle ensuite une méthode unique pour gérer ces événements distincts de manière identique.
Le mécanisme de liaison utilisé avec les délégués est dynamique : un délégué peut être lié au moment de l’exécution à n’importe quelle méthode dont la signature correspond à celle du gestionnaire d’événements. Avec cette fonctionnalité, vous pouvez configurer ou modifier la méthode liée en fonction d’une condition et attacher dynamiquement un gestionnaire d’événements à un contrôle.
Événements dans Windows Forms
Les événements dans Windows Forms sont déclarés avec le délégué pour les méthodes de gestionnaire EventHandler<TEventArgs>. Chaque gestionnaire d’événements fournit deux paramètres qui vous permettent de gérer l’événement correctement. L’exemple suivant montre un gestionnaire d’événements pour l’événement d’un contrôle ButtonClick.
Private Sub button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles button1.Click
End Sub
private void button1_Click(object sender, System.EventArgs e)
{
}
Le premier paramètre fournitsender une référence à l’objet qui a déclenché l’événement. Le deuxième paramètre, transmet eun objet spécifique à l’événement qui est géré. En référençant les propriétés de l’objet (et, parfois, ses méthodes), vous pouvez obtenir des informations telles que l’emplacement de la souris pour les événements de souris ou les données transférées dans des événements glisser-déplacer.
En règle générale, chaque événement produit un gestionnaire d’événements avec un type d’objet événement différent pour le deuxième paramètre. Certains gestionnaires d’événements, tels que ceux pour les événements MouseDown et MouseUp, ont le même type d’objet pour leur deuxième paramètre. Pour ces types d’événements, vous pouvez utiliser le même gestionnaire d’événements pour gérer les deux événements.
Vous pouvez également utiliser le même gestionnaire d’événements pour gérer le même événement pour différents contrôles. Par exemple, si vous avez un groupe de RadioButton contrôles sur un formulaire, vous pouvez créer un seul gestionnaire d’événements pour l’événement Click de chaque RadioButton. Pour plus d’informations, consultez Comment gérer un événement de contrôle.
Gestionnaires d’événements Async
Les applications modernes doivent souvent effectuer des opérations asynchrones en réponse à des actions utilisateur, telles que le téléchargement de données à partir d’un service web ou l’accès aux fichiers. Les gestionnaires d’événements Windows Forms peuvent être déclarés en tant que async méthodes pour prendre en charge ces scénarios, mais il existe des considérations importantes pour éviter les pièges courants.
Modèle de gestionnaire d’événements asynchrones de base
Les gestionnaires d’événements peuvent être déclarés avec le async modificateur (Async en Visual Basic) et l’utiliser await (Await en Visual Basic) pour les opérations asynchrones. Étant donné que les gestionnaires d’événements doivent retourner void (ou être déclarés en tant que Sub visual Basic), ils sont l’une des rares utilisations acceptables de async void (ou Async Sub en Visual Basic) :
private async void downloadButton_Click(object sender, EventArgs e)
{
downloadButton.Enabled = false;
statusLabel.Text = "Downloading...";
try
{
using var httpClient = new HttpClient();
string content = await httpClient.GetStringAsync("https://github.com/dotnet/docs/raw/refs/heads/main/README.md");
// Update UI with the result
loggingTextBox.Text = content;
statusLabel.Text = "Download complete";
}
catch (Exception ex)
{
statusLabel.Text = $"Error: {ex.Message}";
}
finally
{
downloadButton.Enabled = true;
}
}
Private Async Sub downloadButton_Click(sender As Object, e As EventArgs) Handles downloadButton.Click
downloadButton.Enabled = False
statusLabel.Text = "Downloading..."
Try
Using httpClient As New HttpClient()
Dim content As String = Await httpClient.GetStringAsync("https://github.com/dotnet/docs/raw/refs/heads/main/README.md")
' Update UI with the result
loggingTextBox.Text = content
statusLabel.Text = "Download complete"
End Using
Catch ex As Exception
statusLabel.Text = $"Error: {ex.Message}"
Finally
downloadButton.Enabled = True
End Try
End Sub
Important
Bien qu’il async void soit déconseillé, il est nécessaire pour les gestionnaires d’événements (et le code semblable au gestionnaire d’événements, par Control.OnClickexemple) car ils ne peuvent pas retourner Task. Habillage toujours des opérations attendues dans try-catch des blocs pour gérer correctement les exceptions, comme indiqué dans l’exemple précédent.
Pièges courants et interblocages
Avertissement
N’utilisez jamais d’appels bloquants tels que .Wait(), .Resultou .GetAwaiter().GetResult() dans des gestionnaires d’événements ou n’importe quel code d’interface utilisateur. Ces modèles peuvent entraîner des interblocages.
Le code suivant illustre un anti-modèle courant qui provoque des interblocages :
// DON'T DO THIS - causes deadlocks
private void badButton_Click(object sender, EventArgs e)
{
try
{
// This blocks the UI thread and causes a deadlock
string content = DownloadPageContentAsync().GetAwaiter().GetResult();
loggingTextBox.Text = content;
}
catch (Exception ex)
{
MessageBox.Show($"Error: {ex.Message}");
}
}
private async Task<string> DownloadPageContentAsync()
{
using var httpClient = new HttpClient();
await Task.Delay(2000); // Simulate delay
return await httpClient.GetStringAsync("https://github.com/dotnet/docs/raw/refs/heads/main/README.md");
}
' DON'T DO THIS - causes deadlocks
Private Sub badButton_Click(sender As Object, e As EventArgs) Handles badButton.Click
Try
' This blocks the UI thread and causes a deadlock
Dim content As String = DownloadPageContentAsync().GetAwaiter().GetResult()
loggingTextBox.Text = content
Catch ex As Exception
MessageBox.Show($"Error: {ex.Message}")
End Try
End Sub
Private Async Function DownloadPageContentAsync() As Task(Of String)
Using httpClient As New HttpClient()
Return Await httpClient.GetStringAsync("https://github.com/dotnet/docs/raw/refs/heads/main/README.md")
End Using
End Function
Cela provoque un blocage pour les raisons suivantes :
- Le thread d’interface utilisateur appelle la méthode asynchrone et bloque l’attente du résultat.
- La méthode asynchrone capture le thread d’interface
SynchronizationContextutilisateur . - Une fois l’opération asynchrone terminée, elle tente de continuer sur le thread d’interface utilisateur capturé.
- Le thread d’interface utilisateur est bloqué en attente de la fin de l’opération.
- L’interblocage se produit car aucune opération ne peut se poursuivre.
Opérations entre threads
Lorsque vous devez mettre à jour les contrôles d’interface utilisateur à partir de threads d’arrière-plan dans les opérations asynchrones, utilisez les techniques de marshaling appropriées. Comprendre la différence entre les approches bloquantes et non bloquantes est essentielle pour les applications réactives.
.NET 9 introduit Control.InvokeAsync, qui fournit un marshaling convivial asynchrone au thread d’interface utilisateur. Contrairement Control.Invoke aux envois (bloque le thread appelant), Control.InvokeAsyncles publications (non bloquantes) dans la file d’attente de messages du thread d’interface utilisateur. Pour plus d’informations sur Control.InvokeAsync, consultez Comment effectuer des appels thread-safe aux contrôles.
Principaux avantages d’InvokeAsync :
- Non bloquant : retourne immédiatement, ce qui permet au thread appelant de continuer.
-
Async-friendly : retourne une
Taskvaleur qui peut être attendue. - Propagation d’exception : propage correctement les exceptions vers le code appelant.
-
Prise en charge de l’annulation : prend en charge
CancellationTokenl’annulation de l’opération.
private async void processButton_Click(object sender, EventArgs e)
{
processButton.Enabled = false;
// Start background work
await Task.Run(async () =>
{
for (int i = 0; i <= 100; i += 10)
{
// Simulate work
await Task.Delay(200);
// Create local variable to avoid closure issues
int currentProgress = i;
// Update UI safely from background thread
await progressBar.InvokeAsync(() =>
{
progressBar.Value = currentProgress;
statusLabel.Text = $"Progress: {currentProgress}%";
});
}
});
processButton.Enabled = true;
}
Private Async Sub processButton_Click(sender As Object, e As EventArgs) Handles processButton.Click
processButton.Enabled = False
' Start background work
Await Task.Run(Async Function()
For i As Integer = 0 To 100 Step 10
' Simulate work
Await Task.Delay(200)
' Create local variable to avoid closure issues
Dim currentProgress As Integer = i
' Update UI safely from background thread
Await progressBar.InvokeAsync(Sub()
progressBar.Value = currentProgress
statusLabel.Text = $"Progress: {currentProgress}%"
End Sub)
Next
End Function)
processButton.Enabled = True
End Sub
Pour les opérations véritablement asynchrones qui doivent s’exécuter sur le thread d’interface utilisateur :
private async void complexButton_Click(object sender, EventArgs e)
{
// This runs on UI thread but doesn't block it
statusLabel.Text = "Starting complex operation...";
// Dispatch and run on a new thread
await Task.WhenAll(Task.Run(SomeApiCallAsync),
Task.Run(SomeApiCallAsync),
Task.Run(SomeApiCallAsync));
// Update UI directly since we're already on UI thread
statusLabel.Text = "Operation completed";
}
private async Task SomeApiCallAsync()
{
using var client = new HttpClient();
// Simulate random network delay
await Task.Delay(Random.Shared.Next(500, 2500));
// Do I/O asynchronously
string result = await client.GetStringAsync("https://github.com/dotnet/docs/raw/refs/heads/main/README.md");
// Marshal back to UI thread
await this.InvokeAsync(async (cancelToken) =>
{
loggingTextBox.Text += $"{Environment.NewLine}Operation finished at: {DateTime.Now:HH:mm:ss.fff}";
});
// Do more async I/O ...
}
Private Async Sub complexButton_Click(sender As Object, e As EventArgs) Handles complexButton.Click
'Convert the method to enable the extension method on the type
Dim method = DirectCast(AddressOf ComplexButtonClickLogic,
Func(Of CancellationToken, Task))
'Invoke the method asynchronously on the UI thread
Await Me.InvokeAsync(method.AsValueTask())
End Sub
Private Async Function ComplexButtonClickLogic(token As CancellationToken) As Task
' This runs on UI thread but doesn't block it
statusLabel.Text = "Starting complex operation..."
' Dispatch and run on a new thread
Await Task.WhenAll(Task.Run(AddressOf SomeApiCallAsync),
Task.Run(AddressOf SomeApiCallAsync),
Task.Run(AddressOf SomeApiCallAsync))
' Update UI directly since we're already on UI thread
statusLabel.Text = "Operation completed"
End Function
Private Async Function SomeApiCallAsync() As Task
Using client As New HttpClient()
' Simulate random network delay
Await Task.Delay(Random.Shared.Next(500, 2500))
' Do I/O asynchronously
Dim result As String = Await client.GetStringAsync("https://github.com/dotnet/docs/raw/refs/heads/main/README.md")
' Marshal back to UI thread
' Extra work here in VB to handle ValueTask conversion
Await Me.InvokeAsync(DirectCast(
Async Function(cancelToken As CancellationToken) As Task
loggingTextBox.Text &= $"{Environment.NewLine}Operation finished at: {DateTime.Now:HH:mm:ss.fff}"
End Function,
Func(Of CancellationToken, Task)).AsValueTask() 'Extension method to convert Task
)
' Do more Async I/O ...
End Using
End Function
Conseil / Astuce
.NET 9 inclut des avertissements d’analyseur (WFO2001) pour vous aider à détecter quand les méthodes asynchrones sont transmises de manière incorrecte aux surcharges synchrones de InvokeAsync. Cela permet d’éviter le comportement « fire-and-forget ».
Meilleures pratiques
- Utilisez async/await de manière cohérente : ne mélangez pas de modèles asynchrones avec des appels bloquants.
-
Gérer les exceptions : encapsulez toujours les opérations asynchrones dans les blocs try-catch dans
async voidles gestionnaires d’événements. - Fournissez des commentaires sur l’utilisateur : Mettez à jour l’interface utilisateur pour afficher la progression ou l’état de l’opération.
- Désactiver les contrôles pendant les opérations : empêcher les utilisateurs de démarrer plusieurs opérations.
- Utilisez CancellationToken : Prise en charge de l’annulation de l’opération pour les tâches de longue durée.
- Envisagez ConfigureAwait(false) : utilisez-le dans le code de la bibliothèque pour éviter de capturer le contexte de l’interface utilisateur si nécessaire.
Contenu connexe
.NET Desktop feedback