问题 获取ListView可见项目


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

我怎样才能获得所有当前显示项目的列表?我试着调查一下 ListView 或者在 ScrollViewer,但我仍然不知道如何实现这一目标。如果可以看到解决方案,解决方案不得通过所有项目进行测试,因为这样做太慢了。

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

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

  • 得到的 ScrollViewer 的 ListView  (用 FindDescendant 方法,你可以自己做的 VisualTreeHelper )。

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

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

9378
2018-06-25 10:16


起源

你是如何填写ListView的?显式创建ListViewItem? ItemSource的设置?捆绑 ?也许给我们一些代码吧! - Vivien Ruiz


答案:


在MSDN上查看这个问题,展示一种找出可见的技术 ListView 物品 -

如何在ListView中找到实际可见的行(ListViewItem(s))?

这是该帖子的相关代码 -

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;肯定会的 提高性能

更新:

雅可能是真的(array 与 ObservableCollection)但我希望看到一些与此相关的统计数据;

真正的好处 ObservableCollection 如果您有要求添加/删除您的项目 ListView 在运行时,如果是 Array 你将不得不重新分配 ItemSource 的 ListView 和 ListView 首先抛弃其先前的项目并重新生成其整个列表。


6
2018-06-25 10:48



Rq:对于性能和内存使用情况,该数组为我的应用程序提供了非常大量的Observable集合。我使用的物品数量在100.000-1.000.000范围内。 - GameAlchemist
您提供的MS链接将List与Observable集合进行比较,项目数较少(1000)并且关闭虚拟化,很可能是因为没有明显的差异。所以它不适用于我的情况,我想知道它是否与任何情况相关(为什么有人会关闭虚拟化?) - GameAlchemist
@VincentPiel:查看我的更新。 - akjoshi
ListView只会重新生成项目,因为除了MS :)之外没有人关闭虚拟化。在我的应用程序中,所有数组都可能在刷新时发生变化。项目的ObsColl会导致计数> 200.000(Win XP)的内存异常,并且该计数的过滤时间> 10分钟。随着阵列我在1分钟内达到5.000.000。我希望我可以提供一些“证据”,但我知道WPF没有JSPerf ...对我来说底线是:ObsColl对于不那么大的集合(并且更方便)更好,但没有什么可以击败阵列>> 100.000件物品。 - GameAlchemist
ObservableCollection的问题是它不喜欢线程,即使你使用Dispatcher也是如此。我不得不切换到常规列表并告诉应用程序只更新计数更改,因为OC丢失了它添加了多少项目与列表中存在多少项目,这导致WPF崩溃。 - Patrick


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

我得到的简单可见性测试 这里

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

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

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

6
2017-09-28 14:28





我怎么看东西:

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

  • 另一方面,你有显示器。你的 ListView 已经制作了仅刷新显示数据的技巧,因为它正在虚拟化!你不需要更多技巧,它已经到位了!

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


1
2018-06-25 11:24



如果您不了解它,您可能对MVVM模式感兴趣;) - Vivien Ruiz
我没有调查使用CPU的是什么,但是迭代我的列表并且在一个属性上对所有项目使用NotifyPropertyChanged对于必须执行我的程序的(慢)计算机而言任务太重。该列表可能是100.000项长。因此,虚拟化无法挽救这一天。我测试ObservableCollection比我的应用程序中的数组慢5倍。 - GameAlchemist
访问数组肯定比访问ObservableCollection更快。但ObservableCollection使用绑定完成了保持UI最新的所有工作。根据我的经验,创建新的图形项目大部分时间都是如此。不是数据列表背后的工作。 - Vivien Ruiz


我花了很多时间为此寻找更好的解决方案, 在我的情况下,我有一个滚动查看器,充满了可以设置为可见/不可见的自定义高度的项目,我想出了这个。它与上述解决方案相同,但只占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,
    }

0
2017-09-01 10:19





如果启用了虚拟化 列表显示,然后您可以获得所有当前可见项目,如下所示:

  1. 获取VirtualizingStackPanel
  2. 获取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的 加载显示。 希望能帮助到你 :)。


0
2018-05-05 05:39