如何使用 Blazor 绑定到文档事件



我正在使用 Blazor 编写一个简单的贪吃蛇游戏,但我不知道如何绑定到文档事件。我知道可以在不同的元素(例如div输入)上绑定事件。示例:<input onkeypress="@KeyPressInDiv"/>,其中处理程序public void KeyPressInDiv(UIKeyboardEventArgs ev) {...}

我想应该有一些等效于 JavaScript 方法document.onkeydown = function (evt) {}.我找到了解决此问题的两种方法:

  1. 使用 JavaScript 进行绑定并调用 Blazor 代码(取自 https://github.com/aesalazar/AsteroidsWasm):
document.onkeydown = function (evt) {
evt = evt || window.event;
DotNet.invokeMethodAsync('Test.ClientSide', 'JsKeyDown', evt.keyCode);
//Prevent all but F5 and F12
if (evt.keyCode !== 116 && evt.keyCode !== 123)
evt.preventDefault();
};
document.onkeyup = function (evt) {
evt = evt || window.event;
DotNet.invokeMethodAsync('Test.ClientSide', 'JsKeyUp', evt.keyCode);
//Prevent all but F5 and F12
if (evt.keyCode !== 116 && evt.keyCode !== 123)
evt.preventDefault();
};

。在 C# 中实现一个静态类,其中包含由[JSInvokable]和事件标记的方法。这有效,但会导致每次按键都出现极端延迟。

  1. 可以添加输入标签并绑定到其事件。这比以前的方法工作得更快,但它似乎是一种黑客而不是解决方案。此外,我们无法侦听某些操作,例如向上/向下箭头。

是否有直接方法可以从 Blazor 绑定到文档事件?

更新1:我创建了一个简单的项目来更好地解释我想要实现的目标:https://github.com/XelMed/BlazorSnake Snake 有 3 种实现:

  1. 纯 JS - 这具有预期的行为
  2. 将 JS 与 Blazor 一起使用 - 使用 JsInterop 从 JS 代码调用 Blazor 函数
  3. 使用输入标签 - 绑定到输入标签上的事件以控制蛇

也许使用JsInterop 向文档添加一个事件侦听器,并为事件分配一个匿名函数,该函数使用 even 参数调用 C# 方法。

例如,您的 JsInterop.js:

document.addEventListener('onkeypress', function (e) {
DotNet.invokeMethodAsync('Snake', 'OnKeyPress', serializeEvent(e))
});

与序列化事件作为follos以避免一些古怪:

var serializeEvent = function (e) {
if (e) {
var o = {
altKey: e.altKey,
button: e.button,
buttons: e.buttons,
clientX: e.clientX,
clientY: e.clientY,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
movementX: e.movementX,
movementY: e.movementY,
offsetX: e.offsetX,
offsetY: e.offsetY,
pageX: e.pageX,
pageY: e.pageY,
screenX: e.screenX,
screenY: e.screenY,
shiftKey: e.shiftKey
};
return o;
}
};

在 C# 代码中,你将具有:

[JSInvokable]
public static async Task OnMouseDown(UIMouseEventArgs e){
// Do some stuff here
}

我和你有同样的要求,我设法使用 invokeMethodAsync 将文档事件(例如 keydown)连接到我的 Razor 方法,但后来我发现我错过了 Blazor 提供的自动 DOM 差异和更新,如果 Razor 方法更改绑定到 HTML 元素的某些状态(例如控制div 元素的可见性)。

似乎(根据我对 Blazor 的有限理解)通常 Blazor 在 WebAssembly 方法的返回数据中返回更新 DOM 所需的任何状态数据。 但是,如果使用 invokeMethod 或 JavaScript 中的 invokeMethodAsync,则必须自己管理返回和使用这些数据,但这可能会有问题,因为更新可能与 Blazor 的 DOM 状态视图冲突。

因此,我想出了一种在Razor视图中生成隐藏按钮的黑客方法,例如:

<button id="arrow-left-button" @onclick="HandleArrowLeftPress"></button>

然后在 JavaScript 方面,我连接了一个文档事件,该事件通过 ID 找到该按钮并在其上调用 .click():

document.getElementById('arrow-left-button').click();

我知道这看起来真的很糟糕,但它适用于我正在做的事情,即使用箭头键在屏幕上移动绝对定位的元素。

如果有人知道一种更干净的方法(例如如何在处理程序回调中从 JavaScript 端按其名称强制更新 Razor 视图),请告诉我。

对Blightbuster的答案进行一些扩展

对我来说,至少返回的序列化事件对象不会强制转换为其 .NET 对应对象(在我的情况下KeyboardEventArgs)。我必须先串起来

var serializeEvent = function (e) {
if (e) {
var o = {
//assign properties here
//we can´t just stringify e since it has circular references
};
return JSON.stringify(o);
}
};

然后在 JSRuntime 调用的 .NET 方法中进行反序列化

using System.Text.Json;
T deserialized = JsonSerializer.Deserialize<T>(
s,
new JsonSerializerOptions() {
PropertyNameCaseInsensitive = true
});

此外,如果您像我一样懒惰并且不想从事件中查找所需的属性,则可以在调用 js 方法之前在 .NET 中查找属性名称

var propertyNames = typeof(T)
.GetProperties()
.Where(x => x.CanWrite)
.Select(x => $"{x.Name[..1].ToLower()}{x.Name[1..]}")
.ToArray();

然后将这些传递给js并构建您的事件对象

let o = {};
propertyNames.forEach(propertyName => {
o[propertyName] = e[propertyName];
});
var serialized = JSON.stringify(o);

最后,我不想使用[JSInvokable]属性制作静态方法,所以我只做了一个小包装器。

class JSInvokableWrapper<T>
{
Func<T, Task> Func { get; }
public JSInvokableWrapper(
Func<T, Task> func)
{
Func = func;
}
[JSInvokable]
public async Task Invoke(T argument)
{
await Func.Invoke(argument);
}
}

使用它来包装您希望 js 执行的任何函数并将包装器转换为DotNetObjectReference(也许在IJSRuntime的某种扩展方法中)

var dotNetObjectReference = DotNetObjectReference
.Create(new JSInvokableWrapper<T>(func));

并从 js 调用其 Invoke 方法

dotNetObjectReference.invokeMethodAsync('Invoke', param);

我遇到了类似的情况,并使用以下方法作为解决方法。将事件侦听器添加到div标记,然后将焦点设置为该div以便可以捕获按键。

<div tabindex="0" @ref="container" @onkeydown="onKeyDown" id="container">
//your snake game markup
</div>
@code {
ElementReference container;
protected override async Task OnAfterRenderAsync(bool firstRender) {
await container.FocusAsync();
}
private void onKeyPress(KeyboardEventArgs obj) { //your code here. }
}

您可能需要删除div标记的焦点边框:

#container:focus-visible {
outline: none;
}
#container:focus {
outline: none;
}

您可以将事件直接绑定到 C# 方法,只需使用所需的事件标记(onkeyup/onmousemove ....) 。

@page "/"
<div>
<input type="text" onkeyup=@KeyUp />
</div>
@functions {
void KeyUp(UIKeyboardEventArgs e)
{
Console.WriteLine(e.Key);
}
protected override Task OnInitAsync()
{
return Task.CompletedTask;
}
}

最新更新