【问题标题】:Get ListView Visible items获取 ListView 可见项
【发布时间】: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 ?捆绑 ?也许给我们一些代码!

标签: c# .net wpf vb.net listview


【解决方案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,那么您可以获得所有当前可见项,如下所示:

      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 列表。 希望它有所帮助:)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-01
        • 2014-12-07
        • 1970-01-01
        • 2011-02-24
        • 2014-09-23
        • 2013-04-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多