ASP.NET Core Blazor 事件处理

备注

此版本不是本文的最新版本。 有关当前版本,请参阅本文.NET 9 版本。

警告

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本,请参阅本文.NET 9 版本。

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

有关当前版本,请参阅本文.NET 9 版本。

本文介绍 Blazor 的事件处理功能,包括事件参数类型、事件回调和管理默认浏览器事件。

委托事件处理程序

使用 @on{DOM EVENT}="{DELEGATE}"Razor 语法在 Razor 组件标记中指定委托事件处理程序:

  • {DOM EVENT} 占位符是 DOM 事件(例如,click)。
  • {DELEGATE} 占位符是 C# 委托事件处理程序。

对于事件处理:

  • Blazor Web App 中的委托事件处理程序仅在采用交互式呈现模式的组件中调用。 本文中的示例假定应用在应用的根组件(通常是 App 组件)中全局采用交互式呈现模式。 有关详细信息,请参阅 ASP.NET Core Blazor 呈现模式
  • 支持返回 Task 的异步委托事件处理程序。
  • 委托事件处理程序会自动触发 UI 呈现,因此无需手动调用 StateHasChanged
  • 记录异常。
  • 支持返回 Task 的异步委托事件处理程序。
  • 委托事件处理程序会自动触发 UI 呈现,因此无需手动调用 StateHasChanged
  • 记录异常。

下面的代码:

  • 在 UI 中选择该按钮时,调用 UpdateHeading 方法。
  • UI 中的该复选框发生更改时,调用 CheckChanged 方法。

EventHandler1.razor:

@page "/event-handler-1"

<PageTitle>Event Handler 1</PageTitle>

<h1>Event Handler Example 1</h1>

<h2>@headingValue</h2>

<p>
    <button @onclick="UpdateHeading">
        Update heading
    </button>
</p>

<p>
    <label>
        <input type="checkbox" @onchange="CheckChanged" />
        @checkedMessage
    </label>
</p>

@code {
    private string headingValue = "Initial heading";
    private string checkedMessage = "Not changed yet";

    private void UpdateHeading() => headingValue = $"New heading ({DateTime.Now})";

    private void CheckChanged() => checkedMessage = $"Last change {DateTime.Now}";
}

EventHandler1.razor:

@page "/event-handler-1"

<PageTitle>Event Handler 1</PageTitle>

<h1>Event Handler Example 1</h1>

<h2>@headingValue</h2>

<p>
    <button @onclick="UpdateHeading">
        Update heading
    </button>
</p>

<p>
    <label>
        <input type="checkbox" @onchange="CheckChanged" />
        @checkedMessage
    </label>
</p>

@code {
    private string headingValue = "Initial heading";
    private string checkedMessage = "Not changed yet";

    private void UpdateHeading() => headingValue = $"New heading ({DateTime.Now})";

    private void CheckChanged() => checkedMessage = $"Last change {DateTime.Now}";
}

EventHandlerExample1.razor:

@page "/event-handler-1"

<h1>@headingValue</h1>

<p>
    <button @onclick="UpdateHeading">
        Update heading
    </button>
</p>

<p>
    <label>
        <input type="checkbox" @onchange="CheckChanged" />
        @checkedMessage
    </label>
</p>

@code {
    private string headingValue = "Initial heading";
    private string checkedMessage = "Not changed yet";

    private void UpdateHeading()
    {
        headingValue = $"New heading ({DateTime.Now})";
    }

    private void CheckChanged()
    {
        checkedMessage = $"Last changed at {DateTime.Now}";
    }
}

EventHandlerExample1.razor:

@page "/event-handler-1"

<h1>@headingValue</h1>

<p>
    <button @onclick="UpdateHeading">
        Update heading
    </button>
</p>

<p>
    <label>
        <input type="checkbox" @onchange="CheckChanged" />
        @checkedMessage
    </label>
</p>

@code {
    private string headingValue = "Initial heading";
    private string checkedMessage = "Not changed yet";

    private void UpdateHeading()
    {
        headingValue = $"New heading ({DateTime.Now})";
    }

    private void CheckChanged()
    {
        checkedMessage = $"Last changed at {DateTime.Now}";
    }
}

EventHandlerExample1.razor:

@page "/event-handler-1"

<h1>@headingValue</h1>

<p>
    <button @onclick="UpdateHeading">
        Update heading
    </button>
</p>

<p>
    <label>
        <input type="checkbox" @onchange="CheckChanged" />
        @checkedMessage
    </label>
</p>

@code {
    private string headingValue = "Initial heading";
    private string checkedMessage = "Not changed yet";

    private void UpdateHeading()
    {
        headingValue = $"New heading ({DateTime.Now})";
    }

    private void CheckChanged()
    {
        checkedMessage = $"Last changed at {DateTime.Now}";
    }
}

EventHandlerExample1.razor:

@page "/event-handler-1"

<h1>@headingValue</h1>

<p>
    <button @onclick="UpdateHeading">
        Update heading
    </button>
</p>

<p>
    <label>
        <input type="checkbox" @onchange="CheckChanged" />
        @checkedMessage
    </label>
</p>

@code {
    private string headingValue = "Initial heading";
    private string checkedMessage = "Not changed yet";

    private void UpdateHeading()
    {
        headingValue = $"New heading ({DateTime.Now})";
    }

    private void CheckChanged()
    {
        checkedMessage = $"Last changed at {DateTime.Now}";
    }
}

在以下示例中,UpdateHeading

  • 在选择该按钮时以异步方式调用。
  • 在更新标题之前等待两秒。

EventHandler2.razor:

@page "/event-handler-2"

<PageTitle>Event Handler 2</PageTitle>

<h1>Event Handler Example 2</h1>

<h2>@headingValue</h2>

<p>
    <button @onclick="UpdateHeading">
        Update heading
    </button>
</p>

@code {
    private string headingValue = "Initial heading";

    private async Task UpdateHeading()
    {
        await Task.Delay(2000);

        headingValue = $"New heading ({DateTime.Now})";
    }
}

EventHandler2.razor:

@page "/event-handler-2"

<PageTitle>Event Handler 2</PageTitle>

<h1>Event Handler Example 2</h1>

<h2>@headingValue</h2>

<p>
    <button @onclick="UpdateHeading">
        Update heading
    </button>
</p>

@code {
    private string headingValue = "Initial heading";

    private async Task UpdateHeading()
    {
        await Task.Delay(2000);

        headingValue = $"New heading ({DateTime.Now})";
    }
}

EventHandlerExample2.razor:

@page "/event-handler-2"

<h1>@headingValue</h1>

<p>
    <button @onclick="UpdateHeading">
        Update heading
    </button>
</p>

@code {
    private string headingValue = "Initial heading";

    private async Task UpdateHeading()
    {
        await Task.Delay(2000);

        headingValue = $"New heading ({DateTime.Now})";
    }
}

EventHandlerExample2.razor:

@page "/event-handler-2"

<h1>@headingValue</h1>

<p>
    <button @onclick="UpdateHeading">
        Update heading
    </button>
</p>

@code {
    private string headingValue = "Initial heading";

    private async Task UpdateHeading()
    {
        await Task.Delay(2000);

        headingValue = $"New heading ({DateTime.Now})";
    }
}

EventHandlerExample2.razor:

@page "/event-handler-2"

<h1>@headingValue</h1>

<p>
    <button @onclick="UpdateHeading">
        Update heading
    </button>
</p>

@code {
    private string headingValue = "Initial heading";

    private async Task UpdateHeading()
    {
        await Task.Delay(2000);

        headingValue = $"New heading ({DateTime.Now})";
    }
}

EventHandlerExample2.razor:

@page "/event-handler-2"

<h1>@headingValue</h1>

<p>
    <button @onclick="UpdateHeading">
        Update heading
    </button>
</p>

@code {
    private string headingValue = "Initial heading";

    private async Task UpdateHeading()
    {
        await Task.Delay(2000);

        headingValue = $"New heading ({DateTime.Now})";
    }
}

内置事件参数

对于支持事件参数类型的事件,仅在方法使用事件类型时,才需要在事件方法定义中指定事件参数。 在下面的示例中,ReportPointerLocation 方法使用了 MouseEventArgs 来设置消息文本,以便在用户选择 UI 中的按钮时报告鼠标坐标。

EventHandler3.razor:

@page "/event-handler-3"

<PageTitle>Event Handler 3</PageTitle>

<h1>Event Handler Example 3</h1>

@for (var i = 0; i < 4; i++)
{
    <p>
        <button @onclick="ReportPointerLocation">
            Where's my mouse pointer for this button?
        </button>
    </p>
}

<p>@mousePointerMessage</p>

@code {
    private string? mousePointerMessage;

    private void ReportPointerLocation(MouseEventArgs e) => 
        mousePointerMessage = $"Mouse coordinates: {e.ScreenX}:{e.ScreenY}";
}

EventHandler3.razor:

@page "/event-handler-3"

<PageTitle>Event Handler 3</PageTitle>

<h1>Event Handler Example 3</h1>

@for (var i = 0; i < 4; i++)
{
    <p>
        <button @onclick="ReportPointerLocation">
            Where's my mouse pointer for this button?
        </button>
    </p>
}

<p>@mousePointerMessage</p>

@code {
    private string? mousePointerMessage;

    private void ReportPointerLocation(MouseEventArgs e) => 
        mousePointerMessage = $"Mouse coordinates: {e.ScreenX}:{e.ScreenY}";
}

EventHandlerExample3.razor:

@page "/event-handler-example-3"

@for (var i = 0; i < 4; i++)
{
    <p>
        <button @onclick="ReportPointerLocation">
            Where's my mouse pointer for this button?
        </button>
    </p>
}

<p>@mousePointerMessage</p>

@code {
    private string? mousePointerMessage;

    private void ReportPointerLocation(MouseEventArgs e)
    {
        mousePointerMessage = $"Mouse coordinates: {e.ScreenX}:{e.ScreenY}";
    }
}

EventHandlerExample3.razor:

@page "/event-handler-example-3"

@for (var i = 0; i < 4; i++)
{
    <p>
        <button @onclick="ReportPointerLocation">
            Where's my mouse pointer for this button?
        </button>
    </p>
}

<p>@mousePointerMessage</p>

@code {
    private string? mousePointerMessage;

    private void ReportPointerLocation(MouseEventArgs e)
    {
        mousePointerMessage = $"Mouse coordinates: {e.ScreenX}:{e.ScreenY}";
    }
}

EventHandlerExample3.razor:

@page "/event-handler-example-3"

@for (var i = 0; i < 4; i++)
{
    <p>
        <button @onclick="ReportPointerLocation">
            Where's my mouse pointer for this button?
        </button>
    </p>
}

<p>@mousePointerMessage</p>

@code {
    private string mousePointerMessage;

    private void ReportPointerLocation(MouseEventArgs e)
    {
        mousePointerMessage = $"Mouse coordinates: {e.ScreenX}:{e.ScreenY}";
    }
}

EventHandlerExample3.razor:

@page "/event-handler-example-3"

@for (var i = 0; i < 4; i++)
{
    <p>
        <button @onclick="ReportPointerLocation">
            Where's my mouse pointer for this button?
        </button>
    </p>
}

<p>@mousePointerMessage</p>

@code {
    private string mousePointerMessage;

    private void ReportPointerLocation(MouseEventArgs e)
    {
        mousePointerMessage = $"Mouse coordinates: {e.ScreenX}:{e.ScreenY}";
    }
}

支持的 EventArgs 显示在下表中。

事件 DOM 说明
剪贴板 ClipboardEventArgs
拖动 DragEventArgs DataTransferDataTransferItem 保留拖动的项数据。

使用 JS 互操作HTML 拖放 API 在 Blazor 应用中实现拖放。
错误 ErrorEventArgs
事件 EventArgs EventHandlers 保留属性,以配置事件名称和事件参数类型之间的映射。
焦点 FocusEventArgs 不包含对 relatedTarget 的支持。
输入 ChangeEventArgs
Keyboard KeyboardEventArgs
鼠标 MouseEventArgs
鼠标指针 PointerEventArgs
鼠标滚轮 WheelEventArgs
进度 ProgressEventArgs
触摸 TouchEventArgs TouchPoint 表示触控敏感型设备上的单个接触点。

有关更多信息,请参见以下资源:

自定义事件参数

Blazor 支持自定义事件参数。借助这些参数,你可以将任意数据通过自定义事件传递给 .NET 事件处理程序。

常规配置

通常,通过以下步骤启用具有自定义事件参数的自定义事件。

在 JavaScript 中,定义一个函数,用于通过源事件生成自定义事件参数对象:

function eventArgsCreator(event) { 
  return {
    customProperty1: 'any value for property 1',
    customProperty2: event.srcElement.id
  };
}

event 参数是 DOM 事件(MDN 文档)

JavaScript 初始值设定项中将自定义事件注册到前面的处理程序。 为 browserEventName 提供适当的浏览器事件名称,在本部分所示的示例中则为针对 UI 中的按钮选择的 click

wwwroot/{PACKAGE ID/ASSEMBLY NAME}.lib.module.js{PACKAGE ID/ASSEMBLY NAME} 占位符是应用的包 ID 或程序集名称):

对于 Blazor Web App:

export function afterWebStarted(blazor) {
  blazor.registerCustomEventType('customevent', {
    browserEventName: 'click',
    createEventArgs: eventArgsCreator
  });
}

对于 Blazor Server 或 Blazor WebAssembly 应用:

export function afterStarted(blazor) {
  blazor.registerCustomEventType('customevent', {
    browserEventName: 'click',
    createEventArgs: eventArgsCreator
  });
}

每个事件仅在脚本中调用 registerCustomEventType 一次。

要调用 registerCustomEventType,请使用 Blazor 启动事件提供的 blazor 参数(小写的 b)。 尽管在使用 Blazor 对象(大写的 B)时注册有效,但首选方法是使用参数。

在前面示例中的自定义事件名称 customevent 必须与保留的事件名称 Blazor 匹配。 可以在Blazor 框架引用源(请参阅对 registerBuiltInEventType 函数的调用)中找到保留名称。

备注

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

定义事件参数的类:

namespace BlazorSample.CustomEvents;

public class CustomEventArgs : EventArgs
{
    public string? CustomProperty1 {get; set;}
    public string? CustomProperty2 {get; set;}
}

通过为自定义事件添加 [EventHandler] 特性注释,连接自定义事件和事件参数:

  • 为了使编译器找到 [EventHandler] 类,必须将它放置在 C# 类文件 (.cs) 中,使它成为普通的顶级类。
  • 将类标记为 public
  • 此类不需要成员。
  • 类必须称为“EventHandlers”,才能被 Razor 编译器找到。
  • 将类置于特定于应用的命名空间下。
  • 将命名空间导入到使用事件的 Razor 组件 (.razor) 中。
using Microsoft.AspNetCore.Components;

namespace BlazorSample.CustomEvents;

[EventHandler("oncustomevent", typeof(CustomEventArgs),
    enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}

在一个或多个 HTML 元素上注册此事件处理程序。 在委托处理程序方法中访问从 JavaScript 传入的数据:

@using BlazorSample.CustomEvents

<button id="buttonId" @oncustomevent="HandleCustomEvent">Handle</button>

@if (!string.IsNullOrEmpty(propVal1) && !string.IsNullOrEmpty(propVal2))
{
    <ul>
        <li>propVal1: @propVal1</li>
        <li>propVal2: @propVal2</li>
    </ul>
}

@code
{
    private string? propVal1;
    private string? propVal2;

    private void HandleCustomEvent(CustomEventArgs eventArgs)
    {
        propVal1 = eventArgs.CustomProperty1;
        propVal2 = eventArgs.CustomProperty2;
    }
}

如果 IntelliSense 无法识别 @oncustomevent 属性,请确保组件或 _Imports.razor 文件包含拥有 EventHandler 类的命名空间的 @using 语句。

每当在 DOM 上触发此自定义事件时,都会使用从 JavaScript 传入的数据调用此事件处理程序。

如果正在尝试触发自定义事件,则必须将 bubbles 的值设置为 true 以启用它。 否则,事件无法到达 Blazor 处理程序,进而无法受到处理并进入 C# 自定义 [EventHandler] 特性类。 有关详细信息,请参阅 MDN Web 文档:事件冒泡

自定义剪贴板粘贴事件示例

下面的示例接收包含粘贴时间和用户所粘贴文本的自定义剪贴板粘贴事件。

声明事件的自定义名称 (oncustompaste),并声明一个 .NET 类 (CustomPasteEventArgs) 以保存此事件的事件参数:

CustomEvents.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.CustomEvents;

[EventHandler("oncustompaste", typeof(CustomPasteEventArgs), 
    enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}

public class CustomPasteEventArgs : EventArgs
{
    public DateTime EventTimestamp { get; set; }
    public string? PastedData { get; set; }
}

添加 JavaScript 代码,以便在 JavaScript 初始值设定项中使用前面的处理程序为 EventArgs 子类提供数据。 下面的示例仅处理粘贴文本,但你可以使用任意 JavaScript API 来处理用户粘贴的其他类型的数据(如图像)。

wwwroot/{PACKAGE ID/ASSEMBLY NAME}.lib.module.js:

对于 Blazor Web App:

export function afterWebStarted(blazor) {
  blazor.registerCustomEventType('custompaste', {
    browserEventName: 'paste',
    createEventArgs: event => {
      return {
        eventTimestamp: new Date(),
        pastedData: event.clipboardData.getData('text')
      };
    }
  });
}

对于 Blazor Server 或 Blazor WebAssembly 应用:

export function afterStarted(blazor) {
  blazor.registerCustomEventType('custompaste', {
    browserEventName: 'paste',
    createEventArgs: event => {
      return {
        eventTimestamp: new Date(),
        pastedData: event.clipboardData.getData('text')
      };
    }
  });
}

在前面的示例中,文件名的 {PACKAGE ID/ASSEMBLY NAME} 占位符表示应用的包 ID 或程序集名称。

备注

要调用 registerCustomEventType,请使用 Blazor 启动事件提供的 blazor 参数(小写的 b)。 尽管在使用 Blazor 对象(大写的 B)时注册有效,但首选方法是使用参数。

前面的代码会在本机 paste 事件发生时告诉浏览器:

  • 引发 custompaste 事件。
  • 使用所述的自定义逻辑提供事件参数数据:

.NET 和 JavaScript 的事件名称约定有所不同:

  • 在 .NET 中,事件名称带有前缀“on”。
  • 在 JavaScript 中,事件名称不带前缀。

在 Razor 组件中,将自定义处理程序附加到元素中。

CustomPasteArguments.razor:

@page "/custom-paste-arguments"
@using BlazorSample.CustomEvents

<label>
    Try pasting into the following text box:
    <input @oncustompaste="HandleCustomPaste" />
</label>

<p>
    @message
</p>

@code {
    private string? message;

    private void HandleCustomPaste(CustomPasteEventArgs eventArgs)
    {
        message = $"At {eventArgs.EventTimestamp.ToShortTimeString()}, " +
            $"you pasted: {eventArgs.PastedData}";
    }
}

Lambda 表达式

支持 Lambda 表达式作为委托事件处理程序。

EventHandler4.razor:

@page "/event-handler-4"

<PageTitle>Event Handler 4</PageTitle>

<h1>Event Handler Example 4</h1>

<h2>@heading</h2>

<p>
    <button @onclick="@(e => heading = "New heading!!!")">
        Update heading
    </button>
</p>

@code {
    private string heading = "Initial heading";
}

EventHandler4.razor:

@page "/event-handler-4"

<PageTitle>Event Handler 4</PageTitle>

<h1>Event Handler Example 4</h1>

<h2>@heading</h2>

<p>
    <button @onclick="@(e => heading = "New heading!!!")">
        Update heading
    </button>
</p>

@code {
    private string heading = "Initial heading";
}

EventHandlerExample4.razor:

@page "/event-handler-example-4"

<h1>@heading</h1>

<p>
    <button @onclick="@(e => heading = "New heading!!!")">
        Update heading
    </button>
</p>

@code {
    private string heading = "Initial heading";
}

EventHandlerExample4.razor:

@page "/event-handler-example-4"

<h1>@heading</h1>

<p>
    <button @onclick="@(e => heading = "New heading!!!")">
        Update heading
    </button>
</p>

@code {
    private string heading = "Initial heading";
}

EventHandlerExample4.razor:

@page "/event-handler-example-4"

<h1>@heading</h1>

<p>
    <button @onclick="@(e => heading = "New heading!!!")">
        Update heading
    </button>
</p>

@code {
    private string heading = "Initial heading";
}

EventHandlerExample4.razor:

@page "/event-handler-example-4"

<h1>@heading</h1>

<p>
    <button @onclick="@(e => heading = "New heading!!!")">
        Update heading
    </button>
</p>

@code {
    private string heading = "Initial heading";
}

使用 C# 方法参数关闭附加值通常很方便,例如在循环访问一组元素时。 下面的示例创建三个按钮,每个按钮都调用 UpdateHeading 并传递以下数据:

  • e 中的事件参数 (MouseEventArgs)。
  • buttonNumber 中的按钮编号。

EventHandler5.razor:

@page "/event-handler-5"

<PageTitle>Event Handler 5</PageTitle>

<h1>Event Handler Example 5</h1>

<h2>@heading</h2>

@for (var i = 1; i < 4; i++)
{
    var buttonNumber = i;

    <p>
        <button @onclick="@(e => UpdateHeading(e, buttonNumber))">
            Button #@i
        </button>
    </p>
}

@code {
    private string heading = "Select a button to learn its position";

    private void UpdateHeading(MouseEventArgs e, int buttonNumber) => 
        heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
}

EventHandler5.razor:

@page "/event-handler-5"

<PageTitle>Event Handler 5</PageTitle>

<h1>Event Handler Example 5</h1>

<h2>@heading</h2>

@for (var i = 1; i < 4; i++)
{
    var buttonNumber = i;

    <p>
        <button @onclick="@(e => UpdateHeading(e, buttonNumber))">
            Button #@i
        </button>
    </p>
}

@code {
    private string heading = "Select a button to learn its position";

    private void UpdateHeading(MouseEventArgs e, int buttonNumber) => 
        heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
}

EventHandlerExample5.razor:

@page "/event-handler-example-5"

<h1>@heading</h1>

@for (var i = 1; i < 4; i++)
{
    var buttonNumber = i;

    <p>
        <button @onclick="@(e => UpdateHeading(e, buttonNumber))">
            Button #@i
        </button>
    </p>
}

@code {
    private string heading = "Select a button to learn its position";

    private void UpdateHeading(MouseEventArgs e, int buttonNumber)
    {
        heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
    }
}

EventHandlerExample5.razor:

@page "/event-handler-example-5"

<h1>@heading</h1>

@for (var i = 1; i < 4; i++)
{
    var buttonNumber = i;

    <p>
        <button @onclick="@(e => UpdateHeading(e, buttonNumber))">
            Button #@i
        </button>
    </p>
}

@code {
    private string heading = "Select a button to learn its position";

    private void UpdateHeading(MouseEventArgs e, int buttonNumber)
    {
        heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
    }
}

EventHandlerExample5.razor:

@page "/event-handler-example-5"

<h1>@heading</h1>

@for (var i = 1; i < 4; i++)
{
    var buttonNumber = i;

    <p>
        <button @onclick="@(e => UpdateHeading(e, buttonNumber))">
            Button #@i
        </button>
    </p>
}

@code {
    private string heading = "Select a button to learn its position";

    private void UpdateHeading(MouseEventArgs e, int buttonNumber)
    {
        heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
    }
}

EventHandlerExample5.razor:

@page "/event-handler-example-5"

<h1>@heading</h1>

@for (var i = 1; i < 4; i++)
{
    var buttonNumber = i;

    <p>
        <button @onclick="@(e => UpdateHeading(e, buttonNumber))">
            Button #@i
        </button>
    </p>
}

@code {
    private string heading = "Select a button to learn its position";

    private void UpdateHeading(MouseEventArgs e, int buttonNumber)
    {
        heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
    }
}

在一个循环中创建大量事件委托可能会导致呈现性能不佳。 有关详细信息,请参阅 ASP.NET Core Blazor 性能最佳做法

避免直接在 Lambda 表达式中使用循环变量,如前面的 for 循环示例中的 i。 否则,所有 Lambda 表达式将使用相同的变量,这将导致在所有 Lambda 中使用相同的值。 在局部变量中捕获该变量的值。 在上面的示例中:

  • 将循环变量 i 分配到 buttonNumber
  • buttonNumber 用于 lambda 表达式。

或者,将 foreach 循环与 Enumerable.Range 结合使用,这样就能避开上述问题:

@foreach (var buttonNumber in Enumerable.Range(1, 3))
{
    <p>
        <button @onclick="@(e => UpdateHeading(e, buttonNumber))">
            Button #@buttonNumber
        </button>
    </p>
}

EventCallback

嵌套组件的常见方案是在发生子组件事件时在父组件中执行某个方法。 子组件中发生的 onclick 事件是一个常见用例。 若要跨组件公开事件,请使用 EventCallback。 父组件可向子组件的 EventCallback 分配回调方法。

下面的 Child 组件演示如何设置按钮的 onclick 处理程序以从示例的 ParentComponent 接收 EventCallback 委托。 EventCallback 是用 MouseEventArgs 键入的,这适用于来自外围设备的 onclick 事件。

Child.razor:

<p>
    <button @onclick="OnClickCallback">
        Trigger a Parent component method
    </button>
</p>

@code {
    [Parameter]
    public string? Title { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}
<p>
    <button @onclick="OnClickCallback">
        Trigger a Parent component method
    </button>
</p>

@code {
    [Parameter]
    public string? Title { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}
<p>
    <button @onclick="OnClickCallback">
        Trigger a Parent component method
    </button>
</p>

@code {
    [Parameter]
    public string? Title { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}
<p>
    <button @onclick="OnClickCallback">
        Trigger a Parent component method
    </button>
</p>

@code {
    [Parameter]
    public string? Title { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}
<p>
    <button @onclick="OnClickCallback">
        Trigger a Parent component method
    </button>
</p>

@code {
    [Parameter]
    public string Title { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}
<p>
    <button @onclick="OnClickCallback">
        Trigger a Parent component method
    </button>
</p>

@code {
    [Parameter]
    public string Title { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

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

父组件将子元素 EventCallback<TValue> 的 (OnClickCallback) 设置为其 ShowMessage 方法。

ParentChild.razor:

@page "/parent-child"

<PageTitle>Parent Child</PageTitle>

<h1>Parent Child Example</h1>

<Child Title="Panel Title from Parent" OnClickCallback="ShowMessage">
    Content of the child component is supplied by the parent component.
</Child>

<p>@message</p>

@code {
    private string? message;

    private void ShowMessage(MouseEventArgs e) => 
        message = $"Blaze a new trail with Blazor! ({e.ScreenX}:{e.ScreenY})";
}

ParentChild.razor:

@page "/parent-child"

<PageTitle>Parent Child</PageTitle>

<h1>Parent Child Example</h1>

<Child Title="Panel Title from Parent" OnClickCallback="ShowMessage">
    Content of the child component is supplied by the parent component.
</Child>

<p>@message</p>

@code {
    private string? message;

    private void ShowMessage(MouseEventArgs e) => 
        message = $"Blaze a new trail with Blazor! ({e.ScreenX}:{e.ScreenY})";
}

Parent.razor:

@page "/parent"

<h1>Parent-child example</h1>

<Child Title="Panel Title from Parent" OnClickCallback="ShowMessage">
    Content of the child component is supplied by the parent component.
</Child>

<p>@message</p>

@code {
    private string? message;

    private void ShowMessage(MouseEventArgs e)
    {
        message = $"Blaze a new trail with Blazor! ({e.ScreenX}:{e.ScreenY})";
    }
}

Parent.razor:

@page "/parent"

<h1>Parent-child example</h1>

<Child Title="Panel Title from Parent" OnClickCallback="ShowMessage">
    Content of the child component is supplied by the parent component.
</Child>

<p>@message</p>

@code {
    private string? message;

    private void ShowMessage(MouseEventArgs e)
    {
        message = $"Blaze a new trail with Blazor! ({e.ScreenX}:{e.ScreenY})";
    }
}

Parent.razor:

@page "/parent"

<h1>Parent-child example</h1>

<Child Title="Panel Title from Parent" OnClickCallback="ShowMessage">
    Content of the child component is supplied by the parent component.
</Child>

<p>@message</p>

@code {
    private string message;

    private void ShowMessage(MouseEventArgs e)
    {
        message = $"Blaze a new trail with Blazor! ({e.ScreenX}:{e.ScreenY})";
    }
}

Parent.razor:

@page "/parent"

<h1>Parent-child example</h1>

<Child Title="Panel Title from Parent" OnClickCallback="ShowMessage">
    Content of the child component is supplied by the parent component.
</Child>

<p>@message</p>

@code {
    private string message;

    private void ShowMessage(MouseEventArgs e)
    {
        message = $"Blaze a new trail with Blazor! ({e.ScreenX}:{e.ScreenY})";
    }
}

ChildComponent 中选择该按钮时:

  • 调用 Parent 组件的 ShowMessage 方法。 message 更新并显示在 Parent 组件中。
  • 回调方法 (ShowMessage) 中不需要对 StateHasChanged 的调用。 自动调用 StateHasChanged 以重新呈现 Parent 组件,就像子事件触发组件重新呈现于在子级中执行的事件处理程序中一样。 有关详细信息,请参阅 ASP.NET Core Razor 组件呈现

使用 EventCallbackEventCallback<TValue> 处理事件和绑定组件参数。

优先使用强类型 EventCallback<TValue> 而非 EventCallback。 当使用不当类型时。EventCallback<TValue> 会提供经过优化的错误反馈,引导组件的用户正确实施组件。 与其他 UI 事件处理程序类似,指定事件参数是可选操作。 当没有值传递给回调时,使用 EventCallback

EventCallbackEventCallback<TValue> 允许异步委托。 EventCallback 是弱类型,允许将任何类型参数传入 InvokeAsync(Object)EventCallback<TValue> 是强类型,需要将 T 参数传入可分配到 TValueInvokeAsync(T) 中。

使用 InvokeAsync 调用 EventCallbackEventCallback<TValue> 并等待 Task

await OnClickCallback.InvokeAsync({ARGUMENT});

在前面的示例中,{ARGUMENT} 占位符是可选参数。

以下父子示例演示了该技术。

Child2.razor:

<h3>Child2 Component</h3>

<button @onclick="TriggerEvent">Click Me</button>

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

    private async Task TriggerEvent()
    {
        await OnClickCallback.InvokeAsync("Blaze It!");
    }
}

ParentChild2.razor:

@page "/parent-child-2"

<PageTitle>Parent Child 2</PageTitle>

<h1>Parent Child 2 Example</h1>

<div>
    <Child2 OnClickCallback="(value) => { message1 = value; }" />
    @message1
</div>

<div>
    <Child2 OnClickCallback=
        "async (value) => { await Task.Delay(2000); message2 = value; }" /> 
    @message2
</div>

@code {
    private string message1 = string.Empty;
    private string message2 = string.Empty;
}

Child2 组件的第二次出现演示了异步回调,新的 message2 值被分配并呈现,延迟为 2 秒。

阻止默认操作

使用 @on{DOM EVENT}:preventDefault 指令特性防止事件的默认操作,其中 {DOM EVENT} 占位符是 DOM 事件

在输入设备上选择某个键并且元素焦点位于某个文本框上时,浏览器通常在该文本框中显示该键的字符。 在下面的示例中,通过指定 @onkeydown:preventDefault 指令属性来阻止默认行为。 当焦点位于 <input> 元素上时,计数器随着按 Shift++ 按键顺序而递增。 不会将 + 字符分配到 <input> 元素的值。 有关 keydown 的详细信息,请参阅 MDN Web Docs: Document: keydown 事件

EventHandler6.razor:

@page "/event-handler-6"

<PageTitle>Event Handler 6</PageTitle>

<h1>Event Handler Example 6</h1>

<p>For this example, give the <code><input></code> focus.</p>

<p>
    <label>
        Count of '+' key presses: 
        <input value="@count" @onkeydown="KeyHandler" @onkeydown:preventDefault />
    </label>
</p>

@code {
    private int count = 0;

    private void KeyHandler(KeyboardEventArgs e)
    {
        if (e.Key == "+")
        {
            count++;
        }
    }
}

EventHandler6.razor:

@page "/event-handler-6"

<PageTitle>Event Handler 6</PageTitle>

<h1>Event Handler Example 6</h1>

<p>For this example, give the <code><input></code> focus.</p>

<p>
    <label>
        Count of '+' key presses: 
        <input value="@count" @onkeydown="KeyHandler" @onkeydown:preventDefault />
    </label>
</p>

@code {
    private int count = 0;

    private void KeyHandler(KeyboardEventArgs e)
    {
        if (e.Key == "+")
        {
            count++;
        }
    }
}

EventHandlerExample6.razor:

@page "/event-handler-example-6"

<p>
    <input value="@count" @onkeydown="KeyHandler" @onkeydown:preventDefault />
</p>

@code {
    private int count = 0;

    private void KeyHandler(KeyboardEventArgs e)
    {
        if (e.Key == "+")
        {
            count++;
        }
    }
}

EventHandlerExample6.razor:

@page "/event-handler-example-6"

<p>
    <input value="@count" @onkeydown="KeyHandler" @onkeydown:preventDefault />
</p>

@code {
    private int count = 0;

    private void KeyHandler(KeyboardEventArgs e)
    {
        if (e.Key == "+")
        {
            count++;
        }
    }
}

EventHandlerExample6.razor:

@page "/event-handler-example-6"

<p>
    <input value="@count" @onkeydown="KeyHandler" @onkeydown:preventDefault />
</p>

@code {
    private int count = 0;

    private void KeyHandler(KeyboardEventArgs e)
    {
        if (e.Key == "+")
        {
            count++;
        }
    }
}

EventHandlerExample6.razor:

@page "/event-handler-example-6"

<p>
    <input value="@count" @onkeydown="KeyHandler" @onkeydown:preventDefault />
</p>

@code {
    private int count = 0;

    private void KeyHandler(KeyboardEventArgs e)
    {
        if (e.Key == "+")
        {
            count++;
        }
    }
}

指定没有值的 @on{DOM EVENT}:preventDefault 属性等同于 @on{DOM EVENT}:preventDefault="true"

此特性也允许使用表达式值。 在下面的示例中,shouldPreventDefault 是设置为 truefalsebool 字段:

<input @onkeydown:preventDefault="shouldPreventDefault" />

...

@code {
    private bool shouldPreventDefault = true;
}

停止事件传播

使用 @on{DOM EVENT}:stopPropagation 指令属性来停止事件在 Blazor 范围内传播。 {DOM EVENT}DOM 事件的占位符。

stopPropagation 指令属性的效果仅限于 Blazor 范围,不会扩展到 HTML DOM。 事件必须传播到 HTML DOM 根目录,然后 Blazor 才能对其进行操作。 对于预防 HTML DOM 事件传播的机制,请考虑以下方法:

在下例中,选中复选框可阻止第二个子级 <div> 中的单击事件传播到父级 <div>。 由于传播的单击事件通常会触发 OnSelectParentDiv 方法,因此,如果未选中复选框,则选择第二个子级 <div> 会导致父级 <div> 消息出现。

EventHandler7.razor:

@page "/event-handler-7"

<PageTitle>Event Handler 7</PageTitle>

<h1>Event Handler Example 7</h1>

<div>
    <b>stopPropagation</b>: @stopPropagation
</div>

<div>
    <button @onclick="StopPropagation">
        Stop Propagation (stopPropagation = true)
    </button>
    <button @onclick="EnablePropagation">
        Enable Propagation (stopPropagation = false)
    </button>
</div>

<div class="m-1 p-1 border border-primary" @onclick="OnSelectParentDiv">
    <h3>Parent div</h3>

    <div class="m-1 p-1 border" @onclick="OnSelectChildDiv">
        Child div that never stops propagation to the parent div when 
        selected.
    </div>

    <div class="m-1 p-1 border" @onclick="OnSelectChildDiv" 
            @onclick:stopPropagation="stopPropagation">
        Child div that stops propagation when selected if 
        <b>stopPropagation</b> is <b>true</b>.
    </div>
</div>

<p>
    @message
</p>

@code {
    private bool stopPropagation = false;
    private string? message;

    private void StopPropagation() => stopPropagation = true;

    private void EnablePropagation() => stopPropagation = false;

    private void OnSelectParentDiv() => 
        message = $"The parent div was selected. {DateTime.Now}";

    private void OnSelectChildDiv() => 
        message = $"The child div was selected. {DateTime.Now}";
}

EventHandler7.razor:

@page "/event-handler-7"

<PageTitle>Event Handler 7</PageTitle>

<h1>Event Handler Example 7</h1>

<div>
    <b>stopPropagation</b>: @stopPropagation
</div>

<div>
    <button @onclick="StopPropagation">
        Stop Propagation (stopPropagation = true)
    </button>
    <button @onclick="EnablePropagation">
        Enable Propagation (stopPropagation = false)
    </button>
</div>

<div class="m-1 p-1 border border-primary" @onclick="OnSelectParentDiv">
    <h3>Parent div</h3>

    <div class="m-1 p-1 border" @onclick="OnSelectChildDiv">
        Child div that never stops propagation to the parent div when 
        selected.
    </div>

    <div class="m-1 p-1 border" @onclick="OnSelectChildDiv" 
            @onclick:stopPropagation="stopPropagation">
        Child div that stops propagation when selected if 
        <b>stopPropagation</b> is <b>true</b>.
    </div>
</div>

<p>
    @message
</p>

@code {
    private bool stopPropagation = false;
    private string? message;

    private void StopPropagation() => stopPropagation = true;

    private void EnablePropagation() => stopPropagation = false;

    private void OnSelectParentDiv() => 
        message = $"The parent div was selected. {DateTime.Now}";

    private void OnSelectChildDiv() => 
        message = $"The child div was selected. {DateTime.Now}";
}

EventHandlerExample7.razor:

@page "/event-handler-example-7"

<div>
    <b>stopPropagation</b>: @stopPropagation
</div>

<div>
    <button @onclick="StopPropagation">
        Stop Propagation (stopPropagation = true)
    </button>
    <button @onclick="EnablePropagation">
        Enable Propagation (stopPropagation = false)
    </button>
</div>

<div class="m-1 p-1 border border-primary" @onclick="OnSelectParentDiv">
    <h3>Parent div</h3>

    <div class="m-1 p-1 border" @onclick="OnSelectChildDiv">
        Child div that never stops propagation to the parent div when 
        selected.
    </div>

    <div class="m-1 p-1 border" @onclick="OnSelectChildDiv" 
            @onclick:stopPropagation="stopPropagation">
        Child div that stops propagation when selected if 
        <b>stopPropagation</b> is <b>true</b>.
    </div>
</div>

<p>
    @message
</p>

@code {
    private bool stopPropagation = false;
    private string? message;

    private void StopPropagation() => stopPropagation = true;

    private void EnablePropagation() => stopPropagation = false;

    private void OnSelectParentDiv() =>
        message = $"The parent div was selected. {DateTime.Now}";

    private void OnSelectChildDiv() =>
        message = $"The child div was selected. {DateTime.Now}";
}

EventHandlerExample7.razor:

@page "/event-handler-example-7"

<div>
    <b>stopPropagation</b>: @stopPropagation
</div>

<div>
    <button @onclick="StopPropagation">
        Stop Propagation (stopPropagation = true)
    </button>
    <button @onclick="EnablePropagation">
        Enable Propagation (stopPropagation = false)
    </button>
</div>

<div class="m-1 p-1 border border-primary" @onclick="OnSelectParentDiv">
    <h3>Parent div</h3>

    <div class="m-1 p-1 border" @onclick="OnSelectChildDiv">
        Child div that never stops propagation to the parent div when 
        selected.
    </div>

    <div class="m-1 p-1 border" @onclick="OnSelectChildDiv" 
            @onclick:stopPropagation="stopPropagation">
        Child div that stops propagation when selected if 
        <b>stopPropagation</b> is <b>true</b>.
    </div>
</div>

<p>
    @message
</p>

@code {
    private bool stopPropagation = false;
    private string? message;

    private void StopPropagation() => stopPropagation = true;

    private void EnablePropagation() => stopPropagation = false;

    private void OnSelectParentDiv() =>
        message = $"The parent div was selected. {DateTime.Now}";

    private void OnSelectChildDiv() =>
        message = $"The child div was selected. {DateTime.Now}";
}

EventHandlerExample7.razor:

@page "/event-handler-example-7"

<div>
    <b>stopPropagation</b>: @stopPropagation
</div>

<div>
    <button @onclick="StopPropagation">
        Stop Propagation (stopPropagation = true)
    </button>
    <button @onclick="EnablePropagation">
        Enable Propagation (stopPropagation = false)
    </button>
</div>

<div class="m-1 p-1 border border-primary" @onclick="OnSelectParentDiv">
    <h3>Parent div</h3>

    <div class="m-1 p-1 border" @onclick="OnSelectChildDiv">
        Child div that never stops propagation to the parent div when 
        selected.
    </div>

    <div class="m-1 p-1 border" @onclick="OnSelectChildDiv" 
            @onclick:stopPropagation="stopPropagation">
        Child div that stops propagation when selected if 
        <b>stopPropagation</b> is <b>true</b>.
    </div>
</div>

<p>
    @message
</p>

@code {
    private bool stopPropagation = false;
    private string message;

    private void StopPropagation() => stopPropagation = true;

    private void EnablePropagation() => stopPropagation = false;

    private void OnSelectParentDiv() =>
        message = $"The parent div was selected. {DateTime.Now}";

    private void OnSelectChildDiv() =>
        message = $"The child div was selected. {DateTime.Now}";
}

EventHandlerExample7.razor:

@page "/event-handler-example-7"

<div>
    <b>stopPropagation</b>: @stopPropagation
</div>

<div>
    <button @onclick="StopPropagation">
        Stop Propagation (stopPropagation = true)
    </button>
    <button @onclick="EnablePropagation">
        Enable Propagation (stopPropagation = false)
    </button>
</div>

<div class="m-1 p-1 border border-primary" @onclick="OnSelectParentDiv">
    <h3>Parent div</h3>

    <div class="m-1 p-1 border" @onclick="OnSelectChildDiv">
        Child div that never stops propagation to the parent div when 
        selected.
    </div>

    <div class="m-1 p-1 border" @onclick="OnSelectChildDiv" 
            @onclick:stopPropagation="stopPropagation">
        Child div that stops propagation when selected if 
        <b>stopPropagation</b> is <b>true</b>.
    </div>
</div>

<p>
    @message
</p>

@code {
    private bool stopPropagation = false;
    private string message;

    private void StopPropagation() => stopPropagation = true;

    private void EnablePropagation() => stopPropagation = false;

    private void OnSelectParentDiv() =>
        message = $"The parent div was selected. {DateTime.Now}";

    private void OnSelectChildDiv() =>
        message = $"The child div was selected. {DateTime.Now}";
}

聚焦元素

元素引用上调用 FocusAsync 来将代码中的元素作为焦点。 在下面的示例中,选择按钮可将 <input> 元素作为焦点。

EventHandler8.razor:

@page "/event-handler-8"

<PageTitle>Event Handler 8</PageTitle>

<h1>Event Handler Example 8</h1>

<p>Select the button to give the <code><input></code> focus.</p>

<p>
    <label>
        Input: 
        <input @ref="exampleInput" />
    </label>
    
</p>

<button @onclick="ChangeFocus">
    Focus the Input Element
</button>

@code {
    private ElementReference exampleInput;

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

EventHandler8.razor:

@page "/event-handler-8"

<PageTitle>Event Handler 8</PageTitle>

<h1>Event Handler Example 8</h1>

<p>Select the button to give the <code><input></code> focus.</p>

<p>
    <label>
        Input: 
        <input @ref="exampleInput" />
    </label>
    
</p>

<button @onclick="ChangeFocus">
    Focus the Input Element
</button>

@code {
    private ElementReference exampleInput;

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

EventHandlerExample8.razor:

@page "/event-handler-example-8"

<p>
    <input @ref="exampleInput" />
</p>

<button @onclick="ChangeFocus">
    Focus the Input Element
</button>

@code {
    private ElementReference exampleInput;

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

EventHandlerExample8.razor:

@page "/event-handler-example-8"

<p>
    <input @ref="exampleInput" />
</p>

<button @onclick="ChangeFocus">
    Focus the Input Element
</button>

@code {
    private ElementReference exampleInput;

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