【问题标题】:Callback when selection made inside my custom select blazor component在我的自定义选择 blazor 组件中进行选择时的回调
【发布时间】:2020-05-17 18:18:44
【问题描述】:

我正在开发 Blazor Wasm。我想创建自己的自定义 select 组件。下面的实现有效,但缺少一些东西:在我的自定义选择组件中选择某些内容时没有回调。我希望能够从我的 TestView.razor 组件中捕获事件。

我的自定义实现 InputSelectCustom.cs

public class InputSelectCustom<T> : InputSelect<T>
{
    protected override string FormatValueAsString(T value)
    {
        // Custom code ommitted for clarity
        return base.FormatValueAsString(value);
    }

    protected override bool TryParseValueFromString(string value, out T result, out string validationErrorMessage) 
    {
        if (typeof(T) == typeof(int) ||
            typeof(T) == typeof(int?))
        {
            if (int.TryParse(value, out var resultInt))
            {
                result = (T)(object)resultInt;
                validationErrorMessage = null;
                return true;
            }
            else
            {
                result = default;
                validationErrorMessage = "The chosen value is not valid.";
                return false;
            }
        }
        else 
        if (typeof(T).IsEnum)
        {
            if (CustomFunctions.EnumTryParse<T>(value, out var resultEnum))
            {
                result = (T)(object)resultEnum;
                validationErrorMessage = null;
                return true;
            }
            else
            {
                result = default;
                validationErrorMessage = "The chosen value is not valid.";
                return false;
            }
        }
        else
        if (typeof(T).IsNullableEnum())
        {
            if (CustomFunctions.NullableEnumTryParse<T>(value, out var resultEnum))
            {
                result = (T)(object)resultEnum;
                validationErrorMessage = null;
                return true;
            }
            else
            {
                result = default;
                validationErrorMessage = "The chosen value is not valid.";
                return false;
            }
        }
        else
        {
            return base.TryParseValueFromString(value, out result, out validationErrorMessage);
        }
    }

    protected override Task OnParametersSetAsync()
    {
        return base.OnParametersSetAsync();
    }
}

我的 TestView.razor

@page "/test-view"

<EditForm Model="CurrentFilterModel">
    <InputSelectCustom @bind-Value="@CurrentFilterModel.ClassmentFilter">
        <option value="null"> Show all content </option>
        <option value="@EnumClassment.Popular"> Popular </option>
        <option value="@EnumClassment.Featured"> Featured </option>
        <option value="@EnumClassment.Novelty"> Novelty </option>
    </InputSelectCustom>
</EditForm>

@code
{
    public FilterModel CurrentFilterModel = new FilterModel();

    public class FilterModel
    {
        public EnumClassment? ClassmentFilter { get; set; }
    }
}

我的枚举:

public enum EnumClassment
{
    Featured,
    Popular,
    Novelty
}

更新

所以我需要一种在我选择的值发生变化时得到通知的方法。

起初,我尝试过:

<InputSelectCustom @bind-Value="CurrentFilterModel.ClassmentFilter">
    <option value=""> Show all content </option>
    <option value="@EnumClassment.Popular"> Popular </option>
    <option value="@EnumClassment.Featured"> Featured </option>
    <option value="@EnumClassment.Novelty"> Novelty </option>
</InputSelectCustom>

所选值已正确更改,但使用上面的代码,我无法收到值更改的通知。根据信息,我需要得到通知,因为每当用户更改选择时,我想立即执行服务器端调用以过滤数据。

下面是我的第二次尝试:

<InputSelectCustom @bind-Value="CurrentFilterModel.ClassmentFilter" ValueChanged="ValueChangedForClassmentFilter">
    <option value=""> Show all content </option>
    <option value="@EnumClassment.Popular"> Popular </option>
    <option value="@EnumClassment.Featured"> Featured </option>
    <option value="@EnumClassment.Novelty"> Novelty </option>
</InputSelectCustom>

如您所见,我不能同时使用@bind-ValueValueChanged

所以这是我的最终解决方案:

<InputSelectCustom Value="@CurrentFilterModel.ClassmentFilter" ValueExpression="@( () => CurrentFilterModel.ClassmentFilter )" ValueChanged="@( (EnumClassment? s) => ValueChangedForClassmentFilter(s) )">
    <option value=""> Show all content </option>
    <option value="@EnumClassment.Popular"> Popular </option>
    <option value="@EnumClassment.Featured"> Featured </option>
    <option value="@EnumClassment.Novelty"> Novelty </option>
</InputSelectCustom>

protected async Task ValueChangedForClassmentFilter(EnumClassment? theUserInput)
{
    // You have to update the model manually because handling the ValueChanged event does not let you use @bind-Value
    // For the validation to work you must now also define the ValueExpression because @bind-Value did it for you
    CurrentFilterModel.ClassmentFilter = theUserInput;
    // Refresh data based on filters
    await FilterCoursesServerSide();
}

这一次,一切都按预期工作:每当用户选择另一个值时,我都会收到新值的通知,我可以立即执行自定义代码。

如果我错了,或者由于我是 Blazor 的新手,或者我可能遗漏了什么,请纠正我。

PS:用字符串、整数和枚举测试。

【问题讨论】:

    标签: blazor blazor-client-side


    【解决方案1】:

    你不能那样做,也不应该那样做。做什么的 ? 但是,您可以像使用单选按钮一样执行此操作,但是您的自定义组件应该从 InputBase 派生,并提供 Razor 标记和逻辑。

    在我的自定义选择组件中选择某些内容时没有回调。

    当然有回调...否则如何使用新值更新您的模型。从 InputBase 组件的 CurrentValue 属性触发:

    protected TValue CurrentValue
        {
            get => Value;
            set
            {
                var hasChanged = !EqualityComparer<TValue>.Default.Equals(value, 
                           Value);
                if (hasChanged)
                {
                    Value = value;
                    _ = ValueChanged.InvokeAsync(value);
                    EditContext.NotifyFieldChanged(FieldIdentifier);
                }
            }
        }
    

    这是更新模型的代码:

    _ = ValueChanged.InvokeAsync(value); 
    

    这是您的自定义选择组件的代码。请注意,InputSelect 不支持诸如 int 之类的数字类型(您不能将其应用于诸如 public int Country { get; set; } 之类的 int 属性。也许(不确定,需要检查...)。枚举类型也是如此。我的组件支持这样的价值:

    InputSelectNumber.cs


    public class InputSelectNumber<T> : InputSelect<T>
    {
        protected override bool TryParseValueFromString(string value, out T 
                                     result, out string validationErrorMessage)
        {
            if (typeof(T) == typeof(int))
            {
                if (int.TryParse(value, out var resultInt))
                {
                    result = (T)(object)resultInt;
                    validationErrorMessage = null;
                    return true;
                }
                else
                {
                    result = default;
                    validationErrorMessage = "The chosen value is not a valid 
                                                                      number.";
                    return false;
                }
            }
            else
            {
                return base.TryParseValueFromString(value, out result, out 
                                                       validationErrorMessage);
            }
        }
     }
    

    用法

      <EditForm EditContext="@EditContext"> 
      <DataAnnotationsValidator />
    
      <div class="form-group">
        <label for="name">Enter your Name: </label>
        <InputText Id="name" Class="form-control" @bind-Value="@comment.Name"> 
                                                                 </InputText>
        <ValidationMessage For="@(() => comment.Name)" />
    
    </div>
    <div class="form-group">
        <label for="body">Select your country: </label>
        <InputSelectNumber @bind-Value="@comment.Country">
            <option value="">Select country...</option>
            <option value="1">USA</option>
            <option value="2">Britain</option>
            <option value="3">Germany</option>
            <option value="4">Israel</option>
        </InputSelectNumber>
    
       <label for="body">Select your country: </label>
        <ValidationMessage For="@(() => comment.Country)" />
    </div>
    
     <p>
        <button type="submit">Submit</button>
     </p>
    </EditForm>
    <p>Name: @comment.Name</p>
    <p>Country: @comment.Country</p>
    
    @code
    {
       private EditContext EditContext;
       private Comment comment = new Comment();
    
       protected override void OnInitialized()
       {
        EditContext = new EditContext(comment);
    
       }
    
       public class Comment
      {
    
        public string Name { get; set; }
        // Note that with the subclassed InputSelectNumber I can use either string 
        // or int. In both cases no error occurs.
        public string Country { get; set; }
        //public int Country { get; set; }
     }
    }
    

    根据请求更新:

    如果我错了,或者由于我是 Blazor 的新手,或者我可能遗漏了什么,请纠正我。

    您的代码很好,您应该这样做。 See my answer here 我对 InputSelect 组件做同样的事情。由于您的自定义组件源自 InputSelect 组件,因此这是最好、最优雅的方式。也许是唯一的方法。

    您对 Blazor 的了解程度如何?;)您的知识超过了这里的大多数用户(我指的是那些在这里永久回答问题的用户)。

    但是,从表面上看,我认为你的方向是错误的。您不需要知道选择组件的值何时改变了您的操作方式...

    相反,您应该为 EditContext 对象公开的 OnFieldChanged 事件实现一个事件处理程序,您可以在其中访问通过事件参数传递的新值、操作数据等,甚至验证 EditContext 对象...

    一些指导你的代码:

     private EditContext EditContext;
     private Comment Model = new Comment();
    
     protected override void OnInitialized()
     {
            EditContext = new EditContext(Model);
            EditContext.OnFieldChanged += EditContext_OnFieldChanged;
    
            base.OnInitialized();
     }
    
     // Note: The OnFieldChanged event is raised for each field in the 
                                                     model
        private void EditContext_OnFieldChanged(object sender, 
                                              FieldChangedEventArgs e)
        {
            Console.WriteLine(e.FieldIdentifier.FieldName);
    
            // more code...
    
        }
    

    希望这会有所帮助...

    【讨论】:

    • 感谢您对我的问题感兴趣。请看看我更新的问题。
    • 您对实施event handler to the OnFieldChanged 的建议对我来说是完美的。很高兴知道这一点。再次感谢。
    • 我创建了我的组件 IDisposable 并添加了 Dispose 方法(因为事件处理程序),但这是另一回事...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-25
    • 1970-01-01
    相关资源
    最近更新 更多