16

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

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

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

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

  • 获得ScrollViewerof the ListView (使用一种FindDescendant方法,您可以自己使用VisualTreeHelper)。

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

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

5 回答 5

8

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

您应该做的另一件事是使用ObservableCollectionas yourItemSource而不是Array; 那肯定会提高性能

更新:

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

真正的好处ObservableCollection是,如果您需要ListView在运行时从您的项目中添加/删除项目,那么Array您将不得不重新分配ItemSourceofListView并且第ListView一个丢弃其先前的项目并重新生成其整个列表。

于 2012-06-25T10:48:13.953 回答
8

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

我从这里得到的简单能见度测试。

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 并使用该测试来确定哪些是可见的。由于列表框项的顺序始终相同,因此此列表中的第一个可见项将是用户的第一个可见项。

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;
}
于 2012-09-28T14:28:28.660 回答
1

我如何看待事物:

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

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

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

于 2012-06-25T11:24:20.950 回答
0

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

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

代码如下所示。

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列表。希望能帮助到你 :)。

于 2018-05-05T05:39:33.583 回答
0

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