问题描述
我有一个包含 20 个项目的列表视图.我想以编程方式滚动 Listview.
I have a Listview with 20 items in it. I want to scroll the Listview programmatically.
ListView?.ScrollIntoView(ListView.Items[0])
将列表视图滚动到第一项.
will scroll the listview to the first item.
ListView?.ScrollIntoView(ListView.Items.Count - 1)
将列表视图滚动到页面底部.
will scroll the listview to the bottom of the page.
但是,我无法使用相同的功能将列表视图滚动到中间的项目.
However, I am unable to use the same function to scroll the listview to an item in middle.
Eg: ListView?.ScrollIntoView(ListView.Items[5])
应该滚动并带我到列表的第 5 项.但它却把我带到了列表的第一项.
should scroll and take me to the 5th item of the list. But instead its taking me to the first item of the list.
如果可以通过一些变通方法来实现此行为会很好吗?
Would be great if this behaviour can be achieved with some workaround?
推荐答案
我认为您正在寻找的是一种将元素实际 scroll 到 ListView 顶部的方法代码>.
I think what you are looking for is a method to actually scroll an element to the top of the ListView
.
在这篇文章中,我创建了一个滚动到 ScrollViewer
中特定元素的扩展方法.
In this post, I created an extension method that scrolls to a particular element within a ScrollViewer
.
你的想法是一样的.
您需要首先在 ListView
中找到 ScrollViewer
实例,然后是要滚动到的实际项目,即 ListViewItem
.
You need to first find the ScrollViewer
instance within your ListView
, then the actual item to scroll to, that is, a 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
.
Once I get the ScrollViewer
instance, I have created two more extension methods to scroll to an item based on its index or attached object respectively. Since ListView
and GridView
are sharing the same base class ListViewBase
. These two extension methods should also work for GridView
.
基本上,这些方法将首先找到该项目,如果它已经渲染,然后立即滚动到它.如果item为null
,则表示虚拟化开启,item尚未实现.所以首先要实现item,调用ScrollIntoViewAsync
(基于任务的方法来包装内置的ScrollIntoView
,和ChangeViewAsync
一样,提供了很多更清晰的代码),计算位置并保存.因为现在我知道要滚动到的位置,所以我需要先将项目一直滚动到它之前的位置立即(即没有动画),然后最后用动画滚动到所需的位置.
Basically, the methods will first find the item, if it's already rendered, then scroll to it right away. If the item is null
, it means the virtualization is on and the item has yet to be realized. So to realize the item first, call ScrollIntoViewAsync
(task-based method to wrap the built-in ScrollIntoView
, same as ChangeViewAsync
, which offers much cleaner code), calculate the position and save it. Since now I know the position to scroll to, I need to first scroll the item all the way back to its previous position instantly (i.e. without animation), and then finally scroll to the desired position with animation.
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<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
的新重载,以确保项目在顶部边缘对齐;但是,这样做并没有我以前的扩展方法中的平滑滚动过渡.
You can also use the new overload of ScrollIntoView
by specifying the second parameter to make sure the item is aligned on the top edge; however, doing so doesn't have the smooth scrolling transition in my previous extension methods.
MyListView?.ScrollIntoView(MyListView.Items[5], ScrollIntoViewAlignment.Leading);
这篇关于Windows 10 ScrollIntoView() 没有滚动到列表视图中间的项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!