Anexar código C# a eventos DOM com manipuladores de eventos Blazor
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:
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.
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
:
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
.