在UWP中滚动到ListView的新项目

4

我正在创建一个带有消息列表的聊天应用程序。当发送/接收新消息时,ListView 应该滚动到新消息。

我正在使用 MVVM 模式,因此 ListView 如下:

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

我该怎么做?
编辑:我曾试图在周年更新之前创建一个行为,使其在之前的版本中起作用。这是我迄今为止所做的:
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);
        }
    }
}

这段代码使用了来自UWP Community Toolkit的VisualTreeExtensions。

然而,在调用TransformPoint后,位置始终返回{0, 0}

我做错了什么?


你能获取到想要滚动的ListViewItem吗? - Archana
不要在ListView中包裹ScrollViewer,它已经自动可滚动。要访问其内部的ScrollViewer(例如,调用其ChangeView方法以滚动到给定的像素偏移),请使用ListView.GetScrollViewer()。 - andreask
你有没有查看这个 - AVK
3个回答

7

从Windows 10版本1607开始,您可以使用ItemsStackPanel。通过将ItemsUpdatingScrollMode设置为KeepLastItemInView,这似乎是最自然的选择。

在MS UWP文档(2017-2-8)中有一个"Inverted Lists"示例,它可以简化为以下XAML:

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

作为旁注,我同意你可能需要摆脱ScrollViewer,因为它作为ListView包装器是多余的。
更新:
如果应用程序的目标是“周年版”之前的Windows 10,则不可使用KeepLastItemInView。如果是这种情况,确保在项集合更改后列表始终显示最后一项的一种方法是重写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());
    }
}

这是理想的解决方案,但不幸的是我的项目必须针对10586版本进行构建。实现ItemsStackPanel是否可行?在我这种情况下有哪些选项? - SuperJMN
1
很遗憾,在10586及以下版本的构建中,ItemsUpdatingScrollMode枚举没有KeepLastItemInView选项,因此您必须自己调用ListView.ScrollIntoView。有多个选项可用于放置此调用。我可能会选择扩展ListView并在OnItemsChanged重写中调用它,类似于此问题中讨论的WPF解决方案。 - DK.
不错!顺便说一下,我已经转换到了ItemsControl,因为我不需要ListView提供的所有选择跟踪功能。这对于ItemsControl也适用吗?关于ScrollViewer,它在ItemsControl中是否也会变得多余而不是ListView?谢谢! - SuperJMN
当然,如果您将ListView替换为ItemsControl,则必须提供自己的滚动处理,并且在那里使用ScrollViewer是完全合理的。您仍然可以重写ItemsControl.OnItemsChanged(我喜欢这种方法,因为它允许完全将UI控件代码与模型解耦),但是如果您选择这种方式,则需要将ScrollViewer引用传递给该方法或在可视树中查找它,然后滚动到底部。 - DK.
请查看上面的编辑问题。为了重用性,我正在使用行为来处理它,并且TransformPoint调用始终返回点0,0,因此它无法正常工作。 - SuperJMN

1

这里的vm.newmessageItem是新消息。我使用它来获取您想要滚动的listviewitem。

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

我不明白为什么我要在那里引入一个task.Delay。这对我来说看起来像是一种hack。 - SuperJMN
此外,我认为直接访问ViewModel会使逻辑耦合过度,因此这种行为不可重用。 - SuperJMN
你能否发布你的代码,其中包含接收消息事件的部分? - Archana
如果您知道如何编写逻辑以获取ListView项目而不访问VM,则可以实现。 - Archana

0
这一行代码将自动跳转到您请求的列表项。例如,如果您有一个聊天客户端,并且您的ListView每个条目都会被填充。这将在列表底部显示最新的条目。因此,用户不必每次滚动到底部。这里设置为跳转到最后一个项目。适用于UWP应用程序。
MyListView?.ScrollIntoView(MyListView.Items[allMyItemsInList.Count - 1], ScrollIntoViewAlignment.Leading);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接