获取 ListView 可见项

Posted

技术标签:

【中文标题】获取 ListView 可见项【英文标题】:Get ListView Visible items 【发布时间】:2012-06-26 14:14:16 【问题描述】:

我有一个ListView,它可能包含很多物品,所以它是virtualized 和回收物品。它不使用排序。我需要刷新一些值显示,但是当项目太多时,更新所有内容太慢,所以我想只刷新可见项目。

如何获得所有当前显示项目的列表?我试图查看ListViewScrollViewer,但我仍然不知道如何实现这一点。解决方案不能遍历所有项目来测试它们是否可以看到,因为这太慢了。

我不确定代码或 xaml 是否有用,它只是一个 Virtualized/Recycling ListView,其 ItemSource 绑定到 Array

编辑: 答案: 感谢 akjoshi,我找到了方法:

获取ListViewScrollViewer (使用 FindDescendant 方法,您可以自己使用 VisualTreeHelper )。

读取它的ScrollViewer.VerticalOffset:它是显示的第一个项目的编号

阅读它的ScrollViewer.ViewportHeight:它是显示的项目数。 Rq : CanContentScroll 必须为真。

【问题讨论】:

你是如何填写你的 ListView 的?显式创建 ListViewItem?一组 ItemSource ?捆绑 ?也许给我们一些代码! 另请参阅此答案***.com/a/26344535/1830814 【参考方案1】:

查看 MSDN 上的这个问题,该问题展示了一种找出可见 ListView 项目的技术 -

How to find the rows (ListViewItem(s)) in a ListView that are actually visible?

这是该帖子中的相关代码 -

listView.ItemsSource = from i in Enumerable.Range(0, 100) select "Item" + i.ToString();
listView.Loaded += (sender, e) =>

    ScrollViewer scrollViewer = listView.GetVisualChild<ScrollViewer>(); //Extension method
    if (scrollViewer != null)
    
        ScrollBar scrollBar = scrollViewer.Template.FindName("PART_VerticalScrollBar", scrollViewer) as ScrollBar;
        if (scrollBar != null)
        
            scrollBar.ValueChanged += delegate
            
                //VerticalOffset and ViweportHeight is actually what you want if UI virtualization is turned on.
                Console.WriteLine("Visible Item Start Index:0", scrollViewer.VerticalOffset);
                Console.WriteLine("Visible Item Count:0", scrollViewer.ViewportHeight);
            ;
        
    
;

您应该做的另一件事是使用ObservableCollection 作为您的ItemSource 而不是Array;那肯定会improve the performance。

更新:

是的,这可能是真的(array vs. ObservableCollection)但我想看看一些与此相关的统计数据;

ObservableCollection 的真正好处是,如果您需要在运行时从您的ListView 添加/删除项目,如果是Array,您将不得不重新分配@987654335 的ItemSource @ 和 ListView 首先丢弃它以前的项目并重新生成它的整个列表。

【讨论】:

Rq :就性能和内存使用而言,对于我的应用程序而言,该数组远远超过了 Observable 集合。我使用的项目数量在 100.000-1.000.000 范围内。 您提供的 MS 链接将 List 与 Observable 集合进行比较,项目计数较低 (1000) 并且虚拟化关闭,很可能是因为否则不会看到明显的差异。所以它不适用于我的案例,我想知道它是否与任何案例相关(为什么有人会关闭虚拟化?) ListView 只会重新生成可见的项目,因为除了 MS 没有人关闭虚拟化 :)。在我的应用程序中,几乎所有数组都可能在刷新时发生变化。项目的 ObsColl 将导致计数 > 200.000 (Win XP) 的内存异常,并且在该计数时过滤时间 > 10 分钟。有了一个数组,我在 1 分钟内达到了 5.000.000。我希望我能提供一些“证明”,但我知道 WPF 没有 JSPerf ......对我来说,底线是:ObsColl 更适合不太大的集合(而且更方便),但没有什么能比数组更好>> 100.000 项。 ObservableCollection 的问题是它不喜欢线程,即使您使用 Dispatcher。我不得不切换到常规 List 并告诉应用程序仅更新 Count 更改,因为 OC 无法跟踪它添加了多少项目与列表中存在多少项目,这导致 WPF 崩溃。 那么当CanContentScrollfalse时呢?【参考方案2】:

在尝试找出类似的东西后,我想我会在这里分享我的结果(因为它似乎比其他回复更容易):

我从here 获得的简单可见性测试。

private static bool IsUserVisible(FrameworkElement element, FrameworkElement container)

    if (!element.IsVisible)
        return false;

    Rect bounds =
        element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
    var rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
    return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);

之后,您可以遍历列表框项并使用该测试来确定哪些是可见的。由于列表框项的顺序始终相同,因此此列表中的第一个可见项将是用户第一个可见的项。

private List<object> GetVisibleItemsFromListbox(ListBox listBox, FrameworkElement parentToTestVisibility)

    var items = new List<object>();

    foreach (var item in PhotosListBox.Items)
    
        if (IsUserVisible((ListBoxItem)listBox.ItemContainerGenerator.ContainerFromItem(item), parentToTestVisibility))
        
            items.Add(item);
        
        else if (items.Any())
        
            break;
        
    

    return items;

【讨论】:

【参考方案3】:

我如何看待事物:

一方面,您拥有自己的数据。它们必须是最新的,因为这是您的信息在内存中的位置。迭代你的数据列表应该很快,最重要的是,可以在后台的另一个线程上完成

在另一边,你有显示器。您的ListView 已经使用了只刷新显示数据的技巧,因为它是虚拟化的!你不需要更多的技巧,它已经到位了!

在最后的工作中,对ObservableCollection 使用绑定是一个很好的建议。如果您打算从另一个线程修改ObservableCollection,我建议您这样做:http://blog.quantumbitdesigns.com/2008/07/22/wpf-cross-thread-collection-binding-part-1/

【讨论】:

如果你不了解它,你可能对 MVVM 模式感兴趣 ;) 我没有调查什么在使用 CPU,但是遍历我的列表并在一个属性上为所有项目设置 NotifyPropertyChanged 对于必须执行我的程序的(慢速)计算机来说任务太重了。该列表可能有 100.000 项长。因此,虚拟化并不能挽救局面。我测试 ObservableCollection 比我的应用程序中的数组慢 5 倍以上。 访问数组肯定比访问 ObservableCollection 快。但是 ObservableCollection 使用绑定完成了使 UI 保持最新的所有工作。以我的经验,创建新的图形项目是大部分时间。不是数据列表背后的工作。【参考方案4】:

我花了很多时间为此寻找更好的解决方案, 在我的情况下,我有一个滚动查看器,里面装满了可以设置为可见/不可见的自定义高度的项目,我想出了这个。它的作用与上述解决方案相同,但只占用了 CPU 的一小部分。我希望它对某人有所帮助。 listview / scrollpanel 的第一项是 TopVisibleItem

    public int TopVisibleItem  get; private set; 

    private double CurrentDistance;

    private void TouchScroller_ScrollChanged(object sender, ScrollChangedEventArgs e)
    
        if (myItemControl.Items.Count > 0)
        
            MoveDirection direction = (MoveDirection)Math.Sign(e.VerticalChange);
            if (direction == MoveDirection.Positive)
                while (CurrentDistance < e.VerticalOffset && TopVisibleItem < myItemControl.Items.Count)
                
                    CurrentDistance += ((FrameworkElement)myItemControl.Items[TopVisibleItem]).ActualHeight;
                    TopVisibleItem += 1;
                
            else
                while (CurrentDistance >= e.VerticalOffset && TopVisibleItem > 0)
                
                    CurrentDistance -= ((FrameworkElement)myItemControl.Items[TopVisibleItem]).ActualHeight;
                    TopVisibleItem -= 1;
                
        
    


    public enum MoveDirection
    
        Negative = -1,
        Positive = 1,
    

【讨论】:

【参考方案5】:

如果您启用了虚拟化ListView,那么您可以获得所有当前可见项,如下所示:

    获取 VirtualizingStackPanel 获取 VirtualizingStackPanel 中的所有 ListViewItems

代码如下所示。

VirtualizingStackPanel virtualizingStackPanel = FindVisualChild<VirtualizingStackPanel>(requiredListView);
List<ListViewItem> items = GetVisualChildren<ListViewItem>(virtualizingStackPanel);

功能如下所示。

private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        if (child != null && child is childItem)
            return (childItem)child;
        else
        
            childItem childOfChild = FindVisualChild<childItem>(child);
            if (childOfChild != null)
                return childOfChild;
        
    
    return null;


private List<childItem> GetVisualChildren<childItem>(DependencyObject obj) where childItem : DependencyObject

    List<childItem> childList = new List<childItem>();
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        if (child != null && child is childItem)
            childList.Add(child as childItem);
    

    if (childList.Count > 0)
        return childList;

    return null;

这将返回当前加载显示的 ListViewItem 列表。 希望它有所帮助:)。

【讨论】:

以上是关于获取 ListView 可见项的主要内容,如果未能解决你的问题,请参考以下文章

在 WPF ListView C# 中获取第一个可见项

如何找到所有可见的深层嵌套 ListView 项目?

为什么虚拟ListView的不可见项没有索引?

更改 ListView 中当前顶部可见项的颜色 - Flutter

如何为 ListView 的 ContextActions 的 MenuItem 添加可见性绑定

Xamarin Listview 仅添加 1 项我的列表中有 2 项