Windows 10 ScrollIntoView() 不会滚动到列表视图中间的项目 [英] Windows 10 ScrollIntoView() is not scrolling to the items in the middle of a listview

查看:44
本文介绍了Windows 10 ScrollIntoView() 不会滚动到列表视图中间的项目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个包含 20 个项目的列表视图.我想以编程方式滚动列表视图.

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?

推荐答案

我认为您正在寻找的是一种实际滚动元素到 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.

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

The idea is the same in your case.

您需要首先在 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 实例后,我创建了另外两个扩展方法,分别根据项目的索引或附加对象滚动到项目.由于ListViewGridView 共享相同的基类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.

基本上,这些方法将首先找到该项目,如果它已经呈现,然后立即滚动到它.如果该项为null,则表示已开启虚拟化,该项尚未实现.所以要先实现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() 不会滚动到列表视图中间的项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆