如何制作使用 oninput 而不是 onchange 绑定的 EditForm 输入?

Posted

技术标签:

【中文标题】如何制作使用 oninput 而不是 onchange 绑定的 EditForm 输入?【英文标题】:How to make an EditForm Input that binds using oninput rather than onchange? 【发布时间】:2019-12-05 15:34:42 【问题描述】:

假设我想使用EditForm,但我希望每次用户键入控件时触发值绑定,而不是仅仅在模糊时触发。

假设,为了一个例子,我想要一个 InputNumber<int> 来做这个?我试过使用不同的方法,比如bind-Value:event="oninput",但没有成功。通过复制 InputNumber 的 AspNetCore 源代码并覆盖/重写一些内容,我终于能够或多或少地获得我想要的东西。

这是我的InputNumber<int>,它实现了我的期望:

    public class MyInputNumber: InputNumber<int>
    
        protected override void BuildRenderTree(RenderTreeBuilder builder)
        
            builder.OpenElement(0, "input");
            builder.AddAttribute(1, "step", "any");
            builder.AddMultipleAttributes(2, AdditionalAttributes);
            builder.AddAttribute(3, "type", "number");
            builder.AddAttribute(4, "class", CssClass);
            builder.AddAttribute(5, "value", FormatValue(CurrentValueAsString));
            builder.AddAttribute(6, "oninput", EventCallback.Factory.CreateBinder<string>(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
            builder.CloseElement();
        

        // Copied from AspNetCore - src/Components/Components/src/BindConverter.cs
        private static string FormatValue(string value, CultureInfo culture = null) => FormatStringValueCore(value, culture);
        // Copied from AspNetCore - src/Components/Components/src/BindConverter.cs
        private static string FormatStringValueCore(string value, CultureInfo culture)
        
            return value;
        
    

请注意,序列 6 项中的“oninput”已从基本 InputNumberBuildRenderTree 方法中的“onchange”更改。我想知道如何:

    查看BuildRenderTree 的输出,以便我知道如何使用 Razor 和/或 只是大致了解哪种 Razor 语法相当于将来执行此操作。

我从 AspNetCore 代码中的 cmets 收集到,这绝对不是做这类事情的首选方法,Razor 是首选方法。我已经通过订阅 EditContextOnFieldChangedEvent 测试了这在 .NET Core 3 Preview 7 ASP.NET Core Hosted Blazor 中是否有效,并且可以看到通过这种方法我得到了我正在寻找的不同行为为了。希望有更好的方法。

更新

包括有关问题的更多信息

@using BlazorAugust2019.Client.Components;
@inherits BlazorFormsCode
@page "/blazorforms"

<EditForm EditContext="EditContext">
    <div class="form-group row">
        <label for="date" class="col-sm-2 col-form-label text-sm-right">Date: </label>
        <div class="col-sm-4">
            <KlaInputDate Id="date" Class="form-control" @bind-Value="Model.Date"></KlaInputDate>
        </div>
    </div>
    <div class="form-group row">
        <label for="summary" class="col-sm-2 col-form-label text-sm-right">Summary: </label>
        <div class="col-sm-4">
            <KlaInputText Id="summary" Class="form-control" @bind-Value="Model.Summary"></KlaInputText>
        </div>
    </div>
    <div class="form-group row">
        <label for="temperaturec" class="col-sm-2 col-form-label text-sm-right">Temperature &deg;C</label>
        <div class="col-sm-4">
            <KlaInputNumber Id="temperaturec" Class="form-control" @bind-Value="Model.TemperatureC"></KlaInputNumber>
        </div>
    </div>
    <div class="form-group row">
        <label for="temperaturef" class="col-sm-2 col-form-label text-sm-right">Temperature &deg;F</label>
        <div class="col-sm-4">
            <input type="text" id="temperaturef" class="form-control" value="@Model.TemperatureF" readonly />
        </div>
    </div>
</EditForm>
using BlazorAugust2019.Shared;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System;

namespace BlazorAugust2019.Client.Pages.BlazorForms

    public class BlazorFormsCode : ComponentBase
    
        public EditContext EditContext;
        public WeatherForecast Model;

        public BlazorFormsCode()
        
            Model = new WeatherForecast()
            
                Date = DateTime.Now,
                Summary = "Test",
                TemperatureC = 21
            ;
            EditContext = new EditContext(Model);
            EditContext.OnFieldChanged += EditContext_OnFieldChanged;
        

        private void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
        
            Console.WriteLine($"EditContext_OnFieldChanged - e.FieldIdentifier.FieldName");
        
    

我正在寻找的是在我输入输入时触发的“EditContext_OnFieldChanged”事件。当前示例有效,只是在寻找更好的方法。

【问题讨论】:

当您说“当前示例有效”时,您的意思是在您输入输入时触发了“EditContext_OnFieldChanged”事件吗?我认为无法触发“EditContext_OnFieldChanged”事件,因为没有代码触发 InputBase 中定义的 ValueChanged 委托 我不确定它发生在哪里或为什么发生,但它正在发生。是的,Console.WriteLine 被击中,我可靠地获得了更改的字段的名称。我不知道,也许它在我正在扩展的派生类 InputNumber、InputText 等中。 github.com/aspnet/AspNetCore/blob/… 见第 70 行 【参考方案1】:

试试这个:

 <input type="text" id="example1" @bind-value="@value" @bind-value:event="oninput" />

更多...

不推荐您的方法。您应该使用 Razor 子类化您的组件。

以下是应该可以工作的示例。它可以重定向您如何获得解决方案。

包装InputText的解决方案:

NewInputText.razor

<div class="form-group">
    <label class="col-form-label">@Label</label>
    <InputText Class="form-control" Value="@Value" ValueChanged="@ValueChanged" ValueExpression="@ValueExpression"></InputText>
</div>

@functions 
    [Parameter] string Label  get; set; 
    [Parameter] string Value  get; set; 
    [Parameter] EventCallback<string> ValueChanged  get; set; 
    [Parameter] Expression<Func<string>> ValueExpression  get; set; 

Index.razor

<span>Name of the category: @category.Name</span>
<EditForm Model="@category">
    <NewInputText @bind-Value="@category.Name"/>
</EditForm>

你也可以像这样直接从InputBase继承:

 @inherits InputBase<string>
 <input type="number" bind="@CurrentValue" id="@Id" class="@CssClass" />

希望这会有所帮助...

【讨论】:

我不禁注意到您的每个不同的解决方案都只是解决方案的一半。在某些时候,我想从 InputBase 派生,因为我的假设是在 EditForm 中使用是必需的(以及开箱即用的 EditContext 之类的东西),但我也想使用 OnInput。您是否有经过测试的解决方案,在输入输入时会触发 EditContext.OnFieldChanged 事件?另请注意,我使用的是 EditContext 而不是 Model 参数。 你试过第一个解决方案了吗? 我做了,但它不像我在 OP 中指定的那样工作。 除非你指的是我根本不制作组件的 解决方案。这行不通,因为我出于其他原因需要编辑上下文。编辑:做了测试,也没有按预期工作 更新了 OP 以阐明我在寻找什么。【参考方案2】:

好的,在查看源代码特别是查看属性 CurrentValueAsString 和 CurrentValue 之后,我发现了这一点。这是我提出的用于制作文本输入的解决方案,该解决方案可以在输入上正确触发字段更改事件:

    public class InputTextCode : InputBase<string>
    
        protected override bool TryParseValueFromString(string value, out string result, out string validationErrorMessage)
        
            result = value;
            validationErrorMessage = null;
            return true;
        
    
@inherits InputTextCode;

<input type="text" id="@Id" class="@Class" @bind-value="CurrentValueAsString" @bind-value:event="oninput" />

如果这可以是开箱即用的输入组件上的一个易于配置的选项,那将是非常好的,但至少有一种方法可以做到这一点,不会让我的皮肤爬行。

【讨论】:

你有没有找到任何可配置/更简单的方法?【参考方案3】:

对于任何想知道的人,您可以将InputText 子类化以更改其呈现方式。例如,要使其使用oninput 事件,请创建 MyInputText.razor 包含:

@inherits Microsoft.AspNetCore.Components.Forms.InputText
<input @attributes="@AdditionalAttributes" class="@CssClass" @bind="@CurrentValueAsString" @bind:event="oninput" />

现在,当您使用 &lt;MyInputText @bind-Value="@someval" /&gt; 时,它的行为就像 InputText 一样,只是它会在每次击键时更新。

史蒂夫·桑德森

【讨论】:

太棒了!我不知道!我想知道继承现有组件是否会使用您编写的 Razor 标记覆盖其 BuildRenderTree 方法 抱歉,我不知道这条评论...是的,“继承现有组件会使用您编写的 Razor 标记覆盖其 BuildRenderTree 方法”。事实上,这是推荐的方式:使用现有逻辑,同时提供不同的视图。 执行此操作后,我收到了System.ArgumentException: Object of type 'Microsoft.AspNetCore.Components.Web.KeyboardEventArgs' cannot be converted to type 'Microsoft.AspNetCore.Components.ChangeEventArgs'. 如果我的输入与其他代码放在一起,是否应该将@inherits 和输入抽象为一个单独的组件?还是@inherits 只适用于下一行? 也不清楚从InputText继承有什么好处,而我可以使用具有相同效果的纯html输入。【参考方案4】:

从组件继承并对其进行更改以使其响应input 事件现在包含在official documentation for .NET Core 3.1 中:

基于输入事件的InputText

使用InputText 组件创建一个使用input 事件而不是change 事件的自定义组件。

使用以下标记创建一个组件,并像使用 InputText 一样使用该组件:

剃须刀:

@inherits InputText

<input
    @attributes="AdditionalAttributes" 
    class="@CssClass" 
    value="@CurrentValue" 
    @oninput="EventCallback.Factory.CreateBinder<string>(
        this, __value => CurrentValueAsString = __value, CurrentValueAsString)" />

文档还给出了an example of how to inherit a generic component:

@using System.Globalization
@typeparam TValue
@inherits InputBase<TValue>

因此,如果您将这两者结合在一起,您可以创建一个组件来更改 InputNumber 组件的行为,如下所示:

@typeparam TNumber
@inherits InputNumber<TNumber>

<input 
   type="number"
   step="any"
   @attributes="AdditionalAttributes" 
   class="@CssClass" 
   value="@CurrentValue" 
   @oninput="EventCallback.Factory.CreateBinder<string>(
        this, __value => CurrentValueAsString = __value, CurrentValueAsString)" />

包含type="number" 部分将提供与现有InputNumber 相同的行为(仅允许输入数字字符,使用向上和向下箭头增加/减少等)

如果您将上述代码放在 Blazor 项目的 Shared 文件夹中名为 InputNumberUpdateOnInput.razor 的文件中,则该组件的使用方式与您使用普通 InputNumber 的方式相同,例如:

<InputNumberUpdateOnInput class="form-control text-right" @bind-Value="@invoiceLine.Qty" />

如果您想控制组件允许的小数位数,您需要将step 值作为您传递给组件的参数。 step in this answer 上还有更多内容。

【讨论】:

【参考方案5】:

我发现如果他/她避免使用@bind 而是像这样手动绑定,则可以做到这一点:

<InputText Value=@MyValue @oninput=@HandleInputChange ValueExpression=@(() => MyValue) />

@
  // This will change for every character which is entered/removed from the input
  Public string MyValue  get; set; 

  void HandleInputChange(ChangeEventArgs args)
  
    MyValue = args.Value.ToString();
  


我仍然不明白为什么会这样。我认为这是因为AdditionalAttributes 参数。它将oninput 属性传递给更新您的值的html &lt;input&gt; 元素,因此您不再依赖ValueChanged 回调来更新您的值。

【讨论】:

以上是关于如何制作使用 oninput 而不是 onchange 绑定的 EditForm 输入?的主要内容,如果未能解决你的问题,请参考以下文章

即时反应的input和propertychange方法

如何获取所选选项的文本而不是值

html 范围滑块 - oninput 在 IE 11 中不起作用

从输出元素发送值并提交

如何制作像下面这样的视图而不是使用 DataGridView?

如何制作 - 自动完成搜索全局而不是本地