获取ListView可见项目

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

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

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

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

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

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

  • 读取它的ScrollViewer.ViewportHeight :它是显示的项目数。
    Rq: CanContentScroll必须为true。

在MSDN上查看这个问题,展示一种技术来找出可见的ListView项目 –

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

这是该post的相关代码 –

 listView.ItemsSource = from i in Enumerable.Range(0, 100) select "Item" + i.ToString(); listView.Loaded += (sender, e) => { ScrollViewer scrollViewer = listView.GetVisualChild(); //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 ; 这肯定会提高性能 。

更新:

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

ObservableCollection的真正好处是,如果您需要在运行时从ListView添加/删除项目,则在Array情况下,您将不得不重新分配ListViewItemSource ,并且ListView首先抛弃其先前的项目并重新生成它整个清单。

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

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

 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 GetVisibleItemsFromListbox(ListBox listBox, FrameworkElement parentToTestVisibility) { var items = new List(); foreach (var item in PhotosListBox.Items) { if (IsUserVisible((ListBoxItem)listBox.ItemContainerGenerator.ContainerFromItem(item), parentToTestVisibility)) { items.Add(item); } else if (items.Any()) { break; } } return items; } 

我怎么看东西:

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

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

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

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

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

  1. 获取VirtualizingStackPanel
  2. 获取VirtualizingStackPanel中的所有ListViewItems

代码如下所示。

 VirtualizingStackPanel virtualizingStackPanel = FindVisualChild(requiredListView); List items = GetVisualChildren(virtualizingStackPanel); 

function如下所示。

 private childItem FindVisualChild(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(child); if (childOfChild != null) return childOfChild; } } return null; } private List GetVisualChildren(DependencyObject obj) where childItem : DependencyObject { List childList = new List(); 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的列表。 希望能帮助到你 :)。