ListBox ScrollIntoView when using CollectionViewSource with GroupDescriptions (i.e. IsGrouping == True)

前端 未结 2 1036
太阳男子
太阳男子 2021-02-08 07:43

Short version

I would like to scroll the ListBox item into view when the selection is changed.

Long version

相关标签:
2条回答
  • 2021-02-08 07:52
    1. The out of the box VirtualizingStackPanel does not support virtualizing grouped collection views. When a grouped collection is rendered in an ItemsControl, each group as a whole is an item as opposed to each item in the collection which results in "jerky" scrolling to each group header and not each item.

    2. You'll probably need to roll your own VirtualizingStackPanel or ItemContainerGenerator in order to keep track of the containers displayed in a group. It sounds ridiculous, but the default virtualization with grouping in WPF is lacking to say the least.

    0 讨论(0)
  • 2021-02-08 07:53

    I have found a solution to my problem. I was certain that I wasn't the first person to hit this issue so I continued to search StackOverflow for solutions and I stumbled upon this answer by David about how ItemContainerGenerator works with a grouped list.

    David's solution was to delay accessing the ItemContainerGenerator until after the rendering process.

    I have implemented this solution, with a few changes that I will detail after.

    private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        ListBox control = (ListBox)sender;
    
        if (control.IsGrouping)
        {
             if (control.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
                  Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(DelayedBringIntoView));
             else
                  control.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
        }
        else
            control.ScrollIntoView(control.SelectedItem);
    }
    
    private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
    {
        if (ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
            return;
    
        ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
        Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(DelayedBringIntoView));
    }
    
    private void DelayedBringIntoView()
    {
        var item = ItemContainerGenerator.ContainerFromItem(SelectedItem) as ListBoxItem;
        if (item != null)
            item.BringIntoView();
    }
    

    Changes:

    • Only uses the ItemContainerGenerator approach when it IsGrouping is true, otherwise continue to use the default ScrollIntoView.
    • Check if the ItemContainerGenerator is ready, if so dispatch the action, otherwise listen for the ItemContainerGenerator status to change.. This is important as if it is ready then the StatusChanged event will never fire.
    0 讨论(0)
提交回复
热议问题