如何在Blazor中编写自定义值更改事件处理程序?

如何在Blazor中编写自定义值更改事件处理程序?,blazor,asp.net-blazor,Blazor,Asp.net Blazor,我想为Blazor编写一个自定义下拉列表组件,部分原因是现有的InputSelect组件不绑定到字符串和枚举类型以外的任何类型。这对我来说还不够好,因为我的模型有int和可为null的int类型属性,我想将它们绑定到下拉列表中。到目前为止,我有: @using System.Globalization @typeparam TValue @typeparam TData @inherits InputBase<TValue> <select id="@Id" @bind=

我想为Blazor编写一个自定义下拉列表组件,部分原因是现有的InputSelect组件不绑定到字符串和枚举类型以外的任何类型。这对我来说还不够好,因为我的模型有int和可为null的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;
    }

}

@使用系统全球化
@类型参数TValue
@typeparam-TData
@继承InputBase
@如果(!string.IsNullOrWhiteSpace(OptionLabel)| | Value==null)
{
@(选项标记“-”选择“-”)
}
@foreach(数据中的var项)
{
@GetPropertyValue(项,TextFieldName)
}
组件值为:@Value
@代码{
[参数]
公共字符串Id{get;set;}
[参数]
公共IEnumerable数据{get;set;}=new List();
[参数]
公共字符串ValueFieldName{get;set;}
[参数]
公共字符串TextFieldName{get;set;}
[参数]
公共字符串选项标签{get;set;}
私有类型ValueType=>IsValueTypeNullable()?Nullable.GetUnderlyingType(typeof(TValue)):typeof(TValue);
受保护的覆盖无效OnInitialized()
{
base.OnInitialized();
验证初始化();
}
私有void ValidateInitialization()
{
if(string.IsNullOrWhiteSpace(ValueFieldName))
{
抛出新ArgumentNullException(nameof(ValueFieldName),$“需要参数{nameof(ValueFieldName)}”);
}
if(string.IsNullOrWhiteSpace(TextFieldName))
{
抛出新ArgumentNullException(nameof(TextFieldName),$“需要参数{nameof(TextFieldName)}”;
}
如果(!HasProperty(ValueFieldName))
{
抛出新异常($“数据类型{typeof(TData)}没有名为{ValueFieldName}的属性);
}
如果(!HasProperty(TextFieldName))
{
抛出新异常($“数据类型{typeof(TData)}没有名为{TextFieldName}的属性);
}
}
受保护的重写bool TryParseValueFromString(字符串值、输出TValue结果、输出字符串验证错误消息)
{
validationErrorMessage=null;
if(ValueType==typeof(string))
{
结果=(TValue)(object)值;
返回true;
}
if(ValueType==typeof(int))
{
if(string.IsNullOrWhiteSpace(value))
{
结果=默认值;
}
其他的
{
if(int.TryParse(value,NumberStyles.Integer,CultureInfo.InvariantCulture,out var parsedValue))
{
结果=(TValue)(object)parsedValue;
}
其他的
{
结果=默认值;
validationErrorMessage=$“指定的值无法转换为类型{typeof(TValue)}”;
返回false;
}
}
返回true;
}
if(ValueType==typeof(Guid))
{
validationErrorMessage=null;
if(string.IsNullOrWhiteSpace(value))
{
结果=默认值;
}
其他的
{
if(Guid.TryParse(值,out var parsedValue))
{
结果=(TValue)(object)parsedValue;
}
其他的
{
结果=默认值;
validationErrorMessage=$“指定的值无法转换为类型{typeof(TValue)}”;
返回false;
}
}
返回true;
}
抛出新的InvalidOperationException($“{GetType()}不支持类型“{typeof(TValue)}”。支持的类型为string、int和Guid。”);
}
私有字符串GetPropertyValue(TData源,字符串propertyName)
{
返回source.GetType().GetProperty(propertyName)?.GetValue(source,null).ToString();
}
私有布尔属性(字符串属性名称)
{
返回typeof(TData).GetProperty(propertyName)!=null;
}
私有布尔值IsValueTypeNullable()
{
返回Nullable.GetUnderlineType(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,那么我所能做的就非常有限

有人能告诉我如何从父组件中的子组件捕获值更改吗

我想我需要一个定制的EventCallback

您确实需要一个
EventCallback
,但问题是,您已经有了一个,只是看不到而已

要能够使用
@bind Value
您需要两个参数,
T Value
EventCallback 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;
    }
}

是的,我也这么认为,但是当我添加ValueChanged=“MyChangeHandler”并使用一个类型为nullable int的参数创建该处理程序时,我得到了以下结果
<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>