如何将焦点设置到 InputText 元素?

How to set the focus to an InputText element?

使用 example from the Microsoft docs,我尝试以编程方式将焦点设置到输入元素。

不幸的是,该示例使用标准 <input type="text">,而我想将其用于 InputText element

Microsoft 示例使用了一个扩展方法,该方法采用 ElementReference:

public static Task Focus(this ElementReference elementRef, IJSRuntime jsRuntime)
{
    return jsRuntime.InvokeAsync<object>(
        "exampleJsFunctions.focusElement", 
        elementRef);
}

使用 InputText,我看不出有什么办法可以得到这样的 ElementReference

InputText 代替我自己的 Focus() 重载,编译但没有显示任何视觉结果。所以我一无所知

我的问题

如何以编程方式将焦点设置到 InputText 元素?

您可以将 id 参数添加到您的 InputText 并修改您的 Focus 方法和 JavaScript 代码。

public async Task Focus(string elementId)
{
    await JSRuntime.InvokeVoidAsync("exampleJsFunctions.focusElement", elementId);
}
focusElement: function (id) {
    const element = document.getElementById(id); 
    element.focus();
}

注意:这与其说是正确的解决方案,不如说是一种变通方法,但 Blazor 似乎并不直接支持它。

有几种方法可以通过 Blazor 本机方式将焦点设置在元素上。这是一个:

创建一个派生自 InputBase<string> 的 class,它是 InputText 的基础 class,具有与 InputText 相同的功能。简而言之,将 the code of InputText 复制到您新创建的 class,并添加必要的功能。这是新的 class:TextBox.cs

public class TextBox : InputBase<string>
    {

        private ElementReference InputRef;
        protected override void BuildRenderTree(RenderTreeBuilder builder)
    {

            builder.OpenElement(0, "input");
    builder.AddMultipleAttributes(1, AdditionalAttributes);
    builder.AddAttribute(2, "class", CssClass);
    builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValue));
    builder.AddAttribute(4, "onchange", EventCallback.Factory.CreateBinder<string>
        (this, __value => CurrentValueAsString = __value, CurrentValueAsString));
    builder.AddElementReferenceCapture(5, (value) => {
                InputRef = value; } );


            builder.CloseElement();


    }
        [Inject] IJSRuntime JSRuntime { get; set; }

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                await JSRuntime.InvokeVoidAsync("exampleJsFunctions.focusElement", InputRef);
            }
        }

        protected override bool TryParseValueFromString(string value, out string result, out string validationErrorMessage)
        {
        result = value;
        validationErrorMessage = null;
        return true;
        }
        }

   } 

将此脚本放在 _Host.cshtml 文件的底部,就在

下面

<script src="_framework/blazor.server.js"></script>

<script>

        window.exampleJsFunctions =
        {
            focusElement: function (element) {
               element.focus();
            }
        };
    </script>

注意事项: 1. 定义一个 ElementReference 变量来保存对输入元素的引用。 2. 在 BuildRenderTree 方法中,我添加了代码来捕获对 输入元素 3. 从 OnAfterRenderAsync 方法调用 focusElement JavaScript 函数。 这只执行一次。请注意,我不能使用 OnInitializedAsync 只执行一次的方法,但 ElementReference 变量可能 包含空值。 4. 请注意,您不能 运行 任何没有 EditForm 的表单组件...

重要提示:当您的浏览器处于最小化状态时,按 Ctrl+F5 可能会干扰在文本元素中看到光标。

使用代码:

<EditForm  Model="@employee" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <TextBox @bind-Value="@employee.Name"/>

    <button type="submit">Submit</button>
</EditForm>

@code {
    private Employee employee = new Employee();

    private void HandleValidSubmit()
    {
        Console.WriteLine("OnValidSubmit");
    }

    public class Employee
    {
        public int ID { get; set; } = 1;
        public string Name { get; set; } = "Nancy";
    }
} 

我可以通过直接在 Host.chtml 文件中输入 JavaScript 来完成此操作(您也可以像演练建议的那样添加 .js 文件):

<script type="text/javascript">
    window.exampleJsFunctions = {
        focusElement: function (element) {
            element.focus();
        }
    }
</script>

接下来,在扩展文件中,我添加了扩展(注意:由于命名标准,我将 Focus 重命名为 FocusAsync

public static async Task FocusAsync(this ElementReference elementRef, IJSRuntime jsRuntime)
{
    await jsRuntime.InvokeVoidAsync(
        "exampleJsFunctions.focusElement", elementRef);
}

然后在剃须刀页面(或组件)中,注入 IJSRuntime,添加一个 ElementReference 并将其绑定到您要聚焦的元素(注意:方法名称已更改为遵守命名标准):

@inject IJSRuntime JSRuntime
@using JsInteropClasses

<input @ref="username" />
<button @onclick="SetFocusAsync">Set focus on username</button>

@code {
    private ElementReference username;

    public async Task SetFocusAsync()
    {
        await username.FocusAsync(JSRuntime);
    }
}

.NET5中会简单很多:

<button @onclick="() => textInput.FocusAsync()">Set focus</button>
<input @ref="textInput"/>
@code {
    ElementReference textInput;
}

注意:此功能是在 .NET5 Preview 8 中引入的,因此在最终版本发布之前可能会发生变化!

另外值得一提的是,在 .NET5 RC1 JavaScript isolation 中是通过 JS 模块 export/import 引入的。因此,如果您仍然需要使用 JS 互操作,请不要污染 window 对象。

更新: .NET 5 已发布,此功能未更改。

我也很帅Nuget package which can do some convenient JS tricks for you e.g.: focusing previously active element without having a @ref to it. See docs here.

现在 .net 5 可能是多余的,但如果您不想硬编码元素的 ID 或派生您自己的输入 class,实现此目的的简单方法是添加一个 CSS class 到元素,然后使用 getElementsByClassName 找到并设置焦点。

创建包含助手的脚本:

var JSHelpers = JSHelpers || {};

JSHelpers.setFocusByCSSClass = function () {
    var elems = document.getElementsByClassName("autofocus");
    if (elems.length == 0)
        return;
    elems[0].focus();
};

创建一个共享的'AutofocusByCSS'组件来处理在正确的时间调用JS:

@inject IJSRuntime JSRuntime
@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender) JSRuntime.InvokeVoidAsync("JSHelpers.setFocusByCSSClass");
    }
}   

然后在您的页面中:

<InputText @bind-Value="editmodel.someThing" class="form-control autofocus" />

<AutofocusByCSS />

适用于归结为标准 HTML 元素的任何内容,并且可以通过更改 class 名称输出更轻松地更改自动对焦目标。

与@enet 的回答类似,我继承自 InputBase,但不是在 .razor 文件中而是在 .cs 文件中:

public abstract class MyInputBase<T> : InputBase<T>
{
    /// <summary>
    /// If true, this element will be focused when the page loads
    /// </summary>
    [Parameter] public bool Autofocus { get; set; }

    /// <summary>
    /// Optional. Sets the id on the input element as well as the for attibute on the label element
    /// </summary>
    [Parameter] public string? ElementId { get; set; }


    [Inject] IJSRuntime? Js { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            if (Autofocus) await Js.InvokeVoidAsync("cs.focusElementById", ElementId);
        }
        await base.OnAfterRenderAsync(firstRender);
    }

    protected override void OnInitialized()
    {
        if (string.IsNullOrWhiteSpace(ElementId)) ElementId = Guid.NewGuid().ToString();

        base.OnInitialized();
    }
}

还有我的.js

var cs = {

    focusElementById: function (id) {
        setTimeout(function () {
            var el = document.getElementById(id);
            if (el) el.focus();
        }, 500);
    }

}

几个笔记。第一,MyInputBase<T> 是抽象的,它将 TryParseValue 下推到我对 InputText、InputNumber 等的单独实现,它们继承了 MyInputBase<T> 而不是微软的 InputBase<T>.

另外,请注意我在 javascript 中设置了一个 timeout。至少对我来说,如果我不这样做,聚焦元素是行不通的。

对于 .NET 6:

在您的代码中,在@code 中或在代码后面创建一个 InputText 变量:

private InputText inputTextForFocus;

在 Blazor InputText 组件中,引用该变量:

<InputText @ref=inputTextForFocus>

然后,在您的代码中的适当位置,您可以访问 InputText 的底层 Element 并调用 FocusAsync()。注意 Element 可以为空。

例如,将焦点设置为启动:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
      if (inputTextForFocus.Element != null)
           await inputTextForFocus.Element.Value.FocusAsync();
}

学分到期的学分:https://www.youtube.com/watch?v=9Q7cXdylU7k