#blazor
#blazor
Вопрос:
Примечание. Чтобы было понятно, что я делаю, я добавил пример проекта на github. Это показывает использование
FormRowText
компонента, который работает нормально, иFormRowDropdownEnum
компонента, который не работает. Если кто-нибудь сможет загрузить этот проект и посмотреть, что я сделал не так, это было бы здорово.
Я пытаюсь написать серию повторно используемых компонентов Blazor для размещения элементов управления формой. Большинство из них работают (например, для an <input>
), но у меня возникают проблемы. Идея состоит в том, чтобы передать enum
тип ‘s в качестве параметра, и компонент создаст a <select>
на основе enum
значений.
Я создал общий компонент следующим образом (сначала .razor)…
@typeparam T
<div class="form-group row">
<label for="@PropertyName" class="col-lg-2 col-form-label">@(Caption ?? PropertyName)</label>
<div class="col-lg-10 input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="far fa-@Icon"></i>
</span>
</div>
<select class="form-control" name="@PropertyName" id="@PropertyName" @onchange="@OnChanged">
@foreach (T v in Enum.GetValues(typeof(T)).Cast<T>()) {
if ((int)Enum.Parse(typeof(T), v.ToString()) == Value) {
<option value="@Convert.ToInt32(v)" selected>@Enum.GetName(typeof(T), v)</option>
} else {
<option value="@Convert.ToInt32(v)">@Enum.GetName(typeof(T), v)</option>
}
}
</select>
</div>
</div>
Код, лежащий в основе, выглядит следующим образом…
public partial class FormRowDropdownEnum<T> where T : Enum, IConvertible {
[Parameter]
public int Value { get; set; }
[Parameter]
public EventCallback<int> ValueChanged { get; set; }
[Parameter]
public string PropertyName { get; set; }
[Parameter]
public string Caption { get; set; }
[Parameter]
public string Icon { get; set; }
private async Task OnChanged(ChangeEventArgs cea) {
int val = (int)Enum.Parse(typeof(T), cea.Value?.ToString() ?? "0");
await ValueChanged.InvokeAsync(val);
}
}
Оно используется следующим образом (при условии, что _customer
это модель Frequency
, у которой есть параметр типа Frequency
(an enum
)…
<FormRowDropdownEnum PropertyName="@nameof(Customer.Frequency)"
T="Frequency" @bind-Value="(int)_customer.Frequency" Icon="wave-sine" />
Это отображает <select>
правильно и устанавливает соответствующее <option>
значение в зависимости от параметра модели Frequency
.
Когда выбор изменяется в <select>
, OnChanged
вызывается метод, и правильное значение используется в качестве параметра для обратного вызова события. Однако страница Blazor, на которой содержится этот компонент (то есть <FormRowDropdownEnum>
страница с тегом, показанным выше), никогда не видит новое значение, и модель там не обновляется. Я поставил точку останова на сеттере модели, но она так и не была достигнута.
Как я уже сказал, у меня есть похожие компоненты для других типов ввода, и они работают нормально, поэтому я не уверен, что я здесь сделал не так.
Кто-нибудь может помочь? Спасибо
ОБНОВЛЕНИЕ После ответа enet я попытался добавить закрытую переменную в коде и изменить ее при изменении входного значения…
private int _value;
protected override void OnParametersSet() =>
_value = Value;
private async Task OnChanged(ChangeEventArgs cea) {
int val = (int)Enum.Parse(typeof(T), cea.Value?.ToString() ?? "0");
_value = val;
await ValueChanged.InvokeAsync(val);
}
Я изменил разметку Razor, чтобы использовать _value
вместо Value
следующего…
if ((int)Enum.Parse(typeof(T), v.ToString()) == _value) {
Однако результатом этого стало то, что когда я изменил выбор в выпадающем списке, он был немедленно возвращен к предыдущему значению, и родительская страница никогда не видела нового значения.
Ответ №1:
FormRowDropdownEnum.razor
@typeparam TEnum
<div class="form-group row">
<label for="@PropertyName" class="col-lg-2 col-form-label">@(Caption ?? PropertyName)</label>
<div class="col-lg-10 input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="far fa-@Icon"></i>
</span>
</div>
<select class="form-control" name="@PropertyName" id="@PropertyName" @bind="Value">
@foreach (var enumValue in EnumValues)
{
@ChildContent(enumValue)
}
</select>
</div>
</div>
FormRowDropdownEnum.razor.cs
public partial class FormRowDropdownEnum<TEnum>
{
private TEnum _value;
[Parameter] public string PropertyName { get; set; }
[Parameter] public string Caption { get; set; }
[Parameter] public string Icon { get; set; }
[Parameter]
public TEnum Value
{
get => _value;
set
{
if (_value.Equals(value)) return;
_value = value;
if (ValueChanged.HasDelegate)
{
ValueChanged.InvokeAsync(Value);
}
}
}
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> AdditionalAttributes { get; set; }
[Parameter] public RenderFragment<TEnum> ChildContent { get; set; }
[Parameter] public EventCallback<TEnum> ValueChanged { get; set; }
IEnumerable<TEnum> EnumValues => Enum.GetValues(typeof(TEnum)).Cast<TEnum>();
protected override void OnInitialized()
{
if (Value is not Enum) throw new ArgumentException("Value must be of type Enum");
}
}
Index.razor
...
<FormRowDropdownEnum PropertyName="@nameof(Customer.Frequency)" @bind-Value="_customer.Frequency" Icon="wave-sine" Context="freq">
<option value="@freq">@freq</option>
</FormRowDropdownEnum>
...
@code {
readonly Customer _customer = new Customer {
Name = "Jim",
Email = "jim@spriggs.org",
Frequency = Frequency.Monthly
};
private string _msg = "";
private void OnSubmit() =>
_msg = $"Customer name: {_customer.Name}, email: {_customer.Email}, frequency: {_customer.Frequency}";
}
Комментарии:
1. Спасибо за ответ, но я не смог заставить это работать. Я получил ошибку компилятора «Дочерний элемент контекста ‘ChildContent’ компонента’InputEnum’ использует то же имя параметра (‘context’), что и заключающий дочерний элемент содержимого ‘ChildContent’ компонента ‘EditForm’. Укажите имя параметра, например: ‘<Контекст дочернего содержимого =»another_name»>, чтобы устранить двусмысленность». Извините, если я тупой, но я довольно новичок в Blazor и не уверен, что это значит.
2. Кроме того, меня смущает ваше использование. Если вы посмотрите на мой первоначальный вопрос, вы увидите, как я использую другие компоненты. Я хочу, чтобы это работало так же. Еще раз спасибо
3. Если вы используете его в другом контексте, вам нужно назвать один из них.
4. Спасибо за обновленный проект. Я вижу, что это работает, но я не понимаю ваш код. Где в Index.razor вы указываете компоненту тип перечисления? В моем коде у меня был
T
параметр, но вы его удалили. Кроме того, ваш код возвращает значение enum в виде строки, напримерOneOff
, тогда как мой вернул его какint
. С Anint
намного проще работать, так как вы можете легко привести его к перечислению, и база данных в любом случае сохраняет значение как anint
. Если родительская страница получает строку, она должна выполнить работу по преобразованию ее в значение enum. Есть ли какой-либо способ обойти это? Еще раз спасибо.#5. @AvrohomYisroel Дополнительные атрибуты позволяют разбивать атрибуты … Делает ваш компонент намного более гибким. docs.microsoft.com/en-us/aspnet/core/blazor/components /…