Получить видимые элементы ListView

У меня есть ListView, который может содержать много элементов, поэтому это virtualized и элементы утилизации. Он не использует сортировку. Мне нужно обновить отображение значений, но когда слишком много элементов, слишком медленно обновлять все, поэтому я хотел бы обновить только видимые элементы.

Как я могу получить список всех отображаемых в данный момент элементов? Я попытался заглянуть в ListView или в ScrollViewer, но я до сих пор не знаю, как это сделать. Решение не должно проходить через все элементы, чтобы проверить, можно ли их увидеть, потому что это будет слишком медленно.

Я не уверен, что код или xaml будут полезны, это всего лишь virtualized/Recycling ListView с его ItemSource привязанным к Array.

Изменить: Ответ:
спасибо akjoshi, я нашел способ:

  • получить ScrollViewer ListView (с помощью метода FindDescendant, который вы можете сделать с помощью VisualTreeHelper).

  • прочитайте его ScrollViewer.VerticalOffset: это номер первого показанного элемента

  • прочитайте его ScrollViewer.ViewportHeight: это количество показанных предметов.
    Rq: CanContentScroll должен быть правдой.

Ответ 1

Посмотрите на этот вопрос в MSDN, демонстрируя технику, чтобы узнать видимые элементы ListView -

Как найти строки (ListViewItem (s)) в ListView, которые на самом деле видны?

Здесь соответствующий код из этого сообщения -

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; что, безусловно, улучшит производительность.

Update:

Ya, который может быть правдой (Array vs. ObservableCollection), но я хотел бы увидеть некоторые статистические данные, связанные с этим;

Настоящая выгода ObservableCollection заключается в том, что у вас есть требование добавлять/удалять элементы из вашего ListView во время выполнения, в случае Array вам придется переназначить ItemSource из ListView и ListView сначала отбрасывает свои предыдущие элементы и регенерирует весь его список.

Ответ 2

Попытавшись найти что-то подобное, я подумал, что поделился бы моим результатом (как это кажется легче, чем другие ответы):

Простой тест на видимость, который я получил от здесь.

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

Затем вы можете перебирать элементы listbox и использовать этот тест, чтобы определить, какие из них видны. Поскольку 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;
}

Ответ 3

Как я вижу вещи:

  • с одной стороны, у вас есть данные. Они должны быть в курсе последних событий, потому что именно здесь ваша информация находится в памяти. Итерация в вашем списке данных должна быть довольно быстрой и, самое главное, может быть выполнена в другом потоке, в фоновом режиме

  • с другой стороны, у вас есть дисплей. Ваш ListView уже делает трюк обновления только отображенных данных, поскольку это виртуализация! Вам больше не нужны трюки, они уже на месте!

В прошлой работе использование привязки на ObservableCollection является хорошим советом. Если вы намерены изменить ObservableCollection из другого потока, я бы рекомендовал следующее: http://blog.quantumbitdesigns.com/2008/07/22/wpf-cross-thread-collection-binding-part-1/

Ответ 4

Я трачу много времени на поиск лучшего решения для этого, В моей ситуации у меня есть scrollviewer, наполненный элементами с пользовательскими настройками, которые можно установить видимыми/невидимыми, и я придумал это. Он делает то же самое, что и выше, но с долей CPU. Надеюсь, это поможет кому-то. Первыми элементами списка /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,
    }