上次的Select能用,但有缺陷,当值改变时,没有引发一个属于EditForm的值改变事件,就是说还没有连接到表单,功能不完善。另外,像较为低层的input、select控件,其值只能接受string或其更低层的object,其他类型的结构如数字(如int、short)、日期等都需要进行转换为string赋给它,值改变了再转换后传回来,这个可以看看InputBase源码就明白了,地址:aspnetcore/src/Components/Web/src/Forms/InputBase.cs at main · dotnet/aspnetcore · GitHub
所以我这个Select只接受Value为string类型的值,把转换工作移到外面来,因为放到内部转换不是很可行。这个看看原生的InputSelect就明白了,它根本只给了个ChildContent,像option这些东西需要自己另外弄,遭到了很多人的吐槽。暴露在外边,就没有转换这个任务了,但写代码的任务更多了。编写基础组件是为了更简便、清晰、实用,只是使用起来需要更多的知识技能,只要掌握了就好。
代码:
@implements IDisposable
<div class="d-md-flex">
<label class="control-label mt-2" style="width:@LabelWidth ;text-align-last:justify;"><b>@Label</b></label>
<div style="width:@SelectWidth">
<select value="@Value" @onchange="SelectedChanaged" class="form-select @cssDanger" disabled="@Disabled">
@if (HasAll)
{
<option value="@string.Empty">全部</option>
}
@foreach (var item in Source)
{
<option value="@item.Value">@item.DisplayName</option>
}
</select>
</div>
</div>
@code {
[CascadingParameter]
private EditContext? CascadedEditContext { get; set; }
[Parameter]
public required string Label { get; set; }
[Parameter]
public required string Value { get; set; }
[Parameter]
public string LabelWidth { get; set; } = "80";//Label 四个字的居多,80px刚好,2个字的为50px,三个字与四个字对齐,使用的是字符平均分布
[Parameter]
public string SelectWidth { get; set; } = "200";
[Parameter]
public required IEnumerable<ISelectItem> Source { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
[Parameter]
public Expression<Func<string>>? ValueExpression { get; set; }
[Parameter]
public bool Disabled { get; set; }
[Parameter]
public bool HasAll { get; set; }
public SelectBox()
{
_validationStateChangedHandler = OnValidateStateChanged;
}
private readonly EventHandler<ValidationStateChangedEventArgs> _validationStateChangedHandler;
private bool _hasInitializedParameters;
private void SelectedChanaged(ChangeEventArgs e)
{
if (e.Value != null)
{
var value = (string)e.Value;
_ = ValueChanged.InvokeAsync(value);
EditContext?.NotifyFieldChanged(FieldIdentifier);
}
}
private string cssDanger = "";
protected EditContext EditContext { get; set; } = default!;
protected internal FieldIdentifier FieldIdentifier { get; set; }
protected override void OnParametersSet()
{
base.OnParametersSet();
if (!_hasInitializedParameters)
{
if (ValueExpression == null)
{
throw new InvalidOperationException($"SelectBox requires a value for the 'ValueExpression'" +
$"parameter. Normally this is provided automatically when using 'bind-Value'.");
}
FieldIdentifier = FieldIdentifier.Create(ValueExpression);
if (CascadedEditContext != null)
{
EditContext = CascadedEditContext;
EditContext.OnValidationStateChanged += _validationStateChangedHandler;
}
_hasInitializedParameters = true;
}
LabelWidth = LabelWidth.Contains("px") ? LabelWidth : LabelWidth + "px";
SelectWidth = SelectWidth.Contains("px") ? SelectWidth : SelectWidth + "px";
Label = GetLabel(Label);
}
private void OnValidateStateChanged(object? sender, ValidationStateChangedEventArgs eventArgs)
{
if (EditContext is null)
{
return;
}
if (EditContext.GetValidationMessages(FieldIdentifier).Any())
{
cssDanger = "border-danger";//border-danger 红色浅些,可以改为使用项目原生site.css 中的invalid。
}
else
{
cssDanger = "";
}
StateHasChanged();
}
private static string GetLabel(string label)
{
if (string.IsNullOrWhiteSpace(label))
{
return string.Empty;
}
if (label.Contains(':'))
{
return label;
}
return label + ":";
}
public void Dispose()
{
if (EditContext is not null)
{
EditContext.OnValidationStateChanged -= _validationStateChangedHandler;
}
GC.SuppressFinalize(this);
}
}
ISelectItem.cs
public interface ISelectItem
{
string Value { get; set; }
string DisplayName { get; }
}
SelectItem.cs(也可以使用其他继承了ISelectItem接口的类)
[Serializable]
public class SelectItem : ISelectItem
{
private string? name;
private IList<SelectItem>? childItems;
public required string Value { get; set; }
public string DisplayName
{
get => name == null ? Value : name;//如果DisplayName值未设置,显示Value值。
set => name = value ?? throw new NullReferenceException("显示值不能为空!");
}
//这个是为Select级联使用的
public IList<SelectItem> ChildItems
{
get => childItems ??= new List<SelectItem>();
set => childItems = value;
}
}
外部转换工作示例:
public int Id { get; set; }
public string ID //使用这个属性绑定
{
get => Id.ToString();
set => Id = value == "" ? 0 : int.Parse(value);
}