2

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.

Adi Lester
  • 24,731
  • 12
  • 95
  • 110
dev hedgehog
  • 8,698
  • 3
  • 28
  • 55
  • Is using container recycling an absolute must? Using the standard virtualization mode shouldn't have a very big impact on performance and it will solve your problem. – Adi Lester Sep 22 '13 at 19:05
  • I am very interested in solving this without using the standard virtualization. I would like to see what kind of ideas or suggetions you guys will come up with. Where I come from people say only must in life is to die. This is not an absolute must but I would like to figure out how to make this work. Hope few others here would like the same too. I think it will need a bit of wpf hacking to solve this. Exciting bug I stumbled upon, isnt it?. – dev hedgehog Sep 22 '13 at 19:31

1 Answers1

3

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.

Adi Lester
  • 24,731
  • 12
  • 95
  • 110
  • Yea I also thought of IsExpanded. I will mark this as solution but just one more question among us. Like I mentioned in my question I need to use virtualization because I have large amount of data. How could I solve this without Binding to IsExpanded? I mean it is quite a memory issue since I get the entities from database and I need to extend each of them with isExpanded property. That means for 20 000 simple entities I will get 40 000 created objects in memory. Thats not good. Can you maybe show me a way to solve this issue with recycling mode without binding to IsExanded? – dev hedgehog Sep 22 '13 at 21:13
  • Standard mode of virtualization gotta be saving somewhere information which item is expanded since standard works without binding to IsExpanded property. Why cant recycling mode adopt the same behavior as standard virtualization mode is already doing? – dev hedgehog Sep 22 '13 at 21:16
  • That's a good question. I'm not sure where this data is saved, it would be interesting to check. As for a different solution, I can't think of any right now, but you can take advantage of the way dependency properties are saved in memory to reduce memory usage. You can have your viewmodel inherit from `DependencyObject` and define `IsExpanded` as a dependency property with default value `false`. This way a boolean value will be saved in memory only for expanded items. Also, to reduce overall memory you should have a look at data virtualization. – Adi Lester Sep 22 '13 at 21:25
  • Thanks for fast reply. Well I read about all that ui virtualization magic and when in standard mode the generator calls the method remove while when in recycling mode the generator manages a cache of already realized containers. With generator I mean the thingy creating containers for items. So I guess we will find the answer to the question when we debug the source code of VirtualizingStackPanel. Do you know how could I debug source code of that panel in VS2012? – dev hedgehog Sep 22 '13 at 21:34
  • Can you even load and open source code from symbols in VS2012? I cant. I have set the settings to allow downloading symbols from microsoft server but its not working. I enabled all the nessacary options such as the tick on stepping into .net framework code but still nothing. – dev hedgehog Sep 22 '13 at 23:07
  • There are several answers in SO that explain this, but usually I find it easier to use a tool like ILSpy and work my way through the class' logic. – Adi Lester Sep 23 '13 at 07:07
  • I will try it with ILSpy but I just tried to bind IsExpanded and its not working either. I set the ItemContainerStyle of TreeView to this: When I scroll in recycling mode the same strange behavior happens again. – dev hedgehog Sep 23 '13 at 07:38
  • I'm not sure if that's what causes the problem, but the binding should be `TwoWay`. – Adi Lester Sep 23 '13 at 07:45
  • 1
    If the binding is twoway and generator reuses an already expanded container, wouldnt the data item recieve true as expanded since the container may push values to the source? That is why I thought of using oneway so that only data items may send values to containers – dev hedgehog Sep 23 '13 at 07:56