Collegare il codice C# agli eventi DOM con i gestori di eventi Blazor

Completato

La maggior parte degli elementi HTML espone eventi che vengono attivati quando si verifica qualcosa di significativo, ad esempio quando una pagina ha terminato il caricamento, l'utente ha fatto clic su un pulsante o il contenuto di un elemento HTML è stato modificato. Un'app può gestire un evento in diversi modi:

  • L'app può ignorare l'evento.
  • L'app può eseguire un gestore dell'evento scritto in JavaScript per elaborare l'evento.
  • L'app può eseguire un gestore dell'evento Blazor scritto in C# per elaborare l'evento.

In questa unità si esaminerà in dettaglio la terza opzione: come creare un gestore dell'evento Blazor in C# per elaborare un evento.

Gestire un evento con Blazor e C#

Ogni elemento nel markup HTML di un'app Blazor supporta numerosi eventi. La maggior parte di questi eventi corrisponde agli eventi DOM disponibili nelle normali applicazioni Web, ma è anche possibile creare eventi definiti dall'utente che vengono attivati con la scrittura di codice. Per acquisire un evento con Blazor, scrivere un metodo C# che gestisce l'evento, quindi associare l'evento al metodo con una direttiva Blazor. Per un evento DOM, la direttiva Blazor condivide lo stesso nome dell'evento HTML equivalente, ad esempio @onkeydown o @onfocus. Ad esempio, l'app di esempio generata tramite l'app Blazor Server contiene il codice seguente nella pagina Counter.razor . Nella pagina è visualizzato un pulsante. Quando l'utente seleziona il pulsante, l'evento @onclick attiva il metodo IncrementCount che incrementa un contatore che indica quante volte è stato fatto clic sul pulsante. Il valore della variabile contatore viene visualizzato dall'elemento <p> nella pagina:

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

Molti metodi del gestore dell'evento accettano un parametro che fornisce informazioni contestuali aggiuntive. Questo parametro è il parametro EventArgs. Ad esempio, l'evento @onclick passa informazioni sul pulsante su cui l'utente ha fatto clic o indica se è stato premuto un tasto, ad esempio CTRL o ALT, durante il clic, in un parametro MouseEventArgs. Non è necessario specificare questo parametro quando si chiama il metodo ; Il runtime Blazor lo aggiunge automaticamente. È possibile eseguire query su questo parametro nel gestore dell'evento. Il codice seguente incrementa il contatore mostrato nell'esempio precedente di cinque unità se l'utente preme il tasto CTRL mentre fa clic sul pulsante:

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>


@code {
    private int currentCount = 0;

    private void IncrementCount(MouseEventArgs e)
    {
        if (e.CtrlKey) // Ctrl key pressed as well
        {
            currentCount += 5;
        }
        else
        {
            currentCount++;
        }
    }
}

Altri eventi forniscono parametri EventArgs diversi. Ad esempio, l'evento @onkeypress passa un parametro KeyboardEventArgs che indica il tasto premuto dall'utente. Per qualsiasi evento DOM, se queste informazioni non sono necessarie, è possibile omettere il parametro EventArgs dal metodo di gestione degli eventi.

Gestione degli eventi in JavaScript e gestione degli eventi con Blazor

Un'applicazione Web tradizionale usa JavaScript per acquisire ed elaborare gli eventi. Si crea una funzione come parte di un elemento <script> HTML e quindi si imposta una chiamata alla funzione quando si verifica l'evento. Per un confronto con l'esempio blazor precedente, il codice seguente mostra un frammento di una pagina HTML che incrementa un valore e visualizza il risultato ogni volta che l'utente seleziona il pulsante Fai clic su di me . Il codice usa la libreria jQuery per accedere al DOM.

<p id="currentCount">Current count: 0</p>

<button class="btn btn-primary" onclick="incrementCount()">Click me</button>

<!-- Omitted for brevity -->

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
    var currentCount = 0;

    function incrementCount() {
        currentCount++;
        $('#currentCount').html('Current count:' + currentCount);
    }
</script>

Oltre alle differenze sintattiche nelle due versioni del gestore dell'evento, notare le differenze funzionali seguenti:

  • JavaScript non antepone il nome dell'evento con un @ segno, ma non è una direttiva Blazor.
  • Nel codice Blazor specificare il nome del metodo di gestione degli eventi quando lo si collega a un evento. In JavaScript si scrive un'istruzione che chiama il metodo di gestione degli eventi; specificare parentesi quadre arrotondate ed eventuali parametri necessari.
  • Cosa più importante, il gestore dell'evento JavaScript viene eseguito nel browser nel client. Se si sta creando un'app Blazor Server, il gestore eventi Blazor viene eseguito sul server e aggiorna solo il browser con le modifiche apportate all'interfaccia utente al termine del gestore eventi. Inoltre, il meccanismo Blazor consente a un gestore eventi di accedere ai dati statici condivisi tra sessioni; il modello JavaScript non lo fa. Tuttavia, la gestione di alcuni eventi che si verificano di frequente, ad @onmousemove esempio, può causare la lentezza dell'interfaccia utente perché richiedono un round trip di rete al server. Potrebbe essere preferibile gestire eventi come questi nel browser, usando JavaScript.

Importante

È possibile modificare il DOM usando codice JavaScript da un gestore dell'evento e usare il codice C# Blazor. Tuttavia, Blazor mantiene la propria copia del DOM, che viene usata per aggiornare l'interfaccia utente quando necessario. Se si usa JavaScript e codice Blazor per modificare gli stessi elementi nel DOM, si corre il rischio di danneggiare il DOM e compromettere la privacy e la sicurezza dei dati nell'app Web.

Gestire gli eventi in modo asincrono

Per impostazione predefinita, i gestori di eventi Blazor sono sincroni. Se un gestore dell'evento esegue un'operazione potenzialmente a esecuzione prolungata, ad esempio la chiamata a un servizio Web, il thread in cui viene eseguito il gestore dell'evento verrà bloccato fino al completamento dell'operazione. Ciò può causare una risposta lenta nell'interfaccia utente. Per evitare questo problema, è possibile designare un metodo del gestore dell'evento come asincrono. Usare la parola chiave async di C#. Il metodo deve restituire un oggetto Task. È quindi possibile usare l'operatore await all'interno del metodo del gestore dell'evento per avviare qualsiasi attività a esecuzione prolungata in un thread separato e liberare il thread corrente per altre operazioni. Al termine di un'attività a esecuzione prolungata, viene ripresa l'esecuzione del gestore dell'evento. Il gestore dell'evento di esempio seguente esegue un metodo che richiede molto tempo in modo asincrono:

<button @onclick="DoWork">Run time-consuming operation</button>

@code {
    private async Task DoWork()
    {
        // Call a method that takes a long time to run and free the current thread
        var data = await timeConsumingOperation();

        // Omitted for brevity
    }
}

Nota

Per informazioni dettagliate sulla creazione di metodi asincroni in C#, vedere Scenari di programmazione asincrona.

Usare un evento per impostare lo stato attivo su un elemento del DOM

In una pagina HTML l'utente può usare il tasto TAB per passare da un elemento all'altro e lo stato attivo viene spostato nell'ordine in cui vengono visualizzati gli elementi HTML nella pagina. In alcune occasioni, potrebbe essere necessario eseguire l'override di questa sequenza e forzare l'utente a visitare un elemento specifico.

Il modo più semplice per eseguire questa attività consiste nell'usare il metodo FocusAsync. Si tratta di un metodo di istanza di un oggetto ElementReference. ElementReference deve fare riferimento all'elemento su cui si vuole impostare lo stato attivo. Si designa un riferimento all'elemento con l'attributo @ref e si crea un oggetto C# con lo stesso nome nel codice.

Nell'esempio seguente il @onclick gestore eventi per l'elemento <button> imposta lo stato attivo sull'elemento <di input> . Il gestore dell'evento @onfocus dell'elemento <input> visualizza il messaggio "Received focus" (Stato attivo ricevuto) quando l'elemento riceve lo stato attivo. All'elemento <input> viene fatto riferimento attraverso la variabile InputField nel codice:

<button class="btn btn-primary" @onclick="ChangeFocus">Click me to change focus</button>
<input @ref=InputField @onfocus="HandleFocus" value="@data"/>

@code {
    private ElementReference InputField;
    private string data;

    private async Task ChangeFocus()
    {
        await InputField.FocusAsync();
    }

    private async Task HandleFocus()
    {
        data = "Received focus";
    }

L'immagine seguente mostra il risultato quando l'utente seleziona il pulsante:

Screenshot of the web page after the user has clicked the button to set the focus to the input element.

Nota

Un'app deve solo indirizzare lo stato attivo su un controllo specifico per un motivo specifico, ad esempio per chiedere all'utente di modificare l'input dopo un errore. Non usare lo stato attivo per forzare l'utente a spostarsi tra gli elementi di una pagina in un ordine fisso; questo può essere molto frustrante per l'utente che potrebbe voler rivedere gli elementi per modificare l'input.

Scrivere gestori di eventi inline

C# supporta le espressioni lambda. Un'espressione lambda consente di creare una funzione anonima. Un'espressione lambda è utile se si ha un semplice gestore dell'evento che non è necessario riutilizzare altrove in una pagina o un componente. Nel primo esempio di conteggio dei clic illustrato all'inizio di questa unità è possibile rimuovere il metodo IncrementCount e sostituire la chiamata al metodo con un'espressione lambda che esegue la stessa attività:

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="() => currentCount++">Click me</button>

@code {
    private int currentCount = 0;
}

Nota

Per informazioni dettagliate sul funzionamento delle espressioni lambda, vedere Espressioni lambda e funzioni anonime.

Questo approccio è utile anche se si desidera fornire altri argomenti per un metodo di gestione degli eventi. Nell'esempio seguente il metodo HandleClick accetta un MouseEventArgs parametro nello stesso modo di un gestore eventi click ordinario, ma accetta anche un parametro stringa. Il metodo elabora l'evento clic come in precedenza, ma visualizza anche il messaggio per l'utente che ha premuto il tasto CTRL. L'espressione lambda chiama il metodo HandleCLick, passando il parametro MouseEventArgs (mouseEvent) e una stringa.

@page "/counter"
@inject IJSRuntime JS

<h1>Counter</h1>

<p id="currentCount">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick='mouseEvent => HandleClick(mouseEvent, "Hello")'>Click me</button>

@code {
    private int currentCount = 0;

    private async Task HandleClick(MouseEventArgs e, string msg)
    {
        if (e.CtrlKey) // Ctrl key pressed as well
        {
            await JS.InvokeVoidAsync("alert", msg);
            currentCount += 5;
        }
        else
        {
            currentCount++;
        }
    }
}

Nota

Questo esempio usa la funzione JavaScript alert per visualizzare il messaggio perché non esiste alcuna funzione equivalente in Blazor. Usare l'interoperabilità JavaScript per chiamare JavaScript dal codice Blazor. I dettagli di questa tecnica sono l'argomento di un altro modulo.

Sostituire le azioni del DOM predefinite per gli eventi

Diversi eventi DOM hanno azioni predefinite eseguite quando si verifica l'evento, indipendentemente dal fatto che sia disponibile un gestore eventi per tale evento. Ad esempio, l'evento @onkeypress per un elemento <input> visualizza sempre il carattere che corrisponde al tasto premuto dall'utente e che gestisce la pressione del tasto. Nell'esempio successivo l'evento @onkeypress viene usato per convertire l'input dell'utente in caratteri maiuscoli. Inoltre, se l'utente digita un carattere @, il gestore dell'evento visualizza un avviso:

<input value=@data @onkeypress="ProcessKeyPress"/>

@code {
    private string data;

    private async Task ProcessKeyPress(KeyboardEventArgs e)
    {
        if (e.Key == "@")
        {
            await JS.InvokeVoidAsync("alert", "You pressed @");
        }
        else
        {
            data += e.Key.ToUpper();
        }
    }
}

Se si esegue questo codice e si preme il tasto @, verrà visualizzato l'avviso ma verrà aggiunto anche il carattere @ all'input. L'aggiunta del carattere @ è l'azione predefinita dell'evento.

Screenshot of the user input showing the @ character.

Se si vuole eliminare questo carattere dalla visualizzazione nella casella di input, è possibile sostituire l'azione predefinita con l'attributo preventDefault dell'evento, come illustrato di seguito:

<input value=@data @onkeypress="ProcessKeyPress" @onkeypress:preventDefault />

L'evento verrà comunque attivato, ma verranno eseguite solo le azioni definite dal gestore dell'evento.

Alcuni eventi in un elemento figlio nel DOM possono attivare eventi negli elementi padre. Nell'esempio seguente l'elemento <div> contiene un @onclick gestore eventi. <button> all'interno di <div> ha il proprio gestore dell'evento @onclick. <div> contiene anche un elemento <input>:

<div @onclick="HandleDivClick">
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
    <input value=@data @onkeypress="ProcessKeyPress" @onkeypress:preventDefault />
</div>

@code {
    private async Task HandleDivClick()
    {
        await JS.InvokeVoidAsync("alert", "Div click");
    }

    private async Task ProcessKeyPress(KeyboardEventArgs e)
    {
        // Omitted for brevity
    }

    private int currentCount = 0;

    private void IncrementCount(MouseEventArgs e)
    {
        // Omitted for brevity
    }
}

Quando l'app viene eseguita, se l'utente fa clic su qualsiasi elemento (o spazio vuoto) nell'area occupata dall'elemento <div>, verrà eseguito il metodo HandleDivClick e visualizzato un messaggio. Se l'utente seleziona il pulsante Click me, verrà eseguito il metodo IncrementCount, seguito da HandleDivClick; l'evento @onclick viene propagato verso l'alto nell'albero del DOM. Se <div> era parte di un altro elemento che gestiva anche l'evento @onclick, verrà eseguito anche quel gestore dell'evento e così via fino alla radice dell'albero del DOM. È possibile ridurre questa proliferazione verso l'alto di eventi con l'attributo stopPropagation di un evento, come illustrato di seguito:

<div @onclick="HandleDivClick">
    <button class="btn btn-primary" @onclick="IncrementCount" @onclick:stopPropagation>Click me</button>
    <!-- Omitted for brevity -->
</div>

Usare EventCallback per gestire gli eventi tra i componenti

Una pagina Blazor può contenere uno o più componenti Blazor e i componenti possono essere annidati in una relazione padre-figlio. Un evento in un componente figlio può attivare un metodo del gestore eventi in un componente padre usando un oggetto EventCallback. Un callback fa riferimento a un metodo nel componente padre. Il componente figlio può eseguire il metodo richiamando il callback. Questo meccanismo è simile all'uso di delegate per fare riferimento a un metodo in un'applicazione C#.

Un callback può accettare un singolo parametro. EventCallback è un tipo generico. Il parametro di tipo specifica il tipo dell'argomento passato al callback.

Per un esempio, esaminare lo scenario seguente. Si vuole creare un componente denominato TextDisplay che consente all'utente di immettere una stringa di input e di trasformare la stringa. È possibile che si voglia convertire la stringa in caratteri maiuscoli, minuscoli, maiuscoli e minuscoli, filtrare i caratteri della stringa o eseguire un altro tipo di trasformazione. Tuttavia, quando si scrive il codice per il TextDisplay componente, non si sa quale sarà il processo di trasformazione e si vuole invece rinviare questa operazione a un altro componente. Il codice seguente illustra il TextDisplay componente . Fornisce la stringa di input sotto forma di elemento <input> che consente all'utente di immettere un valore di testo.

@* TextDisplay component *@
@using WebApplication.Data;

<p>Enter text:</p>
<input @onkeypress="HandleKeyPress" value="@data" />

@code {
    [Parameter]
    public EventCallback<KeyTransformation> OnKeyPressCallback { get; set; }

    private string data;

    private async Task HandleKeyPress(KeyboardEventArgs e)
    {
        KeyTransformation t = new KeyTransformation() { Key = e.Key };
        await OnKeyPressCallback.InvokeAsync(t);
        data += t.TransformedKey;
    }
}

Il componente TextDisplay usa un oggetto EventCallback denominato OnKeyPressCallback. Il codice nel metodo HandleKeypress richiama il callback. Il gestore dell'evento @onkeypress viene eseguito ogni volta che viene premuto un tasto e chiama il metodo HandleKeypress. Il metodo HandleKeypress crea un oggetto KeyTransformation usando il tasto premuto dall'utente e passa l'oggetto come parametro al callback. Il tipo KeyTransformation è una classe semplice con due campi:

namespace WebApplication.Data
{
    public class KeyTransformation
    {
        public string Key { get; set; }
        public string TransformedKey { get; set; }
    }
}

Il campo key contiene il valore immesso dall'utente e il campo TransformedKey conterrà il valore trasformato del tasto quando è stato elaborato.

In questo esempio l'oggetto EventCallback è un parametro del componente il cui valore viene fornito al momento della creazione del componente. Questa azione viene eseguita da un altro componente denominato TextTransformer:

@page "/texttransformer"
@using WebApplication.Data;

<h1>Text Transformer - Parent</h1>

<TextDisplay OnKeypressCallback="@TransformText" />

@code {
    private void TransformText(KeyTransformation k)
    {
        k.TransformedKey = k.Key.ToUpper();
    }
}

Il componente TextTransformer è una pagina Blazor che crea un'istanza del componente TextDisplay. Popola il parametro OnKeypressCallback con un riferimento al metodo TransformText nella sezione di codice della pagina. Il metodo TransformText accetta l'oggetto KeyTransformation fornito come argomento e inserisce nella proprietà TransformedKey il valore trovato nella proprietà Keyconvertita in maiuscolo. Il diagramma seguente illustra il flusso del controllo quando un utente immette un valore nel <campo di input> nel TextDisplay componente visualizzato dalla TextTransformer pagina:

Diagram of the flow of control with an EventCallback in a child component.

Il vantaggio di questo approccio consiste nella possibilità di usare il componente TextDisplay con qualsiasi pagina che fornisce un callback per il parametro OnKeypressCallback. Esiste una separazione completa tra la visualizzazione e l'elaborazione. È possibile cambiare il metodo TransformText per qualsiasi altro callback corrispondente alla firma del parametro EventCallback nel componente TextDisplay.

È possibile collegare un callback direttamente a un gestore dell'evento senza usare un metodo intermedio se il tipo di callback viene specificato con il parametro EventArgs appropriato. Ad esempio, un componente figlio potrebbe fare riferimento a un callback in grado di gestire gli eventi del mouse come @onclick come questo:

<button @onclick="OnClickCallback">
    Click me!
</button>

@code {
    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

In questo caso EventCallback accetta un parametro di tipo MouseEventArgs e può quindi essere specificato come gestore per l'evento @onclick.

Verificare le conoscenze

1.

Quale funzionalità è consigliabile usare per passare eventi tra i componenti Blazor?

2.

Quale metodo viene usato per sostituire l'azione predefinita in un elemento del DOM HTML?