Получить видимые элементы 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,
}