【问题标题】:ListBox ScrollIntoView when using CollectionViewSource with GroupDescriptions (i.e. IsGrouping == True)将 CollectionViewSource 与 GroupDescriptions 一起使用时的 ListBox ScrollIntoView(即 IsGrouping == True)
【发布时间】:2011-09-09 20:14:27
【问题描述】:

短版

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

加长版

我有一个 ListBoxItemsSource 绑定到 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 通过反射器。注意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. 使用分组时如何实现滚动到视图?

【问题讨论】:

    标签: c# wpf


    【解决方案1】:

    我找到了解决问题的方法。我确信我不是第一个遇到这个问题的人,所以我继续在 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();
    }
    

    变化:

    • 只有当IsGroupingtrue时才使用ItemContainerGenerator的方式,否则继续使用默认的ScrollIntoView
    • 检查ItemContainerGenerator 是否准备好,如果准备好,则分派操作,否则请监听ItemContainerGenerator 状态的变化。这很重要,因为如果它准备好了,那么StatusChanged 事件将永远不会触发。

    【讨论】:

    • 你应该把你的答案改成正确的,而不是上面那个。
    • @Valentein:我已经更改了标记的答案。然而,crazyarabian 的建议确实帮助我诊断问题,如果您使用我的最终解决方案,最好对 both 答案进行投票。
    • 使用 .NET 4.5.1 和 MVVM,您可以使用行为来执行此操作。该行为在这两种情况下都有效,因为它已经延迟触发了。
    【解决方案2】:
    1. 开箱即用的 VirtualizingStackPanel 不支持虚拟化分组集合视图。当在 ItemsControl 中呈现分组集合时,每个组作为一个整体是一个项目,而不是集合中的每个项目,这会导致“急速”滚动到每个组标题而不是每个项目。

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

    【讨论】:

    • 这就是我的想法,但是我希望我不必这样做,因为编写虚拟化面板可能很棘手。 ...因为每个组作为一个整体是项目,这就是为什么 ItemContainerGenerator 在通过 SelectedItem 时总是返回 null
    • 我相信是的。您应该查看 Bea Stollnitz 博客。她有很多关于分组和虚拟化的好帖子:beacosta.com
    • 谢谢。我已经阅读了几篇关于 WPF 和 CollectionViewSource 和 Grouping 的 Bea Stollnitz 帖子——我实际上在我的问题中使用了她的分组示例。
    • 更新:我找到了解决方案并将其发布为答案。
    猜你喜欢
    • 2015-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-17
    相关资源
    最近更新 更多