Прокрутите до нового элемента в ListView для UWP

Я создаю приложение чата с ListView, содержащим сообщения. Когда новое сообщение отправлено / получено, ListView должен прокрутить до нового сообщения.

Я использую MVVM, поэтому ListView выглядит как

<ScrollViewer>
    <ItemsControl Source="{Binding Messages}" />
</ScrollViewer>

Как я могу это сделать?

РЕДАКТИРОВАТЬ: Я пытался сделать эту работу в версиях до Anniversary Update, создающих поведение. Вот что у меня есть на данный момент:

public class FocusLastBehavior : Behavior<ItemsControl>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Items.VectorChanged += ItemsOnVectorChanged;
    }

    private void ItemsOnVectorChanged(IObservableVector<object> sender, IVectorChangedEventArgs @event)
    {
        var scroll = VisualTreeExtensions.FindVisualAscendant<ScrollViewer>(AssociatedObject);
        if (scroll == null)
        {
            return;
        }

        var last = AssociatedObject.Items.LastOrDefault();

        if (last == null)
        {
            return;
        }

        var container = AssociatedObject.ContainerFromItem(last);


        ScrollToElement(scroll, (UIElement)container);
    }

    private static void ScrollToElement(ScrollViewer scrollViewer, UIElement element,
        bool isVerticalScrolling = true, bool smoothScrolling = true, float? zoomFactor = null)
    {
        var transform = element.TransformToVisual((UIElement)scrollViewer.Content);
        var position = transform.TransformPoint(new Point(0, 0));

        if (isVerticalScrolling)
        {
            scrollViewer.ChangeView(null, position.Y, zoomFactor, !smoothScrolling);
        }
        else
        {
            scrollViewer.ChangeView(position.X, null, zoomFactor, !smoothScrolling);
        }
    }
}

В коде используются VisualTreeExtensions из набора инструментов сообщества UWP.

Однако позиция после вызова TransformPoint всегда возвращает {0, 0}

Что я делаю неправильно?


person SuperJMN    schedule 04.05.2017    source источник
comment
У вас есть элемент listview, к которому вы хотите прокрутить?   -  person Archana    schedule 04.05.2017
comment
Не заключайте ListView в ScrollViewer, он прокручивается автоматически. Чтобы получить доступ к его внутреннему ScrollViewer (например, чтобы вызвать его метод ChangeView для прокрутки до заданного смещения пикселей), используйте ListView.GetScrollViewer().   -  person andreask    schedule 04.05.2017
comment
Вы проверяли это?   -  person AVK    schedule 04.05.2017


Ответы (3)


Начиная с Windows 10 версии 1607, вы можете использовать ItemsStackPanel .

Кстати, да, я согласен с тем, что вы можете избавиться от ScrollViewer, поскольку он избыточен как ListView оболочка.

Upd:

KeepLastItemInView недоступен для приложений, предназначенных для Windows 10 до «Anniversary Edition». В этом случае один из способов убедиться, что в списке всегда отображается последний элемент после изменения коллекции элементов, - это переопределить _ 8_ и вызовите _ 9_. Базовая реализация будет выглядеть так:

using System.Linq;
using Windows.UI.Xaml.Controls;

public class ChatListView : ListView
{
    protected override void OnItemsChanged(object e)
    {
        base.OnItemsChanged(e);
        if(Items.Count > 0) ScrollIntoView(Items.Last());
    }
}
person DK.    schedule 04.05.2017
comment
Это идеальное решение, но, к сожалению, мой проект должен быть нацелен на сборку 10586. Возможно ли реализовать ItemsStackPanel? какие варианты есть в моем случае? - person SuperJMN; 05.05.2017
comment
К сожалению, Items​Updating​Scroll​Mode enum не имеет KeepLastItemInView в сборках 10586 и ниже, поэтому вам придется вызывать ListView.ScrollIntoView самостоятельно. Есть несколько вариантов, где сделать этот звонок. Я бы, вероятно, решил расширить ListView и вызвать его в переопределении OnItemsChanged, аналогично решению WPF, обсуждаемому в этот вопрос. - person DK.; 05.05.2017
comment
Отлично! Кстати, я переключился на ItemsControl, потому что мне не нужны все функции отслеживания выбора, которые предоставляет ListView. Будет ли это работать и для ItemsControl? Что касается ScrollViewer, будет ли он избыточным с ItemsControl вместо ListView? Спасибо! - person SuperJMN; 05.05.2017
comment
Конечно, если вы замените ListView на ItemsControl, вам нужно будет предоставить свою собственную обработку прокрутки, и ScrollViewer будет иметь здесь общий смысл. Вы все еще можете переопределить ItemsControl.OnItemsChanged (мне нравится этот подход, поскольку он позволяет полностью отделить код элементов управления пользовательского интерфейса от модели), но если вы пойдете этим путем, вам нужно будет либо передать ScrollViewer ссылку на этот метод, либо найти ее в визуальном дереве а затем прокрутите вниз. - person DK.; 05.05.2017
comment
Пожалуйста, обратите внимание на вопрос редактирования выше. Я имею дело с этим с помощью Behavior (ради возможности повторного использования), а вызов TransformPoint всегда возвращает точку 0,0, поэтому это не работает. - person SuperJMN; 09.05.2017

Здесь vm.newmessageItem - новое сообщение. Я использовал это, чтобы получить элемент списка, в котором вы хотите прокрутить.

   if (listview != null)
    {
    listview.UpdateLayout();
    listview.ScrollIntoView(vm.newmessageItem);
     var listViewItem = (FrameworkElement)listview.ContainerFromItem(vm.newmessageItem);
        while (listViewItem == null)
        {
        await Task.Delay(1);
        listViewItem = (FrameworkElement)listview.ContainerFromItem(vm.newmessageItem);
        }
        ScrollViewer scroll = Utility.FindFirstElementInVisualTree<ScrollViewer>(listview);

        var topLeft =
        listViewItem .TransformToVisual(listview)                                         .TransformPoint(new Point()).Y;
         var lvih = listViewItem.ActualHeight;
        var lvh = listview.ActualHeight;
         var desiredTopLeft = (lvh - lvih) ;

        var currentOffset = scroll.VerticalOffset;
         var desiredOffset = currentOffset + desiredTopLeft;
        listview.UpdateLayout();
        scroll.ChangeView(null, desiredOffset,null);
        scroll.UpdateLayout();
    }
person Archana    schedule 04.05.2017
comment
Я не понимаю, как мне вводить задачу. Задержка там. Для меня это похоже на взлом - person SuperJMN; 04.05.2017
comment
Кроме того, я думаю, что прямой доступ к ViewModel слишком сильно связывает логику, поэтому такое поведение нельзя будет использовать повторно. - person SuperJMN; 04.05.2017
comment
Можете ли вы разместить свой код там, где вы получите сообщение о событии? - person Archana; 04.05.2017
comment
Если вы знаете, вы можете написать логику для получения элемента списка без доступа к vm - person Archana; 04.05.2017

Эта одна строка кода автоматически перейдет к элементу в списке, который вы запрашиваете. Например, если у вас есть чат-клиент, и ваш ListView заполняется каждой записью. Это покажет последнюю запись внизу списка. Таким образом, пользователю не нужно каждый раз прокручивать страницу вниз. Здесь устанавливается переход к последнему элементу. Работает в приложении UWP.

MyListView?.ScrollIntoView(MyListView.Items[allMyItemsInList.Count - 1], ScrollIntoViewAlignment.Leading);
person Azurespot    schedule 23.08.2018