Выделите новый элемент в 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}

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

3 ответа

Решение

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

В документах MS UWP (2017-2-8) приведен пример "Перевернутые списки", который сводится к следующему XAML:

<ListView Source="{Binding Messages}">
   <ListView.ItemsPanel>
       <ItemsPanelTemplate>
           <ItemsStackPanel
               VerticalAlignment="Bottom"
               ItemsUpdatingScrollMode="KeepLastItemInView"
           />
       </ItemsPanelTemplate>
   </ListView.ItemsPanel>
</ListView>

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

Upd:

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

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

Здесь 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();
    }

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

MyListView?.ScrollIntoView(MyListView.Items[allMyItemsInList.Count - 1], ScrollIntoViewAlignment.Leading);
Другие вопросы по тегам