Подтвердить что ты не робот

Windows 10 ScrollIntoView() не прокручивает элементы в середине списка

У меня есть Listview с 20 элементами в нем. Я хочу программно прокрутить список.

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

прокрутит список к первому элементу.

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

прокрутит список в нижней части страницы.

Однако я не могу использовать одну и ту же функцию для прокрутки списка к элементу в середине.

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

нужно прокрутить и перенести меня в пятый элемент списка. Но вместо этого он приведет меня к первому элементу списка.

Было бы здорово, если это поведение может быть достигнуто с помощью некоторого обходного пути?

4b9b3361

Ответ 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);

Ответ 2

ScrollIntoView просто переносит элемент в представление, период, он не прокручивается до строки.

Если вы вызываете его на члене и находится ниже нижней части видимого списка, он прокручивается до тех пор, пока элемент не станет последним членом в видимом списке.

Если вы вызываете его на члене и он находится над верхней частью списка, он прокручивается до тех пор, пока элемент не станет первым членом в списке.

Если вы вызываете его на члене, и он в настоящее время виден, он вообще не выполняет никаких операций.

Ответ 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;
    }

кредит владельцу кода