如何在Blazor中遍历实现IOldValue接口的直接子组件
实现Blazor父组件批量调用子组件接口方法
我基于input元素实现了一个自定义文本框组件MyTextBox,它实现了IOldValue接口,该接口的SetOldValue()方法可以把当前值保存到OldValue变量里。现在想在父组件里写一个SetAllOldValues()方法,自动遍历所有实现了IOldValue接口的直接子组件,调用它们的SetOldValue()方法,代替现在每个组件都声明引用、逐个调用的繁琐写法(页面组件多的时候特别麻烦)。
想要实现的伪代码逻辑如下:
foreach (var control in this.Controls.OfType<IOldValue>()) { control.SetOldValue(); }
一、现有代码展示
1. IOldValue接口代码
namespace WebUI.Components.Controls; public interface IOldValue { object OldValue { get; set; } object CurrentValue { get; } string FriendlyName { get; set; } bool HasChanges { get; set; } string ReportDifs(); void SetOldValue(); }
2. 自定义MyTextBox组件代码
@implements IOldValue @using Microsoft.AspNetCore.Components @using Microsoft.AspNetCore.Components.Web <input @bind="Value" @bind:event="oninput" /> @code { [Parameter] public string Value { get; set; } [Parameter] public EventCallback<object> ValueChanged { get; set; } private async Task OnInput(ChangeEventArgs e) { Value = e.Value!.ToString()!; await ValueChanged.InvokeAsync(Value); } public object OldValue { get; set; } public object CurrentValue { get { return _currentValue; } } public string FriendlyName { get; set; } // 暂不使用 public bool HasChanges { get; set; } // 暂不使用 private string _currentValue; public string ReportDifs() { return String.Empty; } public void SetOldValue() { OldValue = Value; } }
3. 现有父组件代码(繁琐的逐个调用写法)
<br /> <div class="container col-12"> <div class="row"> <div class="col-2"> <label>前缀</label> </div> <div class="col-4"> <MyTextBox @ref="@_prefix" @bind-Value="@_person.Prefix" /> </div> </div> <div class="row"> <div class="col-2"> <label>名字</label> </div> <div class="col-4"> <MyTextBox @ref="@_firstName" @bind-Value="@_person.FirstName" /> </div> </div> <div class="row"> <div class="col-2"> <label>中间名</label> </div> <div class="col-4"> <MyTextBox @ref="@_middleName" @bind-Value="@_person.MiddleName" /> </div> </div> <div class="row"> <div class="col-2"> <label>姓氏</label> </div> <div class="col-4"> <MyTextBox @ref="@_lastName" @bind-Value="@_person.LastName" /> </div> </div> <div class="row"> <div class="col-2"> <label>后缀</label> </div> <div class="col-4"> <MyTextBox @ref="@_suffix" @bind-Value="@_person.Suffix" /> </div> </div> </div> <br /> <button class="btn btn-primary" type="button" @onclick="ButtonClicked">设置初始值</button> @code { private MyTextBox _prefix = new(); private MyTextBox _firstName = new(); private MyTextBox _middleName = new(); private MyTextBox _lastName = new(); private MyTextBox _suffix = new(); private Person _person = new(); protected override void OnInitialized() { _person = new Person { Prefix = "Mr.", FirstName = "John", MiddleName = "Q", LastName = "Public", Suffix = "Sr." }; //SetAllOldValues(); } private void ButtonClicked(MouseEventArgs e) { SetAllOldValues(); } private void SetAllOldValues() { // 想要实现的伪代码(无法直接运行) // foreach (var control in this.Controls.OfType<IOldValue>()) // { // control.SetOldValue(); // } // 当前可行但繁琐的写法 _prefix.SetOldValue(); _firstName.SetOldValue(); _middleName.SetOldValue(); _lastName.SetOldValue(); _suffix.SetOldValue(); } private class Person { public string Prefix { get; set; } public string FirstName { get; set; } public string MiddleName { get; set; } public string LastName { get; set; } public string Suffix { get; set; } } }
二、解决方案:批量调用实现方式
Blazor没有WinForms那样直接的Controls集合,但可以通过收集子组件引用的方式实现批量操作,以下提供两种方案:
方案1:手动收集组件引用(适合组件数量不多的场景)
- 在父组件的
@code块中定义集合存储子组件:
private List<IOldValue> _oldValueControls = new();
- 给每个
MyTextBox组件添加引用注册逻辑(Blazor 6+支持@ref:after):
<MyTextBox @ref="component" @ref:after="() => RegisterControl(component)" @bind-Value="@_person.Prefix" />
- 实现注册/注销方法:
private void RegisterControl(IOldValue component) { if (component != null && !_oldValueControls.Contains(component)) { _oldValueControls.Add(component); } } private void UnregisterControl(IOldValue component) { if (component != null) { _oldValueControls.Remove(component); } }
- 修改
SetAllOldValues方法遍历集合调用:
private void SetAllOldValues() { foreach (var control in _oldValueControls) { control.SetOldValue(); } }
方案2:封装容器组件自动收集(适合大量组件的场景)
通过CascadingValue让子组件自动注册到容器,无需手动处理每个组件的引用:
- 创建
OldValueContainer容器组件:
@using WebUI.Components.Controls <CascadingValue Value="this"> @ChildContent </CascadingValue> @code { [Parameter] public RenderFragment ChildContent { get; set; } public List<IOldValue> Controls { get; } = new(); public void Register(IOldValue control) { if (!Controls.Contains(control)) { Controls.Add(control); } } public void Unregister(IOldValue control) { Controls.Remove(control); } public void SetAllOldValues() { foreach (var control in Controls) { control.SetOldValue(); } } }
- 修改
MyTextBox组件,自动向容器注册:
@implements IOldValue @using Microsoft.AspNetCore.Components @using Microsoft.AspNetCore.Components.Web @using WebUI.Components.Controls <input @bind="Value" @bind:event="oninput" /> @code { [CascadingParameter] private OldValueContainer Container { get; set; } protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); Container?.Register(this); } public void Dispose() { Container?.Unregister(this); } // 原有组件代码... }
- 父组件使用容器包裹所有
MyTextBox:
<OldValueContainer @ref="container"> <div class="container col-12"> <!-- 所有MyTextBox组件放在这里 --> </div> </OldValueContainer> <button class="btn btn-primary" @onclick="() => container.SetAllOldValues()">设置初始值</button> @code { private OldValueContainer container; private Person _person = new(); // 其他原有代码... }
内容的提问来源于stack exchange,提问作者FirstByte
相关产品推荐
相关产品推荐

