it-swarm.com.ru

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

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

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

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

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

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

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

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

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

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

16
Amar Zeno

Я думаю, что вы ищете метод на самом деле прокрутки элемент в верхней части 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.

Обновление

По сути, методы сначала найдут элемент, если он уже обработан, а затем сразу перейдут к нему. Если элемент 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<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);
25
Justin XL

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

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

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

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

1
Scott Chamberlain

Я решаю это как:

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

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

0
EJL