3

我想为 Blazor 编写一个自定义下拉列表组件,部分原因是现有的 InputSelect 组件不绑定到字符串和枚举类型以外的任何东西。这对我来说还不够好,因为我的模型具有我希望绑定到下拉列表的 int 和可为空的 int 类型属性。到目前为止,我有这个:

@using System.Globalization

@typeparam TValue
@typeparam TData

@inherits InputBase<TValue>

<select id="@Id" @bind="CurrentValueAsString" class="f-select js-form-field">
    @if (!string.IsNullOrWhiteSpace(OptionLabel) || Value == null)
    {
        <option value="">@(OptionLabel ?? "-- SELECT --")</option>
    }
    @foreach (var item in Data)
    {
        <option value="@GetPropertyValue(item, ValueFieldName)">@GetPropertyValue(item, TextFieldName)</option>
    }
</select>
<span>Component Value is: @Value</span>

@code {

    [Parameter]
    public string Id { get; set; }

    [Parameter]
    public IEnumerable<TData> Data { get; set; } = new List<TData>();

    [Parameter]
    public string ValueFieldName { get; set; }

    [Parameter]
    public string TextFieldName { get; set; }

    [Parameter]
    public string OptionLabel { get; set; }

    private Type ValueType => IsValueTypeNullable() ? Nullable.GetUnderlyingType(typeof(TValue)) : typeof(TValue);

    protected override void OnInitialized()
    {
        base.OnInitialized();
        ValidateInitialization();
    }

    private void ValidateInitialization()
    {
        if (string.IsNullOrWhiteSpace(ValueFieldName))
        {
            throw new ArgumentNullException(nameof(ValueFieldName), $"Parameter {nameof(ValueFieldName)} is required.");
        }
        if (string.IsNullOrWhiteSpace(TextFieldName))
        {
            throw new ArgumentNullException(nameof(TextFieldName), $"Parameter {nameof(TextFieldName)} is required.");
        }
        if (!HasProperty(ValueFieldName))
        {
            throw new Exception($"Data type {typeof(TData)} does not have a property called {ValueFieldName}.");
        }
        if (!HasProperty(TextFieldName))
        {
            throw new Exception($"Data type {typeof(TData)} does not have a property called {TextFieldName}.");
        }
    }

    protected override bool TryParseValueFromString(string value, out TValue result, out string validationErrorMessage)
    {
        validationErrorMessage = null;
        if (ValueType == typeof(string))
        {
            result = (TValue)(object)value;
            return true;
        }
        if (ValueType == typeof(int))
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                result = default;
            }
            else
            {
                if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedValue))
                {
                    result = (TValue)(object)parsedValue;
                }
                else
                {
                    result = default;
                    validationErrorMessage = $"Specified value cannot be converted to type {typeof(TValue)}";
                    return false;
                }
            }
            return true;
        }
        if (ValueType == typeof(Guid))
        {
            validationErrorMessage = null;
            if (string.IsNullOrWhiteSpace(value))
            {
                result = default;
            }
            else
            {
                if (Guid.TryParse(value, out var parsedValue))
                {
                    result = (TValue)(object)parsedValue;
                }
                else
                {
                    result = default;
                    validationErrorMessage = $"Specified value cannot be converted to type {typeof(TValue)}";
                    return false;
                }
            }
            return true;
        }

        throw new InvalidOperationException($"{GetType()} does not support the type '{typeof(TValue)}'. Supported types are string, int and Guid.");
    }

    private string GetPropertyValue(TData source, string propertyName)
    {
        return source.GetType().GetProperty(propertyName)?.GetValue(source, null).ToString();
    }

    private bool HasProperty(string propertyName)
    {
        return typeof(TData).GetProperty(propertyName) != null;
    }

    private bool IsValueTypeNullable()
    {
        return Nullable.GetUnderlyingType(typeof(TValue)) != null;
    }

}

在父组件中,我可以像这样使用它:

<DropDownList Id="@nameof(Model.SelectedYear)"
    @bind-Value="Model.SelectedYear"
    Data="Model.Years"
    ValueFieldName="@nameof(Year.Id)"
    TextFieldName="@nameof(Year.YearName)">
</DropDownList>

这很好用,模型绑定到下拉列表,并且当下拉列表值更改时,父模型上的值也会更改。但是我现在想在我的父母身上捕获这个值变化事件并做一些自定义逻辑,主要是根据所选年份加载一些额外的数据。我的猜测是我需要一个自定义 EventCallback 但我尝试的一切都会导致某种构建或运行时错误。似乎如果我的组件继承自 InputBase,那么我能做的事情就非常有限。

谁能告诉我如何捕获父组件中子组件的值变化?

4

1 回答 1

4

我的猜测是我需要一个自定义 EventCallback

你肯定需要一个EventCallback,但问题是,你已经有了一个,只是没有看到它。

为了能够使用@bind-Value,您需要两个参数,T Value并且EventCallback<T> ValueChanged.

当您传递 时@bind-Foo,blazor 会设置这两个参数,Foo并且FooChanged在其中FooChanged它会简单地将新值设置为Foo.

因此,当您执行@bind-Foo="Bar"blazor 在幕后所做的事情时,传递这两个参数

Foo="@Bar"
FooChanged="@(newValue => Bar = newValue)"

所以在你的情况下,你需要做的是传递你自己的ValueChanged函数,它设置新的值,Value但也做一些你想要的额外的事情。

<DropDownList Id="@nameof(Model.SelectedYear)"
    Value="Model.SelectedYear"
    ValueChanged="@((TYPE_OF_VALUE newValue) => HandleValueChanged(newValue))"
    Data="Model.Years"
    ValueFieldName="@nameof(Year.Id)"
    TextFieldName="@nameof(Year.YearName)">
</DropDownList>

@code 
{
    void HandleValueChanged(TYPE_OF_VALUE newValue)
    {
        // do what you want to do 

        // set the newValue if you want
        Model.SelectedYear = newValue;
    }
}

TYPE_OF_VALUE中,您只需将其替换为Model.SelectedYear.

您可以在docs中查看此说明。

编辑

因为您想使用可为空的类型,所以您还需要传递FooExpressionwhich 在您的情况下将是Expression<Func<T>> ValueExpression.

<DropDownList Id="@nameof(Model.SelectedYear)"
    Value="Model.SelectedYear"
    ValueChanged="@((TYPE_OF_VALUE newValue) => HandleValueChanged(newValue))"
    ValueExpression="@(() => Model.SelectedYear)"
    Data="Model.Years"
    ValueFieldName="@nameof(Year.Id)"
    TextFieldName="@nameof(Year.YearName)">
</DropDownList>
于 2020-06-11T12:02:40.373 回答