Windows 10 ScrollIntoView()不会滚动到列表视图中间的项目

我有一个包含20个项目的Listview。 我想以编程方式滚动Listview。

ListView?.ScrollIntoView(ListView.Items[0]) 

将列表视图滚动到第一个项目。

 ListView?.ScrollIntoView(ListView.Items.Count - 1) 

将列表视图滚动到页面底部。

但是,我无法使用相同的function将列表视图滚动到中间的项目。

 Eg: ListView?.ScrollIntoView(ListView.Items[5]) 

应该滚动并带我到列表的第5项。 但它把我带到了列表的第一项。

如果通过一些解决方法可以实现这种行为,那会很棒吗?

我认为您正在寻找的是一种实际将元素滚动ListView顶部的方法。

在这篇文章中 ,我创建了一个扩展方法,滚动到ScrollViewer的特定元素。

在你的情况下,这个想法是一样的。

您需要首先在ListView找到ScrollViewer实例,然后找到要滚动到的实际项目,即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实例,我就创建了两个扩展方法,分别根据索引或附加对象滚动到一个项目。 由于ListViewGridView共享相同的基类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(); var scrollViewer = listViewBase.GetScrollViewer(); EventHandler 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(); EventHandler 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); 

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

对代码所有者的信用