将CollectionViewSource与GroupDescriptions结合使用时ListBox ScrollIntoView(即IsGrouping == True)

精简版

我想在选择更改时将ListBox项目滚动到视图中。

长版

我有一个ListBox ,其ItemsSource绑定到具有GroupDescriptionCollectionViewSource ,如下例所示。

             

代码隐藏文件中有一个SelectionChanged事件。

 public List Animals { get; set; } private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { ListBox control = (ListBox)sender; control.ScrollIntoView(control.SelectedItem); } 

现在。 如果我将AnimalsListBox.SelectedItem设置为当前不可见的项目,我希望它在视图中滚动。 这是棘手的,因为ListBox是组( IsGrouped属性为true ),对ScrollIntoView的调用失败。

System.Windows.Controls.ListBox通过Reflector 。 注意base.IsGrouping中的OnBringItemIntoView

 public void ScrollIntoView(object item) { if (base.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { this.OnBringItemIntoView(item); } else { base.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new DispatcherOperationCallback(this.OnBringItemIntoView), item); } } private object OnBringItemIntoView(object arg) { FrameworkElement element = base.ItemContainerGenerator.ContainerFromItem(arg) as FrameworkElement; if (element != null) { element.BringIntoView(); } else if (!base.IsGrouping && base.Items.Contains(arg)) { VirtualizingPanel itemsHost = base.ItemsHost as VirtualizingPanel; if (itemsHost != null) { itemsHost.BringIndexIntoView(base.Items.IndexOf(arg)); } } return null; } 

问题

  1. 任何人都可以解释为什么它在使用分组时不起作用?
    • ItemContainerGenerator.ContainerFromItem始终返回null ,即使它的状态表明已生成所有容器。
  2. 使用分组时如何实现滚动视图?

我找到了解决问题的方法。 我确信我不是第一个遇到这个问题的人所以我继续搜索StackOverflow的解决方案,我偶然发现了David 关于ItemContainerGenerator如何使用分组列表的答案。

David的解决方案是延迟访问ItemContainerGenerator直到渲染过程完成。

我已经实现了这个解决方案,我将详细介绍一些更改。

 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(); } 

变化:

  • 仅当IsGroupingtrue时才使用ItemContainerGenerator方法,否则继续使用默认的ScrollIntoView
  • 检查ItemContainerGenerator是否准备就绪,如果是,则调度该操作,否则侦听ItemContainerGenerator状态以进行更改。这很重要,就好像它已准备好,然后StatusChanged事件永远不会触发。
  1. 开箱即用的VirtualizingStackPanel不支持虚拟化分组集合视图。 当在ItemsControl中呈现分组集合时,每个组作为整体是项目,而不是集合中的每个项目,这导致“生涩”滚动到每个组标题而不是每个项目。

  2. 您可能需要滚动自己的VirtualizingStackPanel或ItemContainerGenerator,以便跟踪组中显示的容器。 这听起来很荒谬,但在WPF中使用分组的默认虚拟化至少可以说是缺乏。