web-dev-qa-db-fra.com

Windows 10 ScrollIntoView () ne fait pas défiler les éléments au milieu d'une listview

J'ai un Listview avec 20 éléments. Je veux faire défiler la liste vue par programme.

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

fera défiler la liste jusqu'au premier élément.

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

fera défiler la liste jusqu'au bas de la page.

Cependant, je ne parviens pas à utiliser la même fonction pour faire défiler la liste à un élément au milieu.

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

devrait défiler et me prendre au 5ème élément de la liste. Mais au lieu de cela, cela me conduit au premier élément de la liste.

Serait bien si ce problème peut être obtenu avec une solution de contournement?

16
Amar Zeno

Je pense que ce que vous recherchez est une méthode pour réellement scroll un élément en haut de la ListView.

Dans this post , j'ai créé une méthode d'extension qui fait défiler un élément particulier dans une variable ScrollViewer.

L'idée est la même dans votre cas.

Vous devez d’abord rechercher l’instance ScrollViewer dans votre ListView, puis l’élément auquel vous souhaitez accéder, c’est-à-dire une ListViewItem.

Voici une méthode d'extension pour obtenir la variable 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;
}

Une fois que j'ai obtenu l'instance ScrollViewer, j'ai créé deux méthodes d'extension supplémentaires pour faire défiler un élément en fonction de son index ou de son objet attaché, respectivement. Puisque ListView et GridView partagent la même classe de base ListViewBase. Ces deux méthodes d'extension devraient également fonctionner pour GridView.

Mettre à jour

En gros, les méthodes vont d'abord trouver l'élément, s'il est déjà rendu, puis le faire défiler immédiatement. Si l'élément est null, cela signifie que la virtualisation est activée et que l'élément n'a pas encore été réalisé. Donc, pour réaliser l’article en premier, appelez ScrollIntoViewAsync (méthode basée sur la tâche pour encapsuler la ScrollIntoView intégrée, comme pour ChangeViewAsync, qui offre un code beaucoup plus propre), calculer la position et la sauvegarder. Depuis que je connais la position de défilement, je dois tout d’abord faire défiler l’élément jusqu’à sa position précédente instantanément (c’est-à-dire sans animation), puis enfin faire défiler jusqu’à la position souhaitée avec 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;
    }
}


Une approche plus simple, mais sans animation

Vous pouvez également utiliser la nouvelle surcharge de ScrollIntoView en spécifiant le deuxième paramètre pour vous assurer que l'élément est aligné sur le bord supérieur; Cependant, cela n'a pas la transition de défilement régulier dans mes méthodes d'extension précédentes.

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

ScrollIntoView amène simplement l'élément dans la vue, point, il ne défile pas jusqu'à une ligne.

Si vous l'appelez sur un membre situé en bas de la liste visible, l'élément défile jusqu'à ce que l'élément soit le dernier membre de la liste visible.

Si vous l'appelez sur un membre situé en haut de la liste, l'élément défile jusqu'à ce que l'élément soit le premier membre de la liste.

Si vous l'appelez sur un membre et qu'il est actuellement visible, aucune opération n'est effectuée.

1
Scott Chamberlain

Je résous ceci comme:

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

Et la méthode 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;
    }

crédits au propriétaire du code

0
EJL