Ответ 1
Я думаю, что то, что вы ищете, - это метод фактического прокрутки элемента до вершины 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
используют один и тот же базовый класс ListViewBase
. Эти два метода расширения должны также работать для GridView
.
Update
В основном, методы сначала найдут элемент, если он уже отображен, затем прокрутите его прямо сейчас. Если элемент 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 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 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<object>();
var scrollViewer = listViewBase.GetScrollViewer();
EventHandler<ScrollViewerViewChangedEventArgs> viewChanged = (s, e) => tcs.TrySetResult(null);
try
{
scrollViewer.ViewChanged += viewChanged;
listViewBase.ScrollIntoView(item, ScrollIntoViewAlignment.Leading);
await tcs.Task;
}
finally
{
scrollViewer.ViewChanged -= viewChanged;
}
}
public static async Task ChangeViewAsync(this ScrollViewer scrollViewer, double? horizontalOffset, double? verticalOffset, bool disableAnimation)
{
var tcs = new TaskCompletionSource<object>();
EventHandler<ScrollViewerViewChangedEventArgs> viewChanged = (s, e) => tcs.TrySetResult(null);
try
{
scrollViewer.ViewChanged += viewChanged;
scrollViewer.ChangeView(horizontalOffset, verticalOffset, null, disableAnimation);
await tcs.Task;
}
finally
{
scrollViewer.ViewChanged -= viewChanged;
}
}
Более простой подход, но без анимации
Вы также можете использовать новую перегрузку ScrollIntoView
, указав второй параметр, чтобы убедиться, что элемент выровнен по верхнему краю; однако при этом в моих предыдущих методах расширения нет плавного перехода прокрутки.
MyListView?.ScrollIntoView(MyListView.Items[5], ScrollIntoViewAlignment.Leading);