Fermer

décembre 3, 2020

Comment passer des arguments à vos fonctions onclick dans Blazor


Il est simple de connecter des gestionnaires d'événements à des événements HTML comme onClick dans vos composants Blazor, mais comment pouvez-vous transmettre des informations supplémentaires et différents types d'arguments?

Supposons que vous souhaitiez effectuer une action lorsqu'un bouton est cliqué dans Blazor.

Cela est simple à faire en utilisant la syntaxe de gestion d'événements de Blazor.

Vous pouvez câbler une méthode à un événement d'élément HTML et Blazor invoquera cette méthode comme gestionnaire d'événements pour ledit événement.

 @page " /dis bonjour"

 < bouton   @onclick  =  " GreetMe "  >  Click me!  </  bouton > 

@Message

@code {
    string Message {get; ensemble; }

    void GreetMe ()
    {
        Message = "Bonjour";
    }
}

Blazor invoquera GreetMe lorsque l'utilisateur cliquera sur le bouton, et l'utilisateur verra un message d'accueil convivial (quoique peu imaginatif).

Votre gestionnaire d'événements onclick peut éventuellement accepter un MouseEventArgs paramètre que Blazor inclura automatiquement lorsqu'il invoquera le gestionnaire.

 @code  {  
     string  Message  {  get ; [19659019] set ;  } 

     void   GreetMe  ( MouseEventArgs args ) 
     {
         if   ( args  .  AltKey ) 
            Message  =   "Salutations" ; 
         autre 
            Message  =   "Bonjour" ; 
    }     
} 

Maintenant, nous pouvons facilement accéder à des détails supplémentaires sur le clic du bouton, par exemple si l'utilisateur a maintenu la touche ALT enfoncée quand il a cliqué, dans ce cas affichant un message d'accueil différent si c'est le cas.

Différents événements inclura différents types d'arguments d'événement – consultez la documentation officielle pour une liste complète.

Transmission de données supplémentaires à vos gestionnaires d'événements

Jusqu'ici tout va bien, mais que faire si vous avez besoin de transmettre des données supplémentaires à vos gestionnaires d'événements?

Par exemple, vous devrez peut-être parcourir une collection et afficher des boutons pour chaque élément.

 @page "/ todoList"
@utilisation de System.Diagnostics

@foreach (var todo dans Todos)
{
     < p >  @ todo.Text  </  p > 
    
}

@code {
    Liste  < Todo >  Todos {get; ensemble; } = nouvelle liste  < Todo > 
    {
        nouveau Todo {Id = 1, Text = "Do this"},
        nouveau Todo {Id = 2, Text = "And this"}
    };

    annuler Supprimer (Todo todo)
    {
        Debug.WriteLine ($ "Suppression de {todo.Id}");
    }

    cours privé Todo
    {
        public int Id {get; ensemble; }
        chaîne publique Text {get; ensemble; }
    }
}

Cet exemple parcourt une liste de Todos et affiche du texte pour chacun d'eux.

Mais que se passe-t-il si nous voulons supprimer un Todo? Nous avons besoin d'un moyen pour indiquer quel Todo doit être supprimé lorsque nous cliquons sur un bouton pour ce Todo spécifique.

La manière standard d'appeler un EventHandler ne fonctionnera pas car nous ne pouvons pas passer d'arguments supplémentaires.

  < bouton   @onclick  =  " Supprimer "  >  X  </  bouton > 

Delete n'aurait aucune idée du Todo à supprimer!

La réponse est d'utiliser un lambda qui sera ensuite délégué à notre gestionnaire d'événements.

 @foreach (var todo in Todos)
{
     < p >  @ todo.Text  </  p > 
     < bouton   @onclick  =  " () => Supprimer (todo) "  >  X  </  bouton > 
}

Nous avons dit à Blazor d'invoquer une expression anonyme (représentée ici en utilisant la syntaxe lambda) qui à son tour appelle Delete en passant l'instance actuelle Todo pour le trajet. [19659003] Notre méthode Delete recevra l'instance pertinente de Todo et pourra prendre toutes les mesures qu'elle juge nécessaires.

  void   Delete  ( ] Todo todo ) 
 {
    Debug .  WriteLine  ( $  "Deleting {todo.Id}" ) ; 
    
} 

Que faire si vous avez besoin des EventArgs d'origine

Que faire si vous avez besoin à la fois des arguments d'événement d'origine (tels que MouseEventArgs ) et de vos propres arguments (tels que celui de Todo à supprimer).

Pour cela, vous pouvez référencer l'original event args dans votre lambda et faites-les suivre.

  < button   @onclick  =  " args => Delete (todo, args) "  >  X  </  bouton > 

Ceci transmet efficacement les arguments de l'événement d'origine à votre gestionnaire d'événements, où vous pouvez les utiliser comme bon vous semble.

  void   Delete  ( Todo todo  ,  MouseEventArgs args ) 
 {
    Débogage .  WriteLine  ( $  "Alt Key pressed: {args.AltKey} while deleting {todo.Id}" ) ; 
} 

Gotcha — Loops and Lambdas

Utiliser des lambdas à l'intérieur de forEach boucles dans vos composants Blazor fonctionnera généralement comme prévu, mais attention si vous décidez d'utiliser un normal pour boucle avec une variable de boucle à la place.

 @page "/ loops"
@utilisation de System.Diagnostics

@for (int i = 0; i <5; i ++)
{
     < bouton   @onclick  =  " () => Log (i) "  >  @i [19659006] </  bouton > 
}

@code {
    void Log (int i)
    {
        Debug.WriteLine ($ "Journalisation: {i}");
    }
}

Dans cet exemple, nous bouclons simplement de 0 à 5 et restituons un bouton à chaque fois.

Lorsque vous cliquez sur le bouton, vous voyez la valeur de la variable de boucle i déconnecté la console de débogage (dans votre IDE lorsque vous déboguez votre application).

Maintenant, faites une pause pendant un moment et réfléchissez à ce que vous vous attendez à voir lorsque vous exécutez ce code et cliquez sur l'un des boutons…

être pardonné de s'attendre à voir des nombres différents lorsque vous cliquez sur différents boutons.

Mais en réalité, vous verrez…

 Sortie de la console montrant les résultats de l'utilisation directe de la variable de boucle

… sur 5s!

Quel que soit le bouton sur lequel vous cliquez, la sortie de débogage vous indique que i est toujours 5.

Etrange hein?!

Ce n'est pas une chose Blazor, mais en fait la manière anonyme les expressions et les boucles fonctionnent en C #.

Si vous voulez simplement résoudre ce problème et passer à autre chose, capturer i dans une variable locale à l'intérieur du lo op fera l'affaire.

 @page "/ loops"
@utilisation de System.Diagnostics

@for (int i = 0; i <5; i ++)
{
    var j = i;
     < bouton   @onclick  =  " () => Journal (j) "  >  @i [19659006] </  bouton > 
}

@code {
    void Log (int i)
    {
        Debug.WriteLine ($ "Journalisation: {i}");
    }
}

Ici, nous saisissons la valeur de i dans j puis utilisons j dans notre expression lambda.

Pour comprendre pourquoi cela se produit, il faut un détour dans le monde secret de votre compilateur C #!

Comprendre les boucles C # et les lambdas

Voici une représentation pseudo-code du code C # compilé qui sera exécuté pour notre composant Blazor. En règle générale, nous n'avons pas besoin d'y prêter beaucoup d'attention, mais dans ce cas, il est utile de voir ce qui se passe réellement «sous le capot».

  [ Route  ( " / loops ") ] 
 public   class   Loops  :  ComponentBase
 {
     void   BuildRenderTree  ( RenderTreeBuilder _builder ) 
     {
        Boucles .  DisplayClass displayClass  =   nouveau   Boucles .  DisplayClass  () ; 
        displayClass .  this   =   this ; 
         for   ( displayClass .  i  =   0 [19659009];  displayClass .  i  <  5 ;  displayClass .  i  ++ ) [19659182] {
            _builder .  openElement  ( 'button' ) ; 
            _builder .  AddAttribute  < MouseEventArgs > 
                 ( "onclick"   new   Action  ( displayClass .  handle ) ) ; 
            _builder .  closeElement  ( 'button' ) ; 
        } 
    } 

     private   void   Log  ( int  i ) 
     {
        Debug .  WriteLine  ( string .  Format  ( "Logging: {0}"   ( object )  i ) ) ; 
    } 

     [ CompilerGenerated ] 
     private   scellé   class [19659160] DisplayClass 
     {
         public   int  i ; 
         public  Loops  this ; 

         internal   void   handle  () 
         {
             this .  Log  ( i ) ; 
        } 
    } [19659088]} 

J'ai supprimé certains détails et simplifié le code actuel pour le garder lisible, mais l'essentiel est le suivant.

Le compilateur génère une méthode BuildRenderTree pour votre composant.

Quand ceci est rendu, cette méthode crée une seule instance de quelque chose appelé DisplayClass .

Notez comment BuildRenderTree ne crée qu'une seule instance de cette classe (en dehors de la boucle), puis référence cette seule à l'intérieur de la boucle.

Chaque fois qu'il fait le tour de la boucle, il ajoute un bouton au RenderTreeBuilder et ajoute un attribut onclick pour ce bouton pointant vers un Action .

Fondamentalement, chaque action pointe vers la même instance unique de DisplayClass .

DisplayClass.i est ensuite incrémentée et nous refaites le tour de la boucle.

Au moment où nous avons été autour de la lo op 5 fois, le nombre final stocké dans i dans la classe d'affichage sera 5.

Maintenant, lorsque vous exécutez l'application et cliquez sur l'un des boutons, toutes les actions pointent vers l'instance unique de DisplayClass contenant la valeur i (5 dans ce cas).

Il n'est donc pas surprenant que vous voyiez beaucoup de 5s pour chaque clic de bouton!

L'astuce à surmonter il s'agit de capturer la valeur de i dans une variable locale à l'intérieur de la boucle for .

 @page "/ loops"
@utilisation de System.Diagnostics

@for (int i = 0; i <5; i ++)
{
    var j = i;
     < bouton   @onclick  =  " () => Journal (j) "  >  @i [19659006] </  bouton > 
}

Dans le code compilé résultant, une nouvelle instance de DisplayClass sera désormais créée à chaque tour de la boucle.

  void   BuildRenderTree  ( RenderTreeBuilder _builder ) 
 {
     for   ( int  index  =   0 ;  index  <  5 [19659009];   ++  index ) 
     {
Boucles .  DisplayClass displayClass  =   nouveau   Boucles .  DisplayClass  () ; 
    displayClass .  this   =   this ; 
        displayClass .  j  =  index ; 
        _builder .  openElement  ( 'button' ) ; 
        _builder .  AddAttribute  < MouseEventArgs > 
             ( "onclick"   new   Action  ( displayClass .  handle ) ) ; 
        _builder .  closeElement  ( 'button' ) ; 
    } 
} 

 [ CompilerGenerated ] 
 private   scellé   class   DisplayClass 
 {
     public   int  j ; 
     public  Loops  this ; [19659024] interne   vide   handle  () 
     {
         this .  Log  ( j )  ; 
    } 
} 

Cela évite le problème de valeur partagée i et cliquer sur les boutons fonctionne comme prévu.

En résumé

Blazor vous permet de gérer les événements HTML tels que onClick ] événements via des gestionnaires d'événements.

Vous pouvez utiliser des lambdas pour capturer des valeurs (par exemple dans une boucle) et les transmettre à vos gestionnaires d'événements.

Différents événements transmettent différents types d'arguments d'événements, alors consultez la documentation officielle pour

Attention à ces embêtants pour boucles; si vous utilisez la variable de boucle sans la capturer dans une variable locale à l'intérieur de la boucle, vous tomberez sous le coup du compilateur C # et verrez un comportement que vous ne voulez ou ne vous attendez probablement pas!





Source link