将CollectionViewSource与GroupDescriptions一起使用时ListBox ScrollIntoView(即IsGrouping == True)

时间:2011-09-09 20:14:27

标签: c# wpf

简短版

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

长版

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

<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来自 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. 使用分组时如何实现滚动浏览?

2 个答案:

答案 0 :(得分:8)

我找到了解决问题的方法。我确信我不是第一个遇到这个问题的人所以我继续搜索StackOverflow寻找解决方案,我偶然发现了David about how ItemContainerGenerator works with a grouped list的答案。

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

的变化:

  • 仅在ItemContainerGeneratorIsGrouping时使用true方法,否则继续使用默认ScrollIntoView
  • 检查ItemContainerGenerator是否准备就绪,如果是,则调度操作,否则请监听ItemContainerGenerator状态进行更改..这一点非常重要,就好像它已准备就绪StatusChanged事件一样永远不会开火。

答案 1 :(得分:3)

  1. 开箱即用的VirtualizingStackPanel不支持虚拟化分组集合视图。当一个分组集合在ItemsControl中呈现时,每个组作为一个整体是一个项目,而不是集合中的每个项目,这导致“生涩”滚动到每个组标题而不是每个项目。

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