it-swarm.com.de

Windows 10 ScrollIntoView () scrollt nicht zu den Elementen in der Mitte einer Listenansicht

Ich habe eine Listenansicht mit 20 Elementen. Ich möchte die Listenansicht programmgesteuert scrollen.

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

blättert die Listenansicht zum ersten Element.

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

rollt die Listenansicht bis zum Ende der Seite.

Ich kann jedoch nicht dieselbe Funktion verwenden, um die Listenansicht zu einem Element in der Mitte zu scrollen.

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

sollte blättern und mich zum fünften Punkt der Liste bringen. Stattdessen bringt es mich zum ersten Punkt der Liste.

Wäre toll, wenn dieses Verhalten mit einer Problemumgehung erreicht werden kann?

16
Amar Zeno

Ich denke, was Sie suchen, ist eine Methode, um scroll ein Element an die Spitze der ListView zu verschieben.

In this post habe ich eine Erweiterungsmethode erstellt, die zu einem bestimmten Element innerhalb einer ScrollViewer scrollt.

Die Idee ist in Ihrem Fall die gleiche.

Sie müssen zuerst die ScrollViewer-Instanz in Ihrer ListView suchen, dann das eigentliche Element, zu dem Sie blättern möchten, d. H. Eine ListViewItem.

Hier ist eine Erweiterungsmethode, um die ScrollViewer zu erhalten.

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

Sobald ich die ScrollViewer-Instanz erhalten habe, habe ich zwei weitere Erweiterungsmethoden erstellt, um auf Basis des Index bzw. des angehängten Objekts zu einem Element zu scrollen. Da ListView und GridView dieselbe Basisklasse haben, ListViewBase. Diese beiden Erweiterungsmethoden sollten auch für GridView funktionieren.

Aktualisieren

Grundsätzlich werden die Methoden das Element zuerst finden, sofern es bereits gerendert ist, und dann sofort zu ihm scrollen. Wenn das Element null ist, bedeutet dies, dass die Virtualisierung aktiviert ist und das Element noch nicht realisiert wurde. Um das Element zuerst zu realisieren, rufen Sie ScrollIntoViewAsync auf (aufgabenbasierte Methode, um die eingebaute ScrollIntoView zu umschließen, ebenso wie ChangeViewAsync, die viel saubereren Code enthält), berechnen Sie die Position und speichern Sie sie. Da ich jetzt die Position weiß, zu der ich scrollen möchte, muss ich das Element zunächst ganz zurück zu seiner vorherigen Position sofort (d. H. Ohne Animation) und dann schließlich mit Animation an die gewünschte Position scrollen.

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


Einfacher, aber ohne Animation

Sie können auch die neue Überladung von ScrollIntoView verwenden, indem Sie den zweiten Parameter angeben, um sicherzustellen, dass das Element am oberen Rand ausgerichtet ist. In meinen vorherigen Erweiterungsmethoden ist dies jedoch nicht mit dem reibungslosen Bildlauf möglich.

MyListView?.ScrollIntoView(MyListView.Items[5], ScrollIntoViewAlignment.Leading);
25
Justin XL

ScrollIntoView bringt das Element nur in die Ansicht, Punkt, es wird nicht zu einer Zeile gescrollt.

Wenn Sie es für ein Mitglied aufrufen und es sich unterhalb der sichtbaren Liste befindet, scrollt es nach unten, bis das Element das letzte Mitglied in der sichtbaren Liste ist.

Wenn Sie es für ein Mitglied aufrufen und es sich oberhalb der Liste befindet, wird nach oben gescrollt, bis das Element das erste Mitglied in der Liste ist.

Wenn Sie es für ein Mitglied aufrufen und es derzeit sichtbar ist, wird keine Operation ausgeführt.

1

Ich löse das wie folgt:

 var sv = new ScrollViewerHelper().GetScrollViewer(listView);
        sv.UpdateLayout();
        sv.ChangeView(0, sv.ExtentHeight, null);

Und die GetScrollViewer-Methode:

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

kredite an den Besitzer des Codes

0
EJL