Skip to content

Commit 71a4aba

Browse files
committed
fix(TabBar): dp exceptions when using TBI as ItemTemplate root
1 parent 36b844a commit 71a4aba

File tree

3 files changed

+93
-33
lines changed

3 files changed

+93
-33
lines changed

src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs

+20
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,26 @@ namespace Uno.Toolkit.RuntimeTests.Tests
3636
[RunsOnUIThread]
3737
internal partial class TabBarTests // test cases
3838
{
39+
[TestMethod]
40+
public async Task TabBar1285_ICS_With_TBI_ItemTemplate()
41+
{
42+
// note: this bug doesnt happen with ItemsSource = [TBI,...]
43+
// because IsItemItsOwnContainerOverride=true. It only occurs
44+
// with the ItemTemplate>DataTemplate>TBI setup (IsUsingOwnContainerAsTemplateRoot),
45+
// which cause a ContentPresnter to be created as the item container.
46+
var source = Enumerable.Range(0, 1).ToArray();
47+
var SUT = new TabBar
48+
{
49+
ItemsSource = source,
50+
ItemTemplate = XamlHelper.LoadXaml<DataTemplate>("""
51+
<DataTemplate>
52+
<utu:TabBarItem Content="{Binding}" />
53+
</DataTemplate>
54+
"""),
55+
};
56+
await UnitTestUIContentHelperEx.SetContentAndWait(SUT);
57+
}
58+
3959
[TestMethod]
4060
[DataRow(new int[0], null)]
4161
[DataRow(new[] { 1 }, 1)]

src/Uno.Toolkit.UI/Controls/TabBar/TabBar.cs

+71-33
Original file line numberDiff line numberDiff line change
@@ -73,32 +73,74 @@ protected override DependencyObject GetContainerForItemOverride()
7373

7474
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
7575
{
76-
base.PrepareContainerForItemOverride(element, item);
76+
if (IsUsingOwnContainerAsTemplateRoot && element is ContentPresenter cp)
77+
{
78+
// ItemsControl::PrepareContainerForItemOverride will apply the ItemContainerStyle to the element which is not something we want here,
79+
// since it can throw: The DP [WrongDP] is owned by [Control] and cannot be used on [ContentPresenter].
80+
// While this doesnt break the control or the visual, it can cause a scaling performance degradation.
81+
82+
cp.ContentTemplate = ItemTemplate;
83+
cp.ContentTemplateSelector = ItemTemplateSelector;
84+
85+
cp.DataContext = item;
86+
SetContent(cp, item);
7787

78-
void SetupTabBarItem(TabBarItem item)
88+
if (cp.ContentTemplateRoot is TabBarItem tbi)
89+
{
90+
ApplyContainerStyle(tbi);
91+
SetupTabBarItem(tbi);
92+
}
93+
}
94+
else
7995
{
80-
item.IsSelected = IsSelected(IndexFromContainer(element));
81-
item.Click += OnTabBarItemClick;
82-
item.IsSelectedChanged += OnTabBarIsSelectedChanged;
96+
base.PrepareContainerForItemOverride(element, item);
97+
if (element is TabBarItem tbi)
98+
{
99+
SetupTabBarItem(tbi);
100+
}
83101
}
84102

85-
if (element is TabBarItem container)
103+
void SetContent(ContentPresenter cp, object item)
86104
{
87-
SetupTabBarItem(container);
105+
if (string.IsNullOrEmpty(DisplayMemberPath))
106+
{
107+
cp.Content = item;
108+
}
109+
else
110+
{
111+
cp.SetBinding(ContentPresenter.ContentProperty, new Binding
112+
{
113+
Source = item,
114+
Path = DisplayMemberPath,
115+
});
116+
}
88117
}
89-
else if (IsUsingOwnContainerAsTemplateRoot &&
90-
element is ContentPresenter outerContainer)
118+
void ApplyContainerStyle(TabBarItem tbi)
91119
{
92-
var templateRoot = outerContainer.ContentTemplate.LoadContent();
93-
if (templateRoot is TabBarItem tabBarItem)
120+
var localStyleValue = tbi.ReadLocalValue(FrameworkElement.StyleProperty);
121+
var isStyleSetFromTabBar = tbi.IsStyleSetFromTabBar;
122+
123+
if (localStyleValue == DependencyProperty.UnsetValue || isStyleSetFromTabBar)
94124
{
95-
outerContainer.ContentTemplate = null;
96-
SetupTabBarItem(tabBarItem);
97-
tabBarItem.DataContext = item;
98-
tabBarItem.Style ??= ItemContainerStyle;
99-
outerContainer.Content = tabBarItem;
125+
var style = ItemContainerStyle ?? ItemContainerStyleSelector?.SelectStyle(item, tbi);
126+
if (style is { })
127+
{
128+
tbi.Style = style;
129+
tbi.IsStyleSetFromTabBar = true;
130+
}
131+
else
132+
{
133+
tbi.ClearValue(FrameworkElement.StyleProperty);
134+
tbi.IsStyleSetFromTabBar = false;
135+
}
100136
}
101137
}
138+
void SetupTabBarItem(TabBarItem tbi)
139+
{
140+
tbi.IsSelected = IsSelected(IndexFromContainer(element));
141+
tbi.Click += OnTabBarItemClick;
142+
tbi.IsSelectedChanged += OnTabBarIsSelectedChanged;
143+
}
102144
}
103145

104146
internal virtual bool IsSelected(int index)
@@ -108,30 +150,26 @@ internal virtual bool IsSelected(int index)
108150

109151
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
110152
{
111-
base.ClearContainerForItemOverride(element, item);
112-
113-
void TearDownTabBarItem(TabBarItem item)
153+
if (IsUsingOwnContainerAsTemplateRoot && element is ContentPresenter cp)
114154
{
115-
item.Click -= OnTabBarItemClick;
116-
item.IsSelectedChanged -= OnTabBarIsSelectedChanged;
117-
if (!IsUsingOwnContainerAsTemplateRoot)
155+
if (cp.ContentTemplateRoot is TabBarItem tbi)
118156
{
119-
item.Style = null;
157+
TearDownTabBarItem(tbi);
120158
}
121159
}
122-
if (element is TabBarItem container)
160+
else
123161
{
124-
TearDownTabBarItem(container);
125-
}
126-
else if (IsUsingOwnContainerAsTemplateRoot &&
127-
element is ContentPresenter outerContainer)
128-
{
129-
if (outerContainer.Content is TabBarItem innerContainer)
162+
base.ClearContainerForItemOverride(element, item);
163+
if (element is TabBarItem tbi)
130164
{
131-
TearDownTabBarItem(innerContainer);
132-
innerContainer.DataContext = null;
165+
TearDownTabBarItem(tbi);
133166
}
134-
outerContainer.Content = null;
167+
}
168+
169+
void TearDownTabBarItem(TabBarItem item)
170+
{
171+
item.Click -= OnTabBarItemClick;
172+
item.IsSelectedChanged -= OnTabBarIsSelectedChanged;
135173
}
136174
}
137175

src/Uno.Toolkit.UI/Controls/TabBar/TabBarItem.Properties.cs

+2
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ public object CommandParameter
9595
DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(TabBarItem), new PropertyMetadata(null, OnPropertyChanged));
9696
#endregion
9797

98+
internal bool IsStyleSetFromTabBar { get; set; }
99+
98100
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
99101
{
100102
var owner = (TabBarItem)sender;

0 commit comments

Comments
 (0)