Windows 10 ScrollIntoView() 没有滚动到列表视图中间的项目
Posted
技术标签:
【中文标题】Windows 10 ScrollIntoView() 没有滚动到列表视图中间的项目【英文标题】:Windows 10 ScrollIntoView() is not scrolling to the items in the middle of a listview 【发布时间】:2015-12-10 00:10:09 【问题描述】:我有一个包含 20 个项目的列表视图。我想以编程方式滚动 Listview。
ListView?.ScrollIntoView(ListView.Items[0])
将列表视图滚动到第一项。
ListView?.ScrollIntoView(ListView.Items.Count - 1)
将列表视图滚动到页面底部。
但是,我无法使用相同的功能将列表视图滚动到中间的项目。
Eg: ListView?.ScrollIntoView(ListView.Items[5])
应该滚动并带我到列表的第 5 项。但它却把我带到了列表的第一项。
如果可以通过一些变通方法来实现此行为会很好吗?
【问题讨论】:
【参考方案1】:我认为您正在寻找的是一种将元素实际滚动到ListView
顶部的方法。
在this post 中,我创建了一个扩展方法,可以滚动到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
实例后,我创建了另外两个扩展方法,分别根据其索引或附加对象滚动到一个项目。因为ListView
和GridView
共享同一个基类ListViewBase
。这两种扩展方法也应该适用于GridView
。
更新
基本上,这些方法将首先找到该项目,如果它已经渲染,然后立即滚动到它。如果项目为null
,则表示虚拟化已开启,项目尚未实现。所以要先实现item,调用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<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);
【讨论】:
工作就像一个魅力。谢谢贾斯汀 :) 嗨贾斯汀!我想知道如何在 MVVM 方法中附加相同的逻辑。就像在转换器中使用 changeview 逻辑并将其直接绑定到 ListView 滚动查看器 XAML。 考虑将逻辑包装在附加属性中。看看***.com/q/8370209/231837 你是个天才。我爱你! @JustinXL 非常感谢。在您的帮助下,我成功创建了SmoothScrollingHelper【参考方案2】:ScrollIntoView 只是将项目带入视图,句号,它不会滚动到一行。
如果您在成员上调用它并且它位于可见列表底部的下方,它会向下滚动,直到该项目是可见列表中的最后一个成员。
如果你在一个成员上调用它并且它在列表顶部的上方,它会向上滚动,直到该项目是列表中的第一个成员。
如果你在一个成员上调用它并且它当前是可见的,它根本不做任何操作。
【讨论】:
即使我调用可见列表底部下方的成员,该功能也不起作用。假设,我在可见列表视图中有 3 个项目。 ListView.ScrollIntoView(abc.ItemsCollection[6].item) 理想情况下应该将第 6 项作为可见列表视图的最后一项。但就我而言,什么都没有发生。【参考方案3】:我这样解决这个问题:
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;
感谢代码所有者
【讨论】:
【参考方案4】:试试这个:
listView.SelectedIndex = i;
SemanticZoomLocation location = new SemanticZoomLocation
Item = listView.SelectedItem
;
listView.MakeVisible(location);
或者滚动到中间,没有选择项:
SemanticZoomLocation location = new SemanticZoomLocation
Item = listView.Items[listView.Items.Count / 2]
;
listView.MakeVisible(location);
【讨论】:
以上是关于Windows 10 ScrollIntoView() 没有滚动到列表视图中间的项目的主要内容,如果未能解决你的问题,请参考以下文章
在 scrollIntoView 的动态长度数组中使用 Refs
scrollIntoView将指定元素定位到浏览器顶部,底部,中间