-
Notifications
You must be signed in to change notification settings - Fork 804
Description
Describe the bug
For a bit of context we have a UserControl, that we call a Carousel, which is basically a ItemsRepeater in a ScrollView.
Every time the user saves a some data that data is stored in an observable vector bound to the ItemsRepeater. The ItemsRepeater's DataTemplate has a custom UserControl in it, and we bind the item to the UserControl. Another crucial piece of this is that we use the SizeChanged event on the ItemsRepeater to scroll the viewport of the ScrollView to the latest saved item.
What we are seeing is that if the ItemsRepeater is loaded with a large list items and it auto-scrolls to the latest item the rendering of some of the UserControls in its datatemplate will not render properly. They will seemingly reuse how previous UserControls look, most of the time some of the first UserControls in the ItemsRepeater.
I've made a pared down version of the UserControls to demonstrate the behavior:
Here I have a list of items that we've bound to the ItemsRepeater.
m_Items = winrt::single_threaded_observable_vector<hstring>();
m_Items.Append(L"🐖");
m_Items.Append(L"🐖");
m_Items.Append(L"🐖");
m_Items.Append(L"🐖");
m_Items.Append(L"🥕");
m_Items.Append(L"🥕");
m_Items.Append(L"🥕");
m_Items.Append(L"🥕");
m_Items.Append(L"🥕");
m_Items.Append(L"🥕");
The ItemsRepeater is defined as following:
<ScrollViewer x:Name="Scroller" Height="64" Width="300" Background="Green" HorizontalScrollMode="Enabled" HorizontalScrollBarVisibility="Visible">
<ItemsRepeater HorizontalCacheLength="100" SizeChanged="Repeater_SizeChanged" x:Name="Repeater" Layout="{StaticResource HorizontalStackLayout}"
ItemsSource="{x:Bind Items}" ItemTemplate="{StaticResource ItemsRepeaterTemplateUserControlTemplate}"/>
</ScrollViewer>
and its data template is defined with:
<DataTemplate x:Key="ItemsRepeaterTemplateUserControlTemplate" x:DataType="x:String">
<local:SimpleUserControl Data="{x:Bind}"></local:SimpleUserControl>
</DataTemplate>
The event code for Repeater_SizeChanged is as follows:
void MainPage::Repeater_SizeChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::SizeChangedEventArgs const&)
{
if (Scroller().ScrollableWidth() > 0)
{
Scroller().ChangeView(Scroller().ScrollableWidth(), nullptr, nullptr);
}
}
Lastly the xaml code for the SimpleUserControl is as follows:
<Grid>
<Border Background="Red" BorderBrush="Black" BorderThickness="1" Width="64" Height="64">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="38" Text="{x:Bind Data}"/>
</Border>
</Grid>
One would expect that you would get a list of 4 pigs followed by 6 carrots. However that is not exactly what you get:
Here we see that the three last items look like pigs, but they should look like carrots. I've added code in the UserControls that set the tooltip on the user control to be whatever the Data is bound to:
void SimpleUserControl::Data(const winrt::hstring& Value)
{
m_Data = Value;
winrt::Microsoft::UI::Xaml::Controls::ToolTipService::SetToolTip(*this, winrt::box_value(Value));
}
Note if I had bound the tooltip to the value of Data in Xaml it would have had the same issue as with the TextBlock
If I replace the ItemsRepeater's DataTemplate with one that does not use a UserControl it will work as expected:
<DataTemplate x:Key="ItemsRepeaterTemplate" x:DataType="x:String"> <!-- Will work as expected. -->
<Grid>
<Border Background="Red" BorderBrush="Black" BorderThickness="1" Width="64" Height="64">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="38" Text="{x:Bind}"/>
</Border>
</Grid>
</DataTemplate>
Also if I remove the code that scrolls to the end of the ScrollView things will work as one expects. So it seems to me there is some sort of issue with the combination of using UserControls in the DataTemplate and at the same time scrolling to the end of the ScrollView.
Also I have set HorizontalCacheLength to 100 in this example. If I left it with whatever the default value is it would not work either.
So I assume this has to do with some sort of reuse in the ItemsRepeater, but I cannot quite understand why using a UserControl, and Scrolling to the end should change the behavior.
Steps to reproduce the bug
- Create a new project with a new Page
- Create a UserControl that that has as String property that you can bind into a TextBlock in the UserControl's xaml.
- Add an observable vector of hstrings to the page, and in the xaml define an ItemsRepeater inside a ScrollView. Define a DataTemplate that uses the UserControl, bind the vector to the ItemsRepeater, set HorizontalCacheLength to 100.
- Add a callback for the SizeChanged event in ItemsRepeater and in the callback make the ScrollView to always scroll to the end.
- Make observable vector has a lot of items that the ScrollView.
For more details see the code above, or here's a link to the test implementation I made: https://github.com/torleifat/UserControlInItemsRepeaterTest
Expected behavior
For it not to reuse UserControls.
Screenshots
No response
NuGet package version
WinUI 3 - Windows App SDK 1.7.1: 1.7.250401001
Windows version
Windows 11 (24H2): Build 26100
Additional context
No response

