Измененное значение не отправляется родительскому компоненту блейзора

#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 . С An int намного проще работать, так как вы можете легко привести его к перечислению, и база данных в любом случае сохраняет значение как an int . Если родительская страница получает строку, она должна выполнить работу по преобразованию ее в значение enum. Есть ли какой-либо способ обойти это? Еще раз спасибо.#

5. @AvrohomYisroel Дополнительные атрибуты позволяют разбивать атрибуты … Делает ваш компонент намного более гибким. docs.microsoft.com/en-us/aspnet/core/blazor/components /…