将CollectionViewSource与GroupDescriptions一起使用时的ListBox ScrollI

本文关键字:ListBox ScrollI CollectionViewSource GroupDescriptions 一起 | 更新日期: 2023-09-27 18:08:14

短版本

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

长版本

我有一个带有ItemsSourceListBox绑定到带有GroupDescriptionCollectionViewSource,如下例所示。

<Window.Resources>
    <CollectionViewSource x:Key="AnimalsView" Source="{Binding Source={StaticResource Animals}, Path=AnimalList}">
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="Category"/>
        </CollectionViewSource.GroupDescriptions>
    </CollectionViewSource>  
</Window.Resources>
<ListBox x:Name="AnimalsListBox"ItemsSource="{Binding Source={StaticResource AnimalsView}}" ItemTemplate="{StaticResource AnimalTemplate}" SelectionChanged="ListBox_SelectionChanged">
    <ListBox.GroupStyle>
        <GroupStyle HeaderTemplate="{StaticResource CategoryTemplate}" />
    </ListBox.GroupStyle>
</ListBox>

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

public List<Animal> 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通过反射器。注意OnBringItemIntoView中的base.IsGrouping

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. 使用分组时,我如何实现滚动到视图中

将CollectionViewSource与GroupDescriptions一起使用时的ListBox ScrollI

我找到了解决问题的方法。我确信我不是第一个遇到这个问题的人,所以我继续搜索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中带有分组的默认虚拟化是缺乏的。