Événements et accesseurs d’événement personnalisés dans les composants Windows Runtime
La prise en charge de .NET pour les composants Windows Runtime facilite la déclaration des composants d’événements en masquant les différences entre le modèle d’événement plateforme Windows universelle (UWP) et le modèle d’événement .NET. Toutefois, lorsque vous déclarez des accesseurs d’événements personnalisés dans un composant Windows Runtime, vous devez suivre le modèle utilisé dans UWP.
Inscription d’événements
Lorsque vous vous inscrivez pour gérer un événement dans UWP, l’accesseur d’ajout retourne un jeton. Pour annuler l’inscription, vous transmettez ce jeton à l’accesseur de suppression. Cela signifie que les accesseurs d’ajout et de suppression pour les événements UWP ont des signatures différentes des accesseurs auxquels vous êtes habitué.
Heureusement, les compilateurs Visual Basic et C# simplifient ce processus : lorsque vous déclarez un événement avec des accesseurs personnalisés dans un composant Windows Runtime, les compilateurs utilisent automatiquement le modèle UWP. Par exemple, vous obtenez une erreur du compilateur si votre accesseur d’ajout ne retourne pas de jeton. .NET fournit deux types pour prendre en charge l’implémentation :
- La structure EventRegistrationToken représente le jeton.
- La classe T> EventRegistrationTokenTable<crée des jetons et gère un mappage entre les jetons et les gestionnaires d’événements. L’argument de type générique est le type d’argument d’événement. Vous créez une instance de cette classe pour chaque événement, la première fois qu’un gestionnaire d’événements est inscrit pour cet événement.
Le code suivant pour l’événement NumberChanged montre le modèle de base pour les événements UWP. Dans cet exemple, le constructeur de l’objet d’argument d’événement, NumberChangedEventArgs, prend un paramètre entier unique qui représente la valeur numérique modifiée.
Notez qu’il s’agit du même modèle que celui utilisé par les compilateurs pour les événements ordinaires que vous déclarez dans un composant Windows Runtime.
private EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
m_NumberChangedTokenTable = null;
public event EventHandler<NumberChangedEventArgs> NumberChanged
{
add
{
return EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
.GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
.AddEventHandler(value);
}
remove
{
EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
.GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
.RemoveEventHandler(value);
}
}
internal void OnNumberChanged(int newValue)
{
EventHandler<NumberChangedEventArgs> temp =
EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
.GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
.InvocationList;
if (temp != null)
{
temp(this, new NumberChangedEventArgs(newValue));
}
}
Private m_NumberChangedTokenTable As _
EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs))
Public Custom Event NumberChanged As EventHandler(Of NumberChangedEventArgs)
AddHandler(ByVal handler As EventHandler(Of NumberChangedEventArgs))
Return EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
AddEventHandler(handler)
End AddHandler
RemoveHandler(ByVal token As EventRegistrationToken)
EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
RemoveEventHandler(token)
End RemoveHandler
RaiseEvent(ByVal sender As Class1, ByVal args As NumberChangedEventArgs)
Dim temp As EventHandler(Of NumberChangedEventArgs) = _
EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
InvocationList
If temp IsNot Nothing Then
temp(sender, args)
End If
End RaiseEvent
End Event
La méthode static (Shared in Visual Basic) GetOrCreateEventRegistrationTokenTable crée l’instance de l’événement de l’objet EventRegistrationTokenTable<T> de manière différée. Transmettez le champ de niveau classe qui contiendra l’instance de table de jetons à cette méthode. Si le champ est vide, la méthode crée la table, stocke une référence à la table dans le champ et retourne une référence à la table. Si le champ contient déjà une référence de table de jetons, la méthode retourne simplement cette référence.
Important pour garantir la sécurité des threads, le champ qui contient l’instance de l’événement EventRegistrationTokenTable<T> doit être un champ de niveau classe. S’il s’agit d’un champ de niveau classe, la méthode GetOrCreateEventRegistrationTokenTable garantit que lorsque plusieurs threads tentent de créer la table de jetons, tous les threads obtiennent la même instance de la table. Pour un événement donné, tous les appels à la méthode GetOrCreateEventRegistrationTokenTable doivent utiliser le même champ de niveau classe.
L’appel de la méthode GetOrCreateEventRegistrationTokenTable dans l’accesseur remove et dans la méthode RaiseEvent (la méthode OnRaiseEvent en C#) garantit qu’aucune exception ne se produit si ces méthodes sont appelées avant l’ajout de délégués de gestionnaire d’événements.
Les autres membres de la classe T> EventRegistrationTokenTable<qui sont utilisés dans le modèle d’événement UWP incluent les éléments suivants :
La méthode AddEventHandler génère un jeton pour le délégué du gestionnaire d’événements, stocke le délégué dans la table, l’ajoute à la liste d’appels et retourne le jeton.
La surcharge de méthode RemoveEventHandler(EventRegistrationToken) supprime le délégué de la table et de la liste d’appel.
Notez que les méthodes AddEventHandler et RemoveEventHandler(EventRegistrationToken) verrouillent la table pour garantir la sécurité des threads.
La propriété InvocationList retourne un délégué qui inclut tous les gestionnaires d’événements actuellement inscrits pour gérer l’événement. Utilisez ce délégué pour déclencher l’événement ou utilisez les méthodes de la classe Delegate pour appeler les gestionnaires individuellement.
Notez que nous vous recommandons de suivre le modèle indiqué dans l’exemple fourni précédemment dans cet article et de copier le délégué dans une variable temporaire avant de l’appeler. Cela évite une condition de concurrence dans laquelle un thread supprime le dernier gestionnaire, réduisant le délégué à null juste avant qu’un autre thread tente d’appeler le délégué. Les délégués sont immuables, de sorte que la copie est toujours valide.
Placez votre propre code dans les accesseurs selon les besoins. Si la sécurité des threads est un problème, vous devez fournir votre propre verrouillage pour votre code.
Utilisateurs C# : lorsque vous écrivez des accesseurs d’événements personnalisés dans le modèle d’événement UWP, le compilateur ne fournit pas les raccourcis syntaxiques habituels. Elle génère des erreurs si vous utilisez le nom de l’événement dans votre code.
Utilisateurs Visual Basic : dans .NET, un événement n’est qu’un délégué multidiffusion qui représente tous les gestionnaires d’événements inscrits. Déclencher l’événement signifie simplement appeler le délégué. La syntaxe Visual Basic masque généralement les interactions avec le délégué, et le compilateur copie le délégué avant de l’appeler, comme décrit dans la note sur la sécurité des threads. Lorsque vous créez un événement personnalisé dans un composant Windows Runtime, vous devez traiter directement le délégué. Cela signifie également que vous pouvez, par exemple, utiliser la méthode MulticastDelegate.GetInvocationList pour obtenir un tableau qui contient un délégué distinct pour chaque gestionnaire d’événements, si vous souhaitez appeler les gestionnaires séparément.