Anexar código C# a eventos DOM com manipuladores de eventos Blazor

Concluído

A maioria dos elementos HTML expõe eventos que são disparados quando algo significativo acontece, como quando uma página que termina de carregar, os usuários clicam em um botão ou o conteúdo de um elemento HTML é alterado. Um aplicativo pode manipular um evento de várias maneiras:

  • O aplicativo pode ignorar o evento.
  • O aplicativo pode executar um manipulador de eventos escrito em JavaScript para processar o evento.
  • O aplicativo pode executar um manipulador de eventos Blazor escrito em C# para processar o evento.

Nesta unidade, você verá detalhadamente a terceira opção; como criar um manipulador de eventos Blazor em C# para processar um evento.

Manipular um evento com Blazor e C#

Cada elemento na marcação HTML de um aplicativo Blazor dá suporte a muitos eventos. A maioria desses eventos corresponde aos eventos DOM disponíveis em aplicativos Web regulares, mas você também pode criar eventos definidos pelo usuário que são disparados escrevendo código. Para capturar um evento com Blazor, você escreve um método C# que manipula o evento e o vincula ao método com uma diretiva Blazor. Para um evento DOM, a diretiva Blazor compartilha o mesmo nome que o evento HTML equivalente, como @onkeydown ou @onfocus. Por exemplo, o aplicativo de exemplo gerado usando o Aplicativo Blazor Server contém o código exibido a seguir na página Counter.razor. Esta página exibe um botão. Quando o usuário seleciona o botão, o evento @onclick dispara o método IncrementCount, que incrementa um contador indicando quantas vezes o botão foi clicado. O valor da variável de contador é exibido pelo elemento <p> na página:

@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++;
    }
}

Muitos métodos de manipulador de eventos levam um parâmetro que fornece informações contextuais extras. Esse parâmetro é conhecido como um parâmetro EventArgs. Por exemplo, o evento @onclick passa informações sobre qual botão o usuário clicou ou se ele pressionou um botão como Ctrl ou Alt ao mesmo tempo em que clica no botão, em um parâmetro MouseEventArgs. Você não precisa fornecer esse parâmetro ao chamar o método; o runtime do Blazor o adiciona automaticamente. Você pode consultar esse parâmetro no manipulador de eventos. O código a seguir incrementa o contador mostrado no exemplo anterior em cinco se o usuário pressiona a tecla Ctrl ao mesmo tempo que clica no botão:

@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++;
        }
    }
}

Outros eventos fornecem parâmetros EventArgs diferentes. Por exemplo, o evento @onkeypress passa um parâmetro KeyboardEventArgs que indica qual tecla o usuário pressionou. Para qualquer um dos eventos DOM, se você não precisar dessas informações, poderá omitir o parâmetro EventArgs do método de manipulação de eventos.

Entender o tratamento de eventos em JavaScript versus manipulação de eventos com Blazor

Um aplicativo Web tradicional usa JavaScript para capturar e processar eventos. Crie uma função como parte de um elemento de <script> HTML e depois prepare a chamada dessa função para quando o evento ocorrer. Em comparação com o exemplo Blazor anterior, o código a seguir mostra um fragmento de uma página HTML que incrementa um valor e exibe o resultado sempre que os usuários selecionam o botão Clique em mim. O código usa a biblioteca jQuery para acessar o 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>

Além das diferenças sintáticas nas duas versões do manipulador de eventos, você deve observar as seguintes diferenças funcionais:

  • O JavaScript não prefixa o nome do evento com um sinal @; não é uma diretiva Blazor.
  • No código Blazor, você especifica o nome do método de manipulação de eventos ao anexá-lo a um evento. No JavaScript, você escreve uma instrução que chama o método de manipulação de eventos; você especifica parênteses e todos os parâmetros necessários.
  • O mais importante é que o manipulador de eventos JavaScript é executado no navegador, no cliente. Se você estiver desenvolvendo um Aplicativo Blazor Server, o manipulador de eventos Blazor é executado no servidor e atualiza apenas o navegador com as alterações feitas na interface do usuário quando o manipulador de eventos é concluído. Além disso, o mecanismo Blazor permite que um manipulador de eventos acesse dados estáticos compartilhados entre sessões; o modelo JavaScript não permite isso. No entanto,manipular alguns eventos que ocorrem com frequência, como @onmousemove, pode fazer com que a interface do usuário fique lenta, pois exigem uma viagem de ida e volta à rede para o servidor. Você pode preferir manipular eventos como esses no navegador, usando JavaScript.

Importante

Você pode manipular o DOM usando o código JavaScript de um manipulador de eventos, bem como usar o código Blazor C#. No entanto, o Blazor mantém sua própria cópia do DOM, que é usada para atualizar a interface do usuário quando necessário. Se você usar o código JavaScript e o Blazor para alterar os mesmos elementos no DOM, correrá o risco de corromper o DOM e possivelmente comprometer a privacidade e a segurança dos dados em seu aplicativo Web.

Manipular eventos de forma assíncrona

Por padrão, os manipuladores de eventos Blazor são síncronos. Se um manipulador de eventos executar uma operação potencialmente longa, como chamar um serviço Web, o thread no qual o manipulador de eventos é executado será bloqueado até que a operação seja concluída. Isso pode levar a uma resposta ruim na interface do usuário. Para combater isso, você pode designar um método de manipulador de eventos como assíncrono. Use a palavra-chave C# async. O método deve retornar um objeto Task. Você pode usar o operador await dentro do método do manipulador de eventos para iniciar tarefas de execução longa em um thread separado e liberar o thread atual para outro trabalho. Quando uma tarefa de execução longa é concluída, o manipulador de eventos é retomado. O manipulador de eventos de exemplo abaixo executa um método demorado de forma assíncrona:

<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
    }
}

Observação

Para obter informações detalhadas sobre a criação de métodos assíncronos em C#, leia Cenários de programação assíncrona.

Usar um evento para definir o foco para um elemento DOM

Em uma página HTML, o usuário pode tabular entre elementos e o foco naturalmente percorre a ordem em que os elementos HTML aparecem na página. Em algumas ocasiões, talvez seja necessário substituir essa sequência e forçar os usuários a visitar um elemento específico.

A maneira mais simples de executar essa tarefa é usar o método FocusAsync. Esse é um método de instância de um objeto ElementReference. O ElementReference deve referenciar o item ao qual você deseja definir o foco. Designe uma referência de elemento com o atributo @ref e crie um objeto C# com o mesmo nome em seu código.

No exemplo a seguir, o manipulador de eventos @onclick para o elemento <botão> define o foco no elemento <entrada>. O manipulador de eventos @onfocus do elemento <input> exibe a mensagem "Foco recebido" quando o elemento recebe o foco. O elemento <input> é referenciado por meio da variável InputField no código:

<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";
    }

A seguinte imagem mostra o resultado quando o usuário seleciona o botão:

Captura de tela da página da web após o usuário clicar no botão para definir o foco no elemento de entrada.

Observação

Um aplicativo só deve direcionar o foco para um controle específico por um motivo específico, como solicitar que o usuário modifique a entrada após um erro. Não use o foco para forçar os usuários a navegarem pelos elementos em uma página em uma ordem fixa; isso pode ser muito frustrante para os usuários que talvez queiram revisitar elementos e alterar a entrada.

Gravar manipuladores de eventos em linha

O C# dá suporte a expressões lambda. Uma expressão lambda permite que você crie uma função anônima. Uma expressão lambda será útil se você tiver um manipulador de eventos simples que não precise reutilizar em outro lugar em uma página ou componente. No exemplo de contagem de cliques inicial mostrado no início desta unidade, você pode remover o método IncrementCount e, em vez disso, substituir a chamada de método por uma expressão lambda que executa a mesma tarefa:

@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;
}

Observação

Para obter detalhes sobre como as expressões lambda funcionam, leia Expressões lambda e funções anônimas.

Essa abordagem também será útil se você quiser fornecer outros argumentos para um método de manipulação de eventos. No exemplo a seguir, o método HandleClick recebe um parâmetro MouseEventArgs da mesma maneira que um manipulador de eventos de clique comum, mas também aceita um parâmetro de cadeia de caracteres. O método processa o evento de clique como antes, mas também exibe a mensagem no usuário que pressionou a tecla Ctrl. A expressão lambda chama o método HandleCLick, passando o parâmetro MouseEventArgs (mouseEvent) e uma cadeia de caracteres.

@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++;
        }
    }
}

Observação

Este exemplo usa a função JavaScript alert para exibir a mensagem porque não há nenhuma função equivalente no Blazor. Use interop JavaScript para chamar JavaScript do código Blazor. Os detalhes dessa técnica são o assunto de um módulo separado.

Substituir ações DOM padrão para eventos

Vários eventos DOM têm ações padrão que são executadas quando o evento ocorre, independentemente de haver ou não um manipulador de eventos disponível para esse evento. Por exemplo, o evento @onkeypress de um elemento <input> sempre exibe o caractere que corresponde à chave que o usuário pressionou e manipula o pressionamento de tecla. No exemplo a seguir, o evento @onkeypress é usado para converter a entrada do usuário em maiúsculas. Além disso, se o usuário digita um caractere @, o manipulador de eventos exibe um alerta:

<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 você executar esse código e pressionar a tecla @, o alerta será exibido, mas o caractere @ também será adicionado à entrada. A adição do caractere @ é a ação padrão do evento.

Captura de tela da entrada do usuário mostrando o caractere @.

Se você quiser suprimir esse caractere de aparecer na caixa de entrada, poderá substituir a ação padrão pelo atributo preventDefault do evento, desta forma:

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

O evento ainda será disparado, mas somente as ações definidas pelo manipulador de eventos serão executadas.

Alguns eventos em um elemento filho no DOM podem disparar eventos em seus elementos pai. No exemplo a seguir, o elemento <div> contém um manipulador de eventos. @onclick O <botão> dentro do <div> tem o próprio manipulador de eventos @onclick. Além disso, o <div> contém um 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 o aplicativo for executado, se o usuário clicar em qualquer elemento (ou espaço vazio) na área ocupada pelo elemento <div>, o método HandleDivClick será executado e exibirá uma mensagem. Se o usuário selecionar o botão Click me, o método IncrementCount será executado, seguido por HandleDivClick; o evento @onclick propaga a árvore DOM. Se o <div> fizesse parte de outro elemento que também manipulasse o evento @onclick, esse manipulador de eventos também seria executado e assim por diante até a raiz da árvore DOM. Você pode reduzir essa proliferação ascendente de eventos com o atributo stopPropagation de um evento, conforme mostrado aqui:

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

Usar um EventCallback para manipular eventos entre componentes

Uma página Blazor pode conter um ou mais componentes Blazor, e os componentes podem ser aninhados em uma relação pai-filho. Um evento em um componente filho pode disparar um método de manipulador de eventos em um componente pai usando um EventCallback. Um retorno de chamada faz referência a um método no componente pai. O componente filho pode executar o método invocando o retorno de chamada. Esse mecanismo é semelhante ao uso de um delegate para referenciar um método em um aplicativo C#.

Um retorno de chamada pode usar um único parâmetro. EventCallback é um tipo genérico. O parâmetro tipo especifica o tipo do argumento passado para o retorno de chamada.

Por exemplo, considere o cenário a seguir. Você deseja criar um componente chamado TextDisplay que permite ao usuário inserir uma cadeia de caracteres de entrada e transformar essa cadeia de caracteres de alguma forma; você pode querer convertê-la para maiúsculas, minúsculas, letras mistas, filtrar caracteres a partir dela, ou realizar algum outro tipo de transformação. No entanto, ao escrever o código para o componente TextDisplay, você não sabe qual será o processo de transformação e prefere adiar essa operação para outro componente. O código a seguir mostra o componente TextDisplay. Ele fornece a cadeia de caracteres de entrada na forma de um elemento <input> em que o usuário pode inserir um valor de texto.

@* 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;
    }
}

O componente TextDisplay usa um objeto EventCallback chamado OnKeyPressCallback. O código no método HandleKeypress invoca o retorno de chamada. O manipulador de eventos @onkeypress é executado sempre que uma tecla é pressionada e chama o método HandleKeypress. O método HandleKeypress cria um objeto KeyTransformation usando a chave pressionada pelo usuário e passa esse objeto como o parâmetro para o retorno de chamada. O tipo KeyTransformation é uma classe simples com dois campos:

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

O campo key contém o valor inserido pelo usuário e o campo TransformedKey conterá o valor transformado da chave quando ela tiver sido processada.

Neste exemplo, o objeto EventCallback é um parâmetro de componente e seu valor é fornecido quando o componente é criado. Essa ação é executada por outro componente, chamado 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();
    }
}

O componente TextTransformer é uma página Blazor que cria uma instância do componente TextDisplay. Ele preenche o parâmetro OnKeypressCallback com uma referência ao método TransformText na seção de código da página. O método TransformText assume o objeto KeyTransformation fornecido como seu argumento e preenche a propriedade TransformedKey com o valor encontrado na propriedade Key convertida em letras maiúsculas. O diagrama a seguir ilustra o fluxo de controle quando os usuários inserem um valor no campo <entrada> no componente TextDisplay exibido pela página TextTransformer:

Diagrama do fluxo de controle com um EventCallback em um componente filho.

O melhor dessa abordagem é que você pode usar o componente TextDisplay com qualquer página que fornece um retorno de chamada para o parâmetro OnKeypressCallback. Há uma separação completa entre a exibição e o processamento. Você pode alternar o método TransformText para qualquer outro retorno de chamada que corresponde à assinatura do parâmetro EventCallback no componente TextDisplay.

Você poderá transferir um retorno de chamada diretamente para um manipulador de eventos sem usar um método intermediário se o retorno de chamada for digitado com o parâmetro EventArgs apropriado. Por exemplo, um componente filho pode referenciar um retorno de chamada que pode manipular eventos do mouse como @onclick assim:

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

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

Nesse caso, o EventCallback aceita um parâmetro de tipo MouseEventArgs, para que ele possa ser especificado como o manipulador do evento @onclick.

Verificar seu conhecimento

1.

Qual recurso você deve usar para passar eventos entre componentes Blazor?

2.

Qual método é usado para substituir a ação padrão em um elemento HTML DOM?