Windows 10 ScrollIntoView () не прокручивает элементы в середине списка
У меня есть Listview с 20 элементами в нем. Я хочу прокручивать Listview программно.
ListView?.ScrollIntoView(ListView.Items[0])
прокрутит список до первого элемента.
ListView?.ScrollIntoView(ListView.Items.Count - 1)
прокрутит список в нижней части страницы.
- Как реализовать воздушное сообщение в приложении WPF
- Конвертер связыванияПараметр
- Пример использования гиперссылки в WPF
- Связывание со статическим свойством
- Значение * (звездочка) в WPF ColumnDefinition?
Тем не менее, я не могу использовать одну и ту же функцию для прокрутки списка к элементу посередине.
Eg: ListView?.ScrollIntoView(ListView.Items[5])
должен прокрутить и перенести меня в пятый элемент списка. Но вместо этого он ведет меня к первому пункту списка.
Было бы здорово, если это поведение может быть достигнуто с помощью некоторого обходного пути?
- Как растянуть WPF вкладку Элементы заголовка к размеру родительского контроля
- Привязать к методу в WPF?
- Как создать пользовательский элемент управления WPF с содержимым NAMED
- Ошибка WPF 40 Ошибка BindingExpression path: свойство не найдено в 'объекте'
- WPF MVVM Зачем использовать ContentControl + DataTemplate Views, а не прямые XAML Window Views?
- Что делает звезда WPF (Width = "100 *")
- Вызывающий stream должен быть STA, потому что многие компоненты пользовательского интерфейса требуют этого
- Как извлечь шаблон управления по умолчанию в Visual Studio?
Я думаю, что вы ищете метод, чтобы прокрутить элемент вверху ListView
.
В этом сообщении я создал метод расширения, который прокручивается к определенному элементу в ScrollViewer
.
Идея в вашем случае такая же.
Вам нужно сначала найти экземпляр ScrollViewer
в вашем ListView
, а затем фактический элемент для прокрутки до, то есть ListViewItem
.
Ниже приведен метод расширения для получения ScrollViewer
.
public static ScrollViewer GetScrollViewer(this DependencyObject element) { if (element is ScrollViewer) { return (ScrollViewer)element; } for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) { var child = VisualTreeHelper.GetChild(element, i); var result = GetScrollViewer(child); if (result == null) { continue; } else { return result; } } return null; }
Как только я получу экземпляр ScrollViewer
, я создал еще два метода расширения для прокрутки к элементу на основе его индекса или прикрепленного объекта соответственно. Поскольку ListView
и GridView
используют один и тот же базовый class ListViewBase
. Эти два метода расширения также должны работать для GridView
.
Обновить
В основном, методы сначала найдут элемент, если он уже отображен, затем прокрутите его прямо сейчас. Если элемент имеет значение null
, это означает, что виртуализация включена, и элемент еще не реализован. Итак, чтобы сначала реализовать элемент, вызовите ScrollIntoViewAsync
(метод на основе задач, чтобы обернуть встроенный ScrollIntoView
, такой же, как ChangeViewAsync
, который предлагает гораздо более чистый код), вычислить позицию и сохранить ее. Поскольку теперь я знаю позицию для прокрутки, мне нужно сначала прокрутить элемент до последней позиции (т.е. без анимации), а затем, наконец, прокрутить до нужной позиции с анимацией.
public async static Task ScrollToIndex(this ListViewBase listViewBase, int index) { bool isVirtualizing = default(bool); double previousHorizontalOffset = default(double), previousVerticalOffset = default(double); // get the ScrollViewer withtin the ListView/GridView var scrollViewer = listViewBase.GetScrollViewer(); // get the SelectorItem to scroll to var selectorItem = listViewBase.ContainerFromIndex(index) as SelectorItem; // when it's null, means virtualization is on and the item hasn't been realized yet if (selectorItem == null) { isVirtualizing = true; previousHorizontalOffset = scrollViewer.HorizontalOffset; previousVerticalOffset = scrollViewer.VerticalOffset; // call task-based ScrollIntoViewAsync to realize the item await listViewBase.ScrollIntoViewAsync(listViewBase.Items[index]); // this time the item shouldn't be null again selectorItem = (SelectorItem)listViewBase.ContainerFromIndex(index); } // calculate the position object in order to know how much to scroll to var transform = selectorItem.TransformToVisual((UIElement)scrollViewer.Content); var position = transform.TransformPoint(new Point(0, 0)); // when virtualized, scroll back to previous position without animation if (isVirtualizing) { await scrollViewer.ChangeViewAsync(previousHorizontalOffset, previousVerticalOffset, true); } // scroll to desired position with animation! scrollViewer.ChangeView(position.X, position.Y, null); } public async static Task ScrollToItem(this ListViewBase listViewBase, object item) { bool isVirtualizing = default(bool); double previousHorizontalOffset = default(double), previousVerticalOffset = default(double); // get the ScrollViewer withtin the ListView/GridView var scrollViewer = listViewBase.GetScrollViewer(); // get the SelectorItem to scroll to var selectorItem = listViewBase.ContainerFromItem(item) as SelectorItem; // when it's null, means virtualization is on and the item hasn't been realized yet if (selectorItem == null) { isVirtualizing = true; previousHorizontalOffset = scrollViewer.HorizontalOffset; previousVerticalOffset = scrollViewer.VerticalOffset; // call task-based ScrollIntoViewAsync to realize the item await listViewBase.ScrollIntoViewAsync(item); // this time the item shouldn't be null again selectorItem = (SelectorItem)listViewBase.ContainerFromItem(item); } // calculate the position object in order to know how much to scroll to var transform = selectorItem.TransformToVisual((UIElement)scrollViewer.Content); var position = transform.TransformPoint(new Point(0, 0)); // when virtualized, scroll back to previous position without animation if (isVirtualizing) { await scrollViewer.ChangeViewAsync(previousHorizontalOffset, previousVerticalOffset, true); } // scroll to desired position with animation! scrollViewer.ChangeView(position.X, position.Y, null); } public static async Task ScrollIntoViewAsync(this ListViewBase listViewBase, object item) { var tcs = new TaskCompletionSource
Более простой подход, но без анимации
Вы также можете использовать новую перегрузку ScrollIntoView
, указав второй параметр, чтобы убедиться, что элемент выровнен по верхнему краю; однако при этом мои предыдущие методы расширения не имеют плавного перехода прокрутки.
MyListView?.ScrollIntoView(MyListView.Items[5], ScrollIntoViewAlignment.Leading);
ScrollIntoView просто приносит элемент в представление, период, он не прокручивается до строки.
Если вы вызываете его на члене, и он находится ниже нижней части видимого списка, он прокручивается до тех пор, пока элемент не станет последним членом в видимом списке.
Если вы назовете его членом, и он находится над верхней частью списка, он прокручивается до тех пор, пока элемент не станет первым членом в списке.
Если вы вызываете его на члене, и он в настоящее время виден, он вообще не выполняет никаких операций.
Я решаю это как:
var sv = new ScrollViewerHelper().GetScrollViewer(listView); sv.UpdateLayout(); sv.ChangeView(0, sv.ExtentHeight, null);
И метод GetScrollViewer:
public ScrollViewer GetScrollViewer(DependencyObject element) { if (element is ScrollViewer) { return (ScrollViewer)element; } for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) { var child = VisualTreeHelper.GetChild(element, i); var result = GetScrollViewer(child); if (result == null) { continue; } else { return result; } } return null; }
кредиты владельцу кода