Koppla C#-kod till DOM-händelser med Blazor-händelsehanterare
- 5 minuter
De flesta HTML-element exponerar händelser som utlöses när något betydande händer. När en sida har lästs in klickar användaren till exempel på en knapp eller så ändras innehållet i ett HTML-element. En app kan hantera en händelse på flera sätt:
- Appen kan ignorera händelsen.
- Appen kan köra en händelsehanterare som skrivits i JavaScript för att bearbeta händelsen.
- Appen kan köra en Blazor-händelsehanterare som skrivits i C# för att bearbeta händelsen.
I den här lektionen får du en detaljerad titt på det tredje alternativet. hur du skapar en Blazor-händelsehanterare i C# för att bearbeta en händelse.
Hantera en händelse med Blazor och C#
Varje element i HTML-koden för en Blazor-app stöder många händelser. De flesta av dessa händelser motsvarar de DOM-händelser som är tillgängliga i vanliga webbprogram, men du kan också skapa användardefinierade händelser som utlöses genom att skriva kod. Om du vill samla in en händelse med Blazor skriver du en C#-metod som hanterar händelsen och binder sedan händelsen till metoden med ett Blazor-direktiv. För en DOM-händelse delar Blazor-direktivet samma namn som motsvarande HTML-händelse, till exempel @onkeydown eller @onfocus. Exempelappen som genereras med blazor-serverappen innehåller till exempel följande kod på sidan Counter.razor . Den här sidan visar en knapp. När användaren väljer knappen @onclick utlöser händelsen metoden IncrementCount som ökar en räknare som anger hur många gånger knappen klickades. Elementet <p> på sidan visar värdet för räknarvariabeln:
@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++;
}
}
Många händelsehanterarmetoder använder en parameter som ger extra kontextuell information. Den här parametern kallas för en EventArgs parameter. Händelsen skickar till exempel @onclick information om vilken knapp användaren klickade på, eller om de tryckte på en knapp, till exempel Ctrl eller Alt samtidigt som de klickade på knappen, i en MouseEventArgs parameter. Du behöver inte ange den här parametern när du anropar metoden. Blazor-körningen lägger till den automatiskt. Du kan köra frågor mot den här parametern i händelsehanteraren. Följande kod ökar räknaren som visas i föregående exempel med fem om användaren trycker på Ctrl-tangenten samtidigt som du klickar på knappen:
@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++;
}
}
}
Andra händelser ger olika EventArgs parametrar. Händelsen skickar till exempel @onkeypress en KeyboardEventArgs parameter som anger vilken nyckel användaren tryckte på. Om du inte behöver den här informationen för någon av DOM-händelserna kan du utelämna parametern EventArgs från händelsehanteringsmetoden.
Förstå händelsehantering i JavaScript jämfört med händelsehantering med Blazor
Ett traditionellt webbprogram använder JavaScript för att samla in och bearbeta händelser. Du skapar en funktion som en del av ett HTML-skriptelement <> och ordnar sedan att anropa den funktionen när händelsen inträffar. Som jämförelse med föregående Blazor-exempel visar följande kod ett fragment från en HTML-sida som ökar ett värde och visar resultatet när användaren väljer knappen Klicka på mig . Koden använder jQuery-biblioteket för att komma åt 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>
Förutom de syntaktiska skillnaderna i de två versionerna av händelsehanteraren bör du notera följande funktionella skillnader:
- JavaScript prefixar inte namnet på händelsen med ett
@-tecken. Det är inte ett Blazor-direktiv. - I Blazor-koden anger du namnet på händelsehanteringsmetoden när du kopplar den till en händelse. I JavaScript skriver du en instruktion som anropar händelsehanteringsmetoden. du anger runda hakparenteser och eventuella parametrar som krävs.
- Viktigast av allt är att JavaScript-händelsehanteraren körs i webbläsaren på klienten. Om du skapar en Blazor Server-app körs Blazor-händelsehanteraren på servern och uppdaterar bara webbläsaren med ändringar som gjorts i användargränssnittet när händelsehanteraren är klar. Dessutom gör Blazor-mekanismen det möjligt för en händelsehanterare att komma åt statiska data som delas mellan sessioner. JavaScript-modellen gör det inte. Men att hantera vissa händelser som inträffar ofta, till exempel
@onmousemovekan göra att användargränssnittet blir trögt eftersom de kräver en tur-och-retur-nätverksresa till servern. Du kanske föredrar att hantera händelser som dessa i webbläsaren med hjälp av JavaScript.
Important
Du kan ändra DOM med hjälp av JavaScript-kod från en händelsehanterare och med hjälp av C# Blazor-kod. Blazor har dock en egen kopia av DOM, som används för att uppdatera användargränssnittet vid behov. Om du använder JavaScript- och Blazor-kod för att ändra samma element i DOM riskerar du att skada DOM. Du kan också äventyra sekretessen och säkerheten för data i webbappen.
Hantera händelser asynkront
Som standard är Blazor-händelsehanterare synkrona. Om en händelsehanterare utför en potentiellt tidskrävande åtgärd, till exempel att anropa en webbtjänst, blockeras tråden som händelsehanteraren kör på tills åtgärden har slutförts. Den här situationen kan leda till dåliga svar i användargränssnittet. För att bekämpa det här problemet kan du ange en händelsehanterarmetod som asynkron. Använd nyckelordet C# async . Metoden måste returnera ett Task objekt. Du kan sedan använda operatorn await i händelsehanterarmetoden för att initiera tidskrävande uppgifter i en separat tråd och frigöra den aktuella tråden för annat arbete. När en tidskrävande uppgift är klar återupptas händelsehanteraren. I följande exempel visas en händelsehanterare som kör en tidskrävande metod asynkront:
<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
}
}
Note
Detaljerad information om hur du skapar asynkrona metoder i C# finns i Asynkrona programmeringsscenarier.
Använd en händelse för att ställa in fokus på ett DOM-element
På en HTML-sida kan användaren ta en flik mellan element och fokus flyttas naturligt i den ordning html-elementen visas på sidan. Vid vissa tillfällen kan du behöva åsidosätta den här sekvensen och tvinga användaren att besöka ett visst element.
Det enklaste sättet att utföra den här uppgiften är att använda FocusAsync metoden. Den här metoden är en instansmetod för ett ElementReference objekt. Bör ElementReference referera till det objekt som du vill ange fokus för. Du anger en elementreferens med @ref attributet och skapar ett C#-objekt med samma namn i koden.
I följande exempel @onclick anger händelsehanteraren för <knappelementet> fokus på <indataelementet> . Händelsehanteraren @onfocus för <indataelementet> visar meddelandet "Mottaget fokus" när elementet får fokus. Indataelementet <> refereras via variabeln InputField i koden:
<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";
}
Följande bild visar resultatet när användaren väljer knappen:
Note
En app bör endast rikta fokus mot en specifik kontroll av en viss anledning, till exempel för att be användaren att ändra indata efter ett fel. Använd inte fokusering för att tvinga användaren att navigera genom elementen på en sida i en fast ordning. Den här designen kan vara frustrerande för den användare som kanske vill gå tillbaka till elementen för att ändra sina indata.
Skriva infogade händelsehanterare
C# stöder lambda-uttryck. Med ett lambda-uttryck kan du skapa en anonym funktion. Ett lambda-uttryck är användbart om du har en enkel händelsehanterare som du inte behöver återanvända någon annanstans på en sida eller komponent. I det första klickantalsexemplet som visas i början av den här lektionen IncrementCount kan du ta bort metoden och i stället ersätta metodanropet med ett lambda-uttryck som utför samma uppgift:
@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;
}
Note
Mer information om hur lambda-uttryck fungerar finns i Lambda-uttryck och anonyma funktioner.
Den här metoden är också användbar om du vill ange andra argument för en händelsehanteringsmetod. I följande exempel tar metoden HandleClick en MouseEventArgs parameter på samma sätt som en vanlig klickhändelsehanterare, men den accepterar även en strängparameter. Metoden bearbetar klickhändelsen som tidigare, men visar även meddelandet om användaren trycker på Ctrl-tangenten . Lambda-uttrycket anropar HandleCLick metoden och skickar in parametern MouseEventArgs (mouseEvent) och en sträng.
@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++;
}
}
}
Note
I det här exemplet används JavaScript-funktionen alert för att visa meddelandet eftersom det inte finns någon motsvarande funktion i Blazor. Du använder JavaScript-interop för att anropa JavaScript från Blazor-kod. Informationen om den här tekniken är föremål för en separat modul.
Åsidosätt standard-DOM-åtgärder för händelser
Flera DOM-händelser har standardåtgärder som körs när händelsen inträffar, oavsett om det finns en händelsehanterare tillgänglig för den händelsen. Ett @onkeypressindataelement<-händelse visar alltid till exempel> det tecken som motsvarar den tangent som användaren trycker på och hanterar sedan tangenttryckningen. I nästa exempel @onkeypress används händelsen för att konvertera användarens indata till versaler. Om användaren dessutom skriver ett @ tecken visar händelsehanteraren en avisering:
<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();
}
}
}
Om du kör den här koden och trycker på tangenten @ visas aviseringen, men @ tecknet läggs också till i indata. Tillägget av @ tecknet är standardåtgärden för händelsen.
Om du vill förhindra att det här tecknet visas i indatarutan kan du åsidosätta standardåtgärden preventDefault med händelsens attribut, så här:
<input value=@data @onkeypress="ProcessKeyPress" @onkeypress:preventDefault />
Händelsen utlöses fortfarande, men endast de åtgärder som definierats av händelsehanteraren utförs.
Vissa händelser i ett underordnat element i DOM kan utlösa händelser i sina överordnade element. I följande exempel <innehåller div-elementet> en @onclick händelsehanterare. Knappen <> i <div> har en egen @onclick händelsehanterare. Dessutom <innehåller div> ett <indataelement> :
<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
}
}
När appen körs, om användaren klickar på något element (eller tomt utrymme) i det område som upptas av <div-elementet> , körs metoden HandleDivClick och visar ett meddelande. Om användaren väljer Click me knappen IncrementCount körs metoden följt av HandleDivClick. @onclick Händelsen sprids upp i DOM-trädet.
<Om div> var en del av ett annat element som också hanterade @onclick händelsen skulle händelsehanteraren också köras, och så vidare, till roten i DOM-trädet. Du kan minska detta uppåtgående spridning av händelser med stopPropagation attributet för en händelse, som du ser här:
<div @onclick="HandleDivClick">
<button class="btn btn-primary" @onclick="IncrementCount" @onclick:stopPropagation>Click me</button>
<!-- Omitted for brevity -->
</div>
Använda en EventCallback för att hantera händelser mellan komponenter
En Blazor-sida kan innehålla en eller flera Blazor-komponenter och komponenter kan kapslas i en överordnad-underordnad relation. En händelse i en underordnad komponent kan utlösa en händelsehanterarmetod i en överordnad komponent med hjälp av en EventCallback. Ett återanrop refererar till en metod i den överordnade komponenten. Den underordnade komponenten kan köra metoden genom att anropa återanropet. Den här mekanismen liknar att använda en delegate för att referera till en metod i ett C#-program.
Ett återanrop kan ta en enskild parameter.
EventCallback är en allmän typ. Typparametern anger vilken typ av argument som skickas till återanropet.
Tänk till exempel på följande scenario. Du vill skapa en komponent med namnet TextDisplay som gör att användaren kan ange en indatasträng och transformera strängen på något sätt. Du kanske vill konvertera den till versaler, gemener, blandade skiftlägen, filtrera tecken från den eller utföra någon annan typ av transformering. Men när du skriver koden för komponenten TextDisplay vet du inte vad omvandlingsprocessen kommer att bli. I stället vill du skjuta upp den här åtgärden till en annan komponent. Följande kod visar komponenten TextDisplay . Den innehåller indatasträngen i form av ett <indataelement> som gör att användaren kan ange ett textvärde.
@* 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;
}
}
Komponenten TextDisplay använder ett EventCallback objekt med namnet OnKeyPressCallback. Koden i HandleKeypress metoden anropar återanropet. Händelsehanteraren @onkeypress körs varje gång en nyckel trycks in och anropar HandleKeypress metoden. Metoden HandleKeypress skapar ett KeyTransformation objekt med den nyckel som användaren tryckte på och skickar det här objektet som parameter till motringningen. Typen KeyTransformation är en enkel klass med två fält:
namespace WebApplication.Data
{
public class KeyTransformation
{
public string Key { get; set; }
public string TransformedKey { get; set; }
}
}
Fältet key innehåller det värde som angetts av användaren och fältet TransformedKey innehåller nyckelns transformerade värde efter bearbetning.
I det här exemplet EventCallback är objektet en komponentparameter och dess värde anges när komponenten skapas. Komponenten med namnet TextTransformer utför den här åtgärden:
@page "/texttransformer"
@using WebApplication.Data;
<h1>Text Transformer - Parent</h1>
<TextDisplay OnKeypressCallback="@TransformText" />
@code {
private void TransformText(KeyTransformation k)
{
k.TransformedKey = k.Key.ToUpper();
}
}
Komponenten TextTransformer är en Blazor-sida som skapar en instans av komponenten TextDisplay . Den fyller parametern OnKeypressCallback med en referens till TransformText metoden i kodavsnittet på sidan. Metoden TransformText tar det KeyTransformation angivna objektet som argument och fyller i TransformedKey egenskapen med värdet som finns i Key egenskapen konverterat till versaler. Följande diagram illustrerar kontrollflödet när en användare anger ett värde i <indatafältet> i komponenten TextDisplay som visas på TextTransformer sidan:
Det fina med den här metoden är att du kan använda komponenten TextDisplay med valfri sida som ger ett återanrop för parametern OnKeypressCallback . Det finns en fullständig separation mellan visningen och bearbetningen. Du kan växla metoden TransformText för andra återanrop som matchar parameterns EventCallback signatur i komponenten TextDisplay .
Du kan koppla ett återanrop till en händelsehanterare direkt utan att använda en mellanliggande metod om återanropet skrivs med lämplig EventArgs parameter. En underordnad komponent kan till exempel referera till ett återanrop som kan hantera mushändelser som @onclick den här:
<button @onclick="OnClickCallback">
Click me!
</button>
@code {
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}
I det här fallet EventCallback tar den en MouseEventArgs typparameter, så att den kan anges som hanterare för @onclick händelsen.