Closed
Description
Describe the bug
When you expand and collapse nodes in a TreeView
with an ItemsSource
set (i.e. in "content mode"), the value of TreeViewItem.IsExpanded
is not preserved. If you scroll through a large TreeView
such that an expanded node is recycled and then you scroll back up, you'll find the node most likely collapsed. Likewise, if you scroll down, you'll find a random node expanded on its own.
microsoft-ui-xaml/controls/dev/TreeView/TreeViewList.cpp
Lines 320 to 343 in b91b3ce
This is due to how
TreeViewList.PrepareContainerForItemOverride
is written, where it considers the container the "source of truth" for IsExpanded
, instead of the TreeViewNode
that the container is wrapping. This makes sense only when the ItemTemplate
of the TreeView
binds the container's IsExpanded
to some property in the view model. Otherwise, the TreeViewNode
(which still remembers the IsExpanded
state the last time it was in view) should be the "source of truth".
Instead, it could be something like
DispatcherQueue().TryEnqueue(winrt::DispatcherQueueHandler(
[itemNode, itemContainer]()
{
if (itemContainer.GetBindingExpression(MetadataAPI::GetDependencyPropertyByIndex(KnownPropertyIndex::TreeViewItem_IsExpanded))
{
itemNode.IsExpanded(itemContainer.IsExpanded());
}
else
{
itemContainer.IsExpanded(itemNode.IsExpanded());
}
}));
Steps to reproduce the bug
<StackPanel>
<Button Click="Button_Click_1">Expand the second root node</Button>
<Button Click="Button_Click_2">Scroll to the end, you'll find root node 16 expanded for no reason</Button>
<Button Click="Button_Click_3">Scroll back up, you'll find that the second root node that was expanded is now collapsed</Button>
<TextBlock>If you scroll around after that, you might find random root nodes expanded and others that were expanded now collapsed.</TextBlock>
<TextBlock>The first root node will always behave well because it's never recycled, so when investigating, play with any node except the very first one.</TextBlock>
<TreeView x:Name="MyTreeView" ItemsSource="{x:Bind DataSource}" Height="100">
<TreeView.ItemTemplate>
<DataTemplate>
<TreeViewItem ItemsSource="{Binding Children}" Content="{Binding Name}"/>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
public sealed partial class MainPage : Page
{
public class Item
{
public string Name { get; set; }
public List<Item> Children { get; set; }
}
public List<Item> DataSource { get; set; }
public MainPage()
{
this.InitializeComponent();
DataSource = Enumerable.Range(0, 20).Select(i =>
new Item
{
Name = $"Root{i}",
Children = new List<Item>
{
new Item { Name = $"Root{i} Child 1", Children = new List<Item>
{
new Item { Name = $"Root{i} Grandchild 1" },
new Item { Name = $"Root{i} Grandchild 2" }
}
},
new Item { Name = $"Root{i} Child 2" }
}
}
).ToList();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
MyTreeView.RootNodes[1].IsExpanded = true;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
var sv = EnumerateDescendants(MyTreeView).OfType<ScrollViewer>().First();
sv.ScrollToVerticalOffset(9999);
}
private void Button_Click_3(object sender, RoutedEventArgs e)
{
var sv = EnumerateDescendants(MyTreeView).OfType<ScrollViewer>().First();
sv.ScrollToVerticalOffset(0);
}
private static IEnumerable<UIElement> EnumerateDescendants(UIElement? reference)
{
var children = Enumerable
.Range(0, VisualTreeHelper.GetChildrenCount(reference))
.Select(x => VisualTreeHelper.GetChild(reference, x))
.OfType<UIElement>();
foreach (var child in children)
{
yield return child;
foreach (var grandchild in EnumerateDescendants(child))
{
yield return grandchild;
}
}
}
}
Click on the buttons from top to bottom.
Expected behavior
Expanded nodes should stay expanded, collapsed nodes should stay collapsed.
Screenshots
No response
NuGet package version
WinUI 3 - Windows App SDK 1.5.2: 1.5.240404000
Windows version
Windows 10 (21H2): Build 19044
Additional context
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment