web-dev-qa-db-ja.com

Windows 10 ScrollIntoView()は、リストビューの途中のアイテムまでスクロールしません

20個のアイテムを含むリストビューがあります。リストビューをプログラムでスクロールしたい。

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インスタンスを取得したら、インデックスまたはアタッチされたオブジェクトに基づいてアイテムにスクロールするための拡張メソッドをさらに2つ作成しました。 ListViewGridViewは同じ基本クラスListViewBaseを共有しているため。これらの2つの拡張メソッドは、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;
    }
}

より単純なアプローチですが、アニメーションはありません

2番目のパラメーターを指定して、ScrollIntoViewの新しいオーバーロードを使用して、アイテムが上端に配置されていることを確認することもできます。ただし、これを行うと、以前の拡張メソッドではスムーズなスクロール遷移が得られません。

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

ScrollIntoViewは、アイテムをビュー、ピリオドに移動するだけで、行にスクロールしません。

メンバーでそれを呼び出し、それが表示リストの下部にある場合、アイテムが表示リストの最後のメンバーになるまで下にスクロールします。

メンバーでそれを呼び出し、それがリストの一番上にある場合、アイテムがリストの最初のメンバーになるまで上にスクロールします。

メンバーでそれを呼び出し、それが現在表示されている場合、それはまったく操作を行いません。

1

私はこれを次のように解決します:

 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