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