【发布时间】:2019-11-25 12:49:12
【问题描述】:
我正在尝试创建一个 Tab 组件,我可以将其数据绑定到单例模型对象。
所以我有一个状态对象
public class AppState
{
private List<TabValue> _tabValues = new List<TabValue>();
private TabValue _selectedValue = null;
public AppState()
{
Add(new TabValue { Title = "Tab 0", Contents = "Contents 0" });
Add(new TabValue { Title = "Tab 1", Contents = "Contents 1" });
Add(new TabValue { Title = "Tab 2", Contents = "Contents 2" });
}
public TabValue[] TabValues { get { return _tabValues.ToArray(); } }
public void Add(TabValue tabValue)
{
this._tabValues.Add(tabValue);
if (_selectedValue == null)
_selectedValue = tabValue;
OnChanged();
}
public TabValue SelectedValue
{
get { return _selectedValue; }
set { _selectedValue = value; OnChanged(); }
}
public event Action Changed;
protected virtual void OnChanged() { if (Changed != null) Changed(); }
}
public class TabValue
{
public string Title { get; set; }
public string Contents { get; set; }
}
我想将此呈现为 Tab 控件,并且我想要 2 路绑定,即当我更改 AppState 对象时,UI 会反映它,而当 UI Tab 控件中的活动选项卡发生更改时,AppState 中的状态为更新了。
@inject AppState MyAppState
@{Debug.WriteLine("Page.BuildRenderTree " + MyAppState.TabValues.Count());}
<TabControl TabPageChanged="@MyTabIndexChanged">
<ChildContent>
@foreach (var tabData in MyAppState.TabValues)
{
Debug.WriteLine("Page.BuildRenderTree TabPage: " + tabData.Title);
<TabPage @key="@tabData" Text="@tabData.Title" Selected="@(MyAppState.SelectedValue == tabData)">
@tabData.Contents
</TabPage>
}
</ChildContent>
</TabControl>
@code {
private void MyTabIndexChanged(int tabIndex)
{
MyAppState.SelectedValue = MyAppState.TabValues[tabIndex];
this.StateHasChanged();
}
protected override void OnInitialized()
{
MyAppState.Changed += this.StateHasChanged;
}
public void Dispose()
{
MyAppState.Changed -= this.StateHasChanged;
}
}
我已将此代码添加到默认 VS 生成项目的“计数器”页面中。
TabControl 代码基于 Blazor-Univerity 代码。
@{Debug.WriteLine("TabControl.BuildRenderTree " + Pages.Count);}
<div class="btn-group" role="group">
@foreach (TabPage tabPage in Pages)
{
Debug.WriteLine("TabControl.BuildRenderTree TabHeader " + tabPage.Text);
<button type="button"
class="btn @GetButtonClass(tabPage)"
@onclick="@(() => OnTabPageChanged(tabPage))">
@tabPage.Text
</button>
}
</div>
<CascadingValue Value="this">
@{Debug.WriteLine("TabControl.BuildRenderTree ChildContent " + Pages.Count);}
@ChildContent
</CascadingValue>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public EventCallback<int> TabPageChanged { get; set; }
List<TabPage> Pages = new List<TabPage>();
public TabPage ActivePage
{
get { return this.Pages.Where(t => t.Selected).FirstOrDefault() ?? this.Pages.FirstOrDefault(); }
}
protected virtual void OnTabPageChanged(TabPage tabPage)
{
TabPageChanged.InvokeAsync(Pages.IndexOf(tabPage));
this.StateHasChanged();
}
internal void AddPage(TabPage tabPage)
{
Pages.Add(tabPage);
}
private string GetButtonClass(TabPage page)
{
return page.Selected ? "btn-primary" : "btn-secondary";
}
protected override void OnInitialized()
{
Debug.WriteLine("TabControl.OnInitialized " + Pages.Count);
base.OnInitialized();
}
}
最后是 TabPage
@{Debug.WriteLine($"TabPage.BuildRenderTree {Text}"); }
@if (Parent.ActivePage == this)
{
<div>
@ChildContent
</div>
}
@code {
private bool _Selected = false;
[CascadingParameter]
private TabControl Parent { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public string Text { get; set; }
[Parameter]
public bool Selected
{
get { return _Selected; }
set
{
if (_Selected == value)
return;
_Selected = value;
StateHasChanged();
}
}
protected override void OnInitialized()
{
if (Parent == null)
throw new ArgumentNullException(nameof(Parent), "TabPage must exist within a TabControl");
Debug.WriteLine($"TabPage.OnInitialized {Text}");
base.OnInitialized();
Parent.AddPage(this);
}
}
所以当我运行它时,它不会显示我的选项卡控件,但在跟踪的帮助下,我添加了明显的原因
Page.BuildRenderTree 3
TabControl.OnInitialized 0 <-- NO TABS
TabControl.BuildRenderTree 0
TabControl.BuildRenderTree ChildContent 0
Page.BuildRenderTree TabPage: Tab 0
Page.BuildRenderTree TabPage: Tab 1
Page.BuildRenderTree TabPage: Tab 2
TabPage.OnInitialized Tab 0
TabPage.OnInitialized Tab 1
TabPage.OnInitialized Tab 2
TabPage.BuildRenderTree Tab 0
TabPage.BuildRenderTree Tab 1
TabPage.BuildRenderTree Tab 2
我似乎在呈现 TabControl 时还不知道它的 TabPages。
我不知道如何解决这个问题......
我已经尝试了很多次迭代和一些第 3 方选项卡控件,我的 UI 似乎总是在更新后刷新(注意按下“计数器”按钮会导致页面按预期刷新)。
所以我的问题是如何确保在呈现 TabControl 时初始化子 TabPages。
目前正在运行 blazor-server-side。
加载时呈现的内容
使用计数器“点击我”按钮强制刷新后呈现的内容
【问题讨论】:
-
如果我仔细阅读了代码,您在单击按钮时设置了页面的
Selected属性,但在TabPage中您测试了Parent.ActivePage。但这并没有改变。在TabPage的Selected属性字段的_Selected上设置测试 -
抱歉,我错过了挂钩 MyAppState.Changed 事件的代码(所以它应该是这样的 - TabPage 按钮单击设置 MyAppState.SelectedValue,它会触发 MyAppState.Changed 事件,从而导致重新渲染),但最初的问题是 TabControl 是在为其提供选项卡之前呈现的,因此它呈现为空。
-
根据您的日志,呈现了 3 个选项卡。
-
但是在呈现 TabControl 的地方,它没有选项卡,因此没有呈现选项卡按钮 (TabControl.BuildRenderTree 0)。然后要求选项卡本身呈现,以便呈现活动选项卡内容,但没有标题按钮。
-
旁注,但“我可以将数据绑定到单例的选项卡组件”是否意味着当您拥有超过 1 个用户时,您对谁控制选项卡有疑问?还有比赛条件?
标签: data-binding blazor blazor-server-side