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

#blazor #blazor-server-side

Вопрос:

У меня есть простой класс InputLength.razor. Он содержит на нем Длину и Тип длины. От родителя я хотел бы получить эти свойства из дочернего компонента InputLength. Я знаю, что могу привязаться к отдельным свойствам, но представьте, что у меня было 10 свойств. Это было бы очень утомительно.

Я хотел бы просто создать экземпляр компонента InputLength, затем передать эту модель дочернему элементу InputLength в качестве связанного параметра, и когда когда-либо будут обновлены какие-либо свойства, я хотел бы, чтобы связанная модель обновлялась на родительском.

См. Код компонента ниже:

 @code {  [Parameter]  public InputLength m { get; set; }  [Parameter]  public EventCallbacklt;InputLengthgt; mChanged { get; set; }  public double _Length;  public double Length { get { return _Length; } set { _Length = value; m.Length = value; mChanged.InvokeAsync(m); } }   public LengthType _SelectedLengthType;  public LengthType SelectedLengthType { get { return _SelectedLengthType; } set { _SelectedLengthType = value; mChanged.InvokeAsync(m); } }   public enum LengthType  {  Inches,  Centimeters,  }; }  

Как вы можете видеть, для облегчения этого я попытался просто создать свойство m типа InputLength в компоненте InputLength, и это единственное свойство. На родителя я привязываюсь к нему

 lt;InputLength bind-m=inputLengthInstancegt;lt;/InputLengthgt;  

По какой-то причине, когда он обновляет значение m.Length (до того, как я вызвал mChanged), кажется, что он создает новый компонент и на самом деле никогда не доходит до вызова mChanged.Вызовите синхронизацию. Если я войду вручную, переместю точку останова и отрегулирую значение объекта, и вызову синхронизацию, это полностью сработает, так что это своего рода правильный путь.

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

Что я делаю не так? Похоже, я написал много кода, чтобы попытаться выполнить довольно простую задачу. Все руководства на веб-сайте microsofts предполагают, что вы просто привязываетесь ко всем основным свойствам, которые вам нужны, что, опять же, работает, но кажется ненужным. Вот почему существуют классы. Просто свяжись со всем вонючим классом.

Anyone have suggestions to make this work, hopefully in a cleaner way that makes sense?

Ответ №1:

Если я правильно понял, вы хотите создать редактор для класса под названием Length и использовать его в разных местах.

Определите класс данных — я его назвал Length .

 public class Length  {  public double LengthValue { get; set; }  public UnitType Units { get; set; }  public string DisplayValue =gt; $"{this.LengthValue} {this.Units}";  public enum UnitType  {  Inches,  Centimeters,  };  }  

Определите и отредактируйте компонент — я назвал его длиной ввода.

 lt;div class="m-2 p-2"gt;  lt;InputNumber @bind-Value=LengthValuegt;lt;/InputNumbergt;  lt;InputSelect @bind-Value=UnitsValuegt;  @foreach (var lt in Enum.GetValues(typeof(Length.UnitType)))  {  lt;optiongt;@ltlt;/optiongt;  }  lt;/InputSelectgt;  lt;/divgt; lt;div class="m-2 p-2"gt;  lt;br /gt;Component Value: @Value.DisplayValue lt;/divgt; @code {  [Parameter] public Length Value { get; set; }  [Parameter] public EventCallbacklt;Lengthgt; ValueChanged { get; set; }   private Length _length;  private double LengthValue  {  get =gt; _length.LengthValue;  set  {  if (value != _length.LengthValue)  {  _length.LengthValue = value;  this.InvokeModelChanged();  }  }  }   private Length.UnitType UnitsValue  {  get =gt; _length.Units;  set  {  if (value != _length.Units)  {  _length.Units = value;  this.InvokeModelChanged();  }  }  }   protected override void OnInitialized()  {  _length = new Length() { LengthValue = this.Value.LengthValue, Units = this.Value.Units };  }   private void InvokeModelChanged()  {  if (ValueChanged.HasDelegate)  ValueChanged.InvokeAsync(_length);  } }  

Вот форма редактирования для демонстрации.

 @page "/NewEditor1" @page "/"  lt;h3gt;EditFormlt;/h3gt;  lt;EditForm EditContext="this._editContext" OnValidSubmit="SubmitForm"gt;  lt;div class="p-2"gt;  Long Side: lt;InputLength @bind-Value="_model.LongSide" /gt;  lt;/divgt;  lt;div class="p-2"gt;  Short Side: lt;InputLength @bind-Value="_model.ShortSide" /gt;  lt;/divgt;  lt;div class="m-2 p-2"gt;  lt;button class="btn btn-success" type="submit"gt;Submitlt;/buttongt;  lt;/divgt; lt;/EditFormgt; lt;div class="m-2 p-2"gt;Form Long Side Value: @_model.LongSide.DisplayValue lt;/divgt; lt;div class="m-2 p-2"gt;Form Short Side Value: @_model.ShortSide.DisplayValue lt;/divgt;  @code {   public class Model  {  public Length LongSide { get; set; } = new Length() { Units = Length.UnitType.Centimeters, LengthValue = 0 };  public Length ShortSide { get; set; } = new Length() { Units = Length.UnitType.Centimeters, LengthValue = 0 };  }  private string message;  Model _model = new Model();   EditContext _editContext;   protected override void OnInitialized()  {  _editContext = new EditContext(_model);  }   void SubmitForm()  {  message = $"Form Submitted at :{DateTime.Now.ToLongTimeString()}";  var x = true;  } }  

Обратите внимание, что это очень упрощенный элемент управления вводом. Он не обрабатывает передачу изменений редактирования в контекст редактирования или взаимодействие с проверкой.

Комментарии:

1. Я согласен, что в настоящее время это правильный способ сделать это. Мне просто не нравится это дублирование.

2. Ха-ха, прекрасно. Принято. Я отметил одно изменение, которое я хотел бы внести в своем посте ниже этого. Если вы расширяете класс данных, вы можете просто добавить / переопределить то, что вам нужно, чтобы привязка работала без необходимости перекодирования.

Ответ №2:

Хитрость в том, чтобы сделать эту работу наиболее разумной до сих пор, состоит в том, чтобы изменить ваше представление о том, как должны работать публичные и частные версии свойств. Вашей публичной собственностью будет то, что вы хотите, чтобы модель представляла (таким образом, ее можно настроить с помощью экземпляра модели). Соответствующим частным свойством должно быть то, что вы привязываете к своему полю ввода. В наборе вы можете обновить общедоступное свойство модели, а затем запустить событие изменения. Это впоследствии обновит объект модели на родительском объекте и передаст обновленные значения обратно дочернему объекту для визуального обновления в обоих местах.

Опять же, цель этого состояла в том, чтобы не нужно было дважды представлять модель как отдельный класс и как класс razorComponent. С помощью этого дизайна я создал компонент razor, который можно использовать в качестве входных данных для остальной части кода.

 lt;InputNumber @bind-Value=_Lengthgt;lt;/InputNumbergt; lt;InputSelect @bind-Value=_SelectedLengthTypegt;  @foreach (var lt in Enum.GetValues(typeof(LengthType)))  {  lt;optiongt;@ltlt;/optiongt;  } lt;/InputSelectgt; lt;br /gt;On Child: @Model.Length ; @Model.SelectedLengthType @code {  [Parameter]  public InputLength Model { get; set; }  [Parameter]  public EventCallbacklt;InputLengthgt; ModelChanged { get; set; }   public double Length;  private double _Length  {  get { return Model.Length; }  set { Model.Length = value; ModelChanged.InvokeAsync(Model); }  }   public LengthType SelectedLengthType;  private LengthType _SelectedLengthType  {  get { return Model.SelectedLengthType; }  set { Model.SelectedLengthType = value; ModelChanged.InvokeAsync(Model); }  }   public enum LengthType  {  Inches,  Centimeters,  }; }  

Комментарии:

1. Если я правильно читаю, у вас есть класс данных, который также является компонентом пользовательского интерфейса. Мне кажется, это безумная идея. Почему? Нет разделения задач, данных и пользовательского интерфейса вместе взятых. У вас есть класс данных с целым набором багажа — я могу позвонить SetParametersAsync по своим данным? Что он делает? Возможно, я, конечно, неправильно понял код!

2. Нет, я думаю, ты прав. Я бы согласился с тем, что бывают ситуации, когда багажа может быть слишком много. Если честно, мне не нравится, что в объекте должен быть объект модели. Однако пользовательский интерфейс в этом случае в основном является классом данных. Так что, что касается разделения проблем, я на самом деле не вижу, чтобы проблемы были очень разными. Мне нужен этот пользовательский интерфейс специально для заполнения этого класса данных и ничего больше. ИТАК, имо, зачем создавать одни и те же свойства в 2 отдельных классах, а затем передавать эти свойства из класса данных непосредственно в точно такие же свойства моего класса представления?

3. И я просто хочу повторить, я согласен с вашими проблемами с дополнительным багажом. Мне тоже очень нравится твое решение. Я не знал, что могу использовать изменение ценности. ХасДелегейт. Спасибо! Независимо от этого, дублирование меня немного беспокоит, и мне не нравится, что оно требуется для того, чтобы убедиться, что все свойства модели могут передаваться в компонент (представление), специально разработанный для класса данных, и из него. Например, если бы у этого класса данных было 10 свойств, то это было бы 20 объявлений этих свойств в двух классах, и, конечно, это могло бы быть редкостью, это могло бы произойти.

4. Проблема смешивания данных и пользовательского интерфейса не часто возникает сразу, это происходит в будущем, когда вы хотите что-то изменить и/или кто-то другой работает над вашим кодом. Часто для разделения проблем требуется больше кода. Поскольку вы не показали, как вы используете/сохраняете длину, я догадался.

5. Я думаю, я думал об этом прошлой ночью, и мы могли бы обновить ваш код, просто расширив класс данных в компоненте razor. Тогда у нас были бы все свойства, которые мне нужны, без необходимости их перекодировать. Спасибо!