Skip to content

Commit 42fd720

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

File tree

4 files changed

+105
-33
lines changed

4 files changed

+105
-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

+76-33
Original file line numberDiff line numberDiff line change
@@ -73,32 +73,79 @@ 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);
87+
88+
#if !HAS_UNO
89+
// force template materialization
90+
cp.Measure(Size.Empty);
91+
#endif
7792

78-
void SetupTabBarItem(TabBarItem item)
93+
if (cp.GetFirstChild() is TabBarItem tbi)
94+
{
95+
ApplyContainerStyle(tbi);
96+
SetupTabBarItem(tbi);
97+
}
98+
}
99+
else
79100
{
80-
item.IsSelected = IsSelected(IndexFromContainer(element));
81-
item.Click += OnTabBarItemClick;
82-
item.IsSelectedChanged += OnTabBarIsSelectedChanged;
101+
base.PrepareContainerForItemOverride(element, item);
102+
if (element is TabBarItem tbi)
103+
{
104+
SetupTabBarItem(tbi);
105+
}
83106
}
84107

85-
if (element is TabBarItem container)
108+
void SetContent(ContentPresenter cp, object item)
86109
{
87-
SetupTabBarItem(container);
110+
if (string.IsNullOrEmpty(DisplayMemberPath))
111+
{
112+
cp.Content = item;
113+
}
114+
else
115+
{
116+
cp.SetBinding(ContentPresenter.ContentProperty, new Binding
117+
{
118+
Source = item,
119+
Path = new(DisplayMemberPath),
120+
});
121+
}
88122
}
89-
else if (IsUsingOwnContainerAsTemplateRoot &&
90-
element is ContentPresenter outerContainer)
123+
void ApplyContainerStyle(TabBarItem tbi)
91124
{
92-
var templateRoot = outerContainer.ContentTemplate.LoadContent();
93-
if (templateRoot is TabBarItem tabBarItem)
125+
var localStyleValue = tbi.ReadLocalValue(FrameworkElement.StyleProperty);
126+
var isStyleSetFromTabBar = tbi.IsStyleSetFromTabBar;
127+
128+
if (localStyleValue == DependencyProperty.UnsetValue || isStyleSetFromTabBar)
94129
{
95-
outerContainer.ContentTemplate = null;
96-
SetupTabBarItem(tabBarItem);
97-
tabBarItem.DataContext = item;
98-
tabBarItem.Style ??= ItemContainerStyle;
99-
outerContainer.Content = tabBarItem;
130+
var style = ItemContainerStyle ?? ItemContainerStyleSelector?.SelectStyle(item, tbi);
131+
if (style is { })
132+
{
133+
tbi.Style = style;
134+
tbi.IsStyleSetFromTabBar = true;
135+
}
136+
else
137+
{
138+
tbi.ClearValue(FrameworkElement.StyleProperty);
139+
tbi.IsStyleSetFromTabBar = false;
140+
}
100141
}
101142
}
143+
void SetupTabBarItem(TabBarItem tbi)
144+
{
145+
tbi.IsSelected = IsSelected(IndexFromContainer(element));
146+
tbi.Click += OnTabBarItemClick;
147+
tbi.IsSelectedChanged += OnTabBarIsSelectedChanged;
148+
}
102149
}
103150

104151
internal virtual bool IsSelected(int index)
@@ -108,30 +155,26 @@ internal virtual bool IsSelected(int index)
108155

109156
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
110157
{
111-
base.ClearContainerForItemOverride(element, item);
112-
113-
void TearDownTabBarItem(TabBarItem item)
158+
if (IsUsingOwnContainerAsTemplateRoot && element is ContentPresenter cp)
114159
{
115-
item.Click -= OnTabBarItemClick;
116-
item.IsSelectedChanged -= OnTabBarIsSelectedChanged;
117-
if (!IsUsingOwnContainerAsTemplateRoot)
160+
if (cp.GetFirstChild() is TabBarItem tbi)
118161
{
119-
item.Style = null;
162+
TearDownTabBarItem(tbi);
120163
}
121164
}
122-
if (element is TabBarItem container)
165+
else
123166
{
124-
TearDownTabBarItem(container);
125-
}
126-
else if (IsUsingOwnContainerAsTemplateRoot &&
127-
element is ContentPresenter outerContainer)
128-
{
129-
if (outerContainer.Content is TabBarItem innerContainer)
167+
base.ClearContainerForItemOverride(element, item);
168+
if (element is TabBarItem tbi)
130169
{
131-
TearDownTabBarItem(innerContainer);
132-
innerContainer.DataContext = null;
170+
TearDownTabBarItem(tbi);
133171
}
134-
outerContainer.Content = null;
172+
}
173+
174+
void TearDownTabBarItem(TabBarItem item)
175+
{
176+
item.Click -= OnTabBarItemClick;
177+
item.IsSelectedChanged -= OnTabBarIsSelectedChanged;
135178
}
136179
}
137180

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;

src/Uno.Toolkit.UI/Helpers/VisualTreeHelperEx.cs

+7
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,13 @@ public static IEnumerable<DependencyObject> GetChildren(this DependencyObject re
195195
.Select(x => VisualTreeHelper.GetChild(reference, x));
196196
}
197197

198+
public static DependencyObject? GetFirstChild(this DependencyObject reference)
199+
{
200+
return VisualTreeHelper.GetChildrenCount(reference) > 0
201+
? VisualTreeHelper.GetChild(reference, 0)
202+
: null;
203+
}
204+
198205
public static DependencyObject? GetTemplateRoot(this DependencyObject o) => o?.GetChildren().FirstOrDefault();
199206
}
200207
internal static partial class VisualTreeHelperEx // TreeGraph helper methods

0 commit comments

Comments
 (0)