onChange事件未在Blazor InputSelect中触发。

33

我有以下代码用于InputSelect:

               <InputSelect class="form-control form-control form-control-sm"
                             placeholder="Role"
                             disabled="@IsReadOnly"
                             @bind-Value="Model.Role"
                              @onchange="@RoleChanged">
                    <option value="Member">Member</option>
                    <option value="Admin">Admin</option>
                    <option value="Pioner">Pioneer</option>
                    <option value="Retailer">Retailer</option>
                </InputSelect>

而对于代码部分:

bool ShowCreated;
bool ShowUpdated;
bool IsReadOnly;
string SelectedRole;

public EditForm AccountForm;
public Multi.Dtos.RegistrationDto Model = new Dtos.RegistrationDto() { Role = "Member" };

public void RoleChanged(ChangeEventArgs e)
{
    SelectedRole = e.Value.ToString();
    Console.WriteLine(e.Value);
}

当我尝试选择新项目时,RoleChange函数没有被调用。有人能指出问题在哪里吗?属性的值已经改变,但事件未被调用。


8
您不能同时进行绑定值和@onchange。 - dani herrera
1
我在调试过程中发现了这个问题。不知道有什么推荐的解决方法吗? - Edu Cielo
你可以在你的模型上实现IPropertyChange接口,并将一些操作绑定到事件上。 - dani herrera
7个回答

52

注意:

  • InputSelect是一个组件元素,而不是HTML元素,因此您不能将@ onchange编译器指令应用于它。该指令通常但并不一定应用于具有"value"属性以形成所谓的双向数据绑定的元素。

  • @bind-Value是一个编译器指令,指示编译器生成代码以启用组件间的双向数据绑定。将@bind-Value应用于InputSelect组件需要您(在这种情况下由Blazor团队已经完成)定义一个名为Value的参数属性和一个EventCallback“委托”,通常命名为ValueChanged。Value和ValueChanged是InputSelect组件的属性。

有几种方法可以做到这一点:

 <InputSelect ValueExpression="@(()=>comment.Country)" 
              Value="@comment.Country" 
              ValueChanged="@((string value) => OnValueChanged(value ))">
        <option value="">Select country...</option>
        <option value="USA">USA</option>
        <option value="Britain">Britain</option>
        <option value="Germany">Germany</option>
        <option value="Israel">Israel</option>
 </InputSelect>

private Task OnValueChanged(string value)
{
    // Assign the selected value to the Model 
    comment.Country = value;
    return Task.CompletedTask;
} 
你也可以像dani herrera建议的那样在你的Model中实现INotifyPropertyChanged.PropertyChanged事件。如果你想摆脱Forms组件,包括EditForm,并使用正常的HTML标签,那么你可以这样做:
<input type="text" value="@MyValue" @onchange=OnChange"/>

当然,您的模型类会很厚,并且它应该与EditContext进行通信....

希望这可以帮助...


1
为什么需要ValueExpression? - Augusto Barreto
1
通常情况下,您不必提供ValueExpression...编译器可以在没有它的情况下进行双向绑定。但有时您需要告诉编译器如何执行它...您可以在我的答案中阅读更多信息:https://dev59.com/QFIH5IYBdhLWcg3wMalg#60659712 - enet
1
我之前一直在使用Angular,现在转到Blazor,发现在InputText中可以使用@onchange,但是在InputSelect中却不能,这让我感到非常困惑和复杂。是否有一种方法可以创建一个CustomInputSelect.razor组件来抽象出这个更改逻辑,以便在每个InputSelect上都不需要它? - Ryan Buening
1
@Ryan Buening,我已经添加了一个“新答案”来回应您的评论... - enet
1
为什么我会收到这个错误:方法'TypeInference.CreateInputSelect_0<TValue>(RenderTreeBuilder, int, int, Expression<Func<TValue>>, int, TValue, int, EventCallback<TValue>, int, RenderFragment)'的类型参数无法从使用中推断出来。请尝试显式指定类型参数。 - Ali
我正在尝试在.NET 6的Blazor上运行此程序,但是出现了编译器错误。错误代码CS0411:无法从使用中推断出方法“TypeInference.CreateInputSelect_2<TValue>(RenderTreeBuilder,int,int,object,int,Expression<Func<TValue>>,int,TValue,int,EventCallback<TValue>,int,RenderFragment>”的类型参数。请尝试明确指定类型参数。 - Hani Safa

13
问题在于@bind属性使用@onchange事件,而Blazor不允许多个@onchange事件处理程序。以下是代码中的解决方法:
  • 方法1:这是一个基本示例。它展示了如何使用HTML select标签连接下拉菜单,当您不需要onchange事件处理程序时使用此方法。
  • 方法2:如果您需要两种数据绑定和一个事件处理程序,那么我认为这是最简单的方法。它使用HTML select标签(而非Blazor组件)进行单向数据绑定,并使用"value"属性。由于未使用@bind属性,因此我们可以自由地将处理程序附加到@onchange事件上。该处理程序除了处理事件外,还需要填充属性以实现双向数据绑定。
  • 方法3:如果您正在使用整个Blazor EditForm和InputText/InputSelect/etc组件架构,则此方法可能最适合您。在没有EditContext的情况下,该示例使用@bind-Value进行双向绑定。为了处理任何组件的onchange事件,我们为整个表单添加了一个事件处理程序(EditContext.OnFieldChanged)。处理程序检查更改的属性(基于FieldName),并相应地触发所需的事件处理程序。
我创建了这个页面来提醒我有关这些选项的内容。希望它能帮助任何发现这里的人。
@page "/dropdown"

<h2 class='@(hightlight ? "bg-info" : "")'>
    User Id: @UserId
</h2>

<hr />

<h3>Using Select Tag</h3>
<p>
    <label>
        Method 1 (2-way binding but do not need to handle an onchange event):<br />
        <select @bind="UserId">
            <option value=""></option>
            @foreach (var u in users)
            {
                <option value="@u.Key">@u.Value</option>
            }
        </select>
    </label>
</p>
<p>
    <label>
        Method 2 (need to handle the onchange event; 2-way binding via the control onchange event handler):<br />
        <select value="@UserId" @onchange="OnChangeUserId">
            <option value=""></option>
            @foreach (var u in users)
            {
                <option value="@u.Key">@u.Value</option>
            }
        </select>
    </label>
</p>

<h3>Using InputSelect Tag</h3>
<EditForm EditContext="EditContext">
    <p>
        <label>
            Method 3 (2-way binding;  event handling via the EditForm EditContext.OnFieldChanged)<br />
            <InputSelect @bind-Value="@UserId">
                <option value=""></option>
                @foreach (var u in users)
                {
                    <option value="@u.Key">@u.Value</option>
                }
            </InputSelect>
        </label>
    </p>
</EditForm>

@code {
    private EditContext EditContext;
    private Dictionary<int, string> users = new Dictionary<int, string>();

    private int? UserId { get; set; }
    private bool hightlight { get; set; }

    protected override void OnInitialized()
    {
        EditContext = new EditContext(users);
        EditContext.OnFieldChanged += EditContext_OnFieldChanged;
    }

    protected override void OnParametersSet()
    {
        // simulate getting current data from the db (which would use async version of this event handler)
        users.Clear();
        users.Add(1, "Bob");
        users.Add(2, "Sue");
        users.Add(3, "R2D2");
        UserId = 2;

        base.OnParametersSet();
    }

    private void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
    {
        if (e.FieldIdentifier.FieldName == "UserId")
        {
            DoStuff();
        }
    }

    private void OnChangeUserId(ChangeEventArgs args)
    {
        // select tag's "value" attribute provide one-way data binding; to get 2-way,
        // we need to manually set the value of the UserId based on the ChangeEventArgs
        if (args.Value == null)
            UserId = null;
        else
            UserId = int.Parse(args.Value.ToString());

        DoStuff();
    }

    private void DoStuff()
    {
        hightlight = (UserId == 3);
    }
}

需要一些文字介绍和描述,以便让其他人更容易理解。 - WurmD
@WurmD 已完成。谢谢! - Dennis Jones
很遗憾,使用方法2时,我们无法直接实现InputSelect的双向绑定和更改事件触发... - Christophe Gigax
是的,方法2是一种单向绑定 - 当模型更改时,控件不会更改。 - G. Stoynev
实际上,它们都支持双向数据绑定。你试过示例代码了吗? - Dennis Jones

10

该请求并不算太老,因此您可以考虑以下操作,它可以与EditForm一起使用(基本上是挂接到另一个事件)=> 在oninput事件处理程序中传递ChangeEventArgs变量,因此您可以获取要处理的值。

<InputSelect @bind-Value="@labBook.StockID" @oninput="OnLabbookStockChange" class="form-control">
                            @if (lstStocks != null)
                                {
                                <option value="">select...</option>
                                @foreach (var stock in lstStocks)
                                    {
                                    <option value="@stock.StockID">@stock.TestArticle.TAName</option>
                                    }
                                }
                        </InputSelect>

3
请注意,@oninput事件会在绑定模型/属性更新所选的值之前触发。您可以在ChangedEventArgs.Value中获取所选值。 - Jan Paolo Go

1

可能有点晚了,但我发现最简单的方法是使用@bind-Value="MyProperty",然后在属性中调用setter函数。因此,不需要使用@onchanged。 所以在C#模型中:

private T _myProperty;

public T MyProperty 
{
 get => _myProperty; 
 set => 
 { 
   _myProperty = value;
   MyFunction(); 
 } 
}

只是一个小问题,编译器在与大括号结合使用时,对箭头函数不允许在setter中出现提出了抱怨。但除此之外,这个建议非常好,帮我节省了很多时间,避免了寻找解决方法的麻烦。 - PajamaDuck

1
使用@bind-Value:after,如微软官方资源中所述,
对于您的示例:
               <InputSelect class="form-control form-control form-control-sm"
                         placeholder="Role"
                         disabled="@IsReadOnly"
                         @bind-Value="Model.Role"
                         @bind-Value:after="RoleChanged">
                <option value="Member">Member</option>
                <option value="Admin">Admin</option>
                <option value="Pioner">Pioneer</option>
                <option value="Retailer">Retailer</option>
            </InputSelect>

代码:

bool ShowCreated;
bool ShowUpdated;
bool IsReadOnly;
string SelectedRole;

public EditForm AccountForm;
public Multi.Dtos.RegistrationDto Model = new Dtos.RegistrationDto() { Role = "Member" };

public void RoleChanged()
{
    Console.WriteLine(Model.Role);
}

感谢您对Stack Overflow社区做出贡献的兴趣。这个问题已经有了很多答案,其中一个答案已经得到社区的广泛验证。您确定您的方法之前没有被提到过吗?如果是这样的话,您可以解释一下您的方法有何不同,什么情况下您的方法可能更好,并且/或者为什么您认为之前的答案不够满意。您可以编辑您的答案,提供一些解释吗? - undefined
谢谢你的回复。网络上有很多答案建议使用Value + ValueExpression + ValueChanged,但是你的答案是目前由Microsoft推荐的,并且专门为OP的使用情况设计的。Blazor正在不断发展 :) - undefined

0

不要使用 onchange 事件,你可以尝试使用 oninput 事件。


-5

我使用@onclick事件简单地解决了这个问题。 它会触发多次,但最后一次触发时,它是所选项。

<div>
    <EditForm Model="model">
        <InputSelect @bind-Value="model.SimulatorType" @onclick="SelectionChanged">
            @foreach (var value in Enum.GetValues(typeof(SimulatorType)))
            {
                <option>@value</option>
            }
        </InputSelect>
    </EditForm>
</div>

@code {
    class Model
    {
        public SimulatorType SimulatorType
        {
            get;set;
        }
    }

    Model model = new Model();

    private async void SelectionChanged()
    {
        await OnSelectionChanged.InvokeAsync(model.SimulatorType.ToString());
    }

    [Parameter]
    public EventCallback<string> OnSelectionChanged { get; set; }
}

天啊,请不要这样做。 - Rob King

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接