Question

Whenever I used TreeView I always had just few nodes and each of them usually had less than 100 items. I never really needed any kind of ui virtualization for that but now for the first time I need it.

The problem appears when using ui virtualization with recycling mode the TreeView seems to expand items even though I never expanded them manually.

I googled the issue and as far I understood recycling mode of virtualization in TreeView the containers get reused.

So I assume that the cause might be applying already expanded reused container to an item which wasn't expanded before.

Here is a simple example:

https://github.com/devhedgehog/wpf/

For those who cannot download code for whatever reason here is basically what I have tried to do with the TreeView.

This is what I have in XAML.

     <Grid>
        <TreeView ItemsSource="{Binding}" VirtualizingStackPanel.IsVirtualizing="True"  VirtualizingStackPanel.VirtualizationMode="Recycling">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Parts}">
                    <TextBlock Text="{Binding Name}"/>
                    <HierarchicalDataTemplate.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding}"/>
                        </DataTemplate>
                    </HierarchicalDataTemplate.ItemTemplate>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </Grid>

And this is code behind:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            IList<Car> list = new List<Car>();
            for (int i = 0; i < 5000; i ++)
            {
                list.Add(new Car() { Name = "test1" + i });
            }

            foreach (var car in list)
            {
                car.Parts = new List<string>();
                for (int i = 0; i < 500; i++)
                {
                    car.Parts.Add("asdf" + i);
                }
            }

            this.DataContext = list;
        }
    }

    public class Car
    {
        public string Name
        {
            get;
            set;
        }

        public List<string> Parts
        {
            get;
            set;
        }
    }

I hope somebody can provide me a solution to this issue. Is this a known bug?

I am sorry in case its a duplicate. Futhermore I hope you guys tell me what I did wrong since this is my first post before you downgrade the question.

Was it helpful?

Solution

As you probably know, this problem can be solved easily by using standard recycling mode:

<TreeView VirtualizingStackPanel.VirtualizationMode="Standard" ...>

This shouldn't have too much of an impact on your TreeView's performance, as the tree will still be virtualized and a container will only be created for visible items. The benefits of the recycling mode only come into play when scrolling (when items are both being virtualized and realized), and usually the standard virtualization mode is good enough.

However, in case performance is really critical (or if you really want a solution for this while keeping the recycling mode, or if you're looking to do things the right way), you can use backing data and data binding to solve this problem.

The reason why this problem occurs in the first place is this:

Let's say you have a TreeViewItem which has its IsExpanded property set to true. When it's being recycled, i.e. its data is replaced, its IsExpanded property remains the same because it has no way to know whether it should be expanded or not, because that data is not available anywhere. The only place where it exists is the IsExpanded property of the TreeViewItem, and it's not going to be relevant because that item is being reused along with its properties.

If however you have a viewmodel for each tree item you'll be able to bind each TreeViewItem to the IsExpanded property in your TreeViewItemViewModel (you will have a view model for each tree item) and you will always get the correct value because you've made that data available and bound each item to it.

Your TreeView's ItemsSource will be bound to a collection of TreeViewItemViewModel objects, and your TreeViewItemViewModel class will look something like this:

class TreeViewItemViewModel : INotifyPropertyChanged
{
    bool IsExpanded { get; set; }
    bool IsSelected { get; set; }
    TreeViewItemViewModel Parent { get; }
    ObservableCollection<TreeViewItemViewModel> Children { get; }
}

You can find more information on how exactly to create such view model in Josh Smith's excellent article Simplifying the WPF TreeView by Using the ViewModel Pattern.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top