【问题标题】:Scroll to new item in ListView for UWP滚动到 UWP 的 ListView 中的新项目
【发布时间】:2017-10-02 13:14:23
【问题描述】:

我正在使用包含消息的 ListView 创建一个聊天应用程序。当发送/接收新消息时,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}

我做错了什么?

【问题讨论】:

  • 您是否获得了要滚动到的列表视图项?
  • 不要将ListView 包裹在ScrollViewer 中,它会自动滚动。要访问其内部ScrollViewer(例如,调用其ChangeView 方法以滚动到给定的像素偏移),请使用ListView.GetScrollViewer()
  • 你检查this了吗?

标签: c# .net xaml uwp windows-community-toolkit


【解决方案1】:

从 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 包装器是多余的。

更新:

KeepLastItemInView 不适用于面向“周年纪念版”之前的 Windows 10 的应用程序。如果是这种情况,确保列表在项目集合更改后始终显示最后一个项目的一种方法是覆盖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是否可行?我的情况有哪些选择?
  • 不幸的是,Items​Updating​Scroll​Mode 枚举在 10586 及以下版本中没有 KeepLastItemInView,因此您必须自己调用 ListView.ScrollIntoView。有多种选择可以拨打此电话。我可能会选择扩展ListView 并在OnItemsChanged 覆盖中调用它,类似于this question 中讨论的WPF 解决方案。
  • 不错!顺便说一句,我已切换到 ItemsControl,因为我不需要 ListView 提供的所有选择跟踪功能。这也适用于 ItemsControl 吗?关于 ScrollViewer,它是否会与 ItemsControl 而不是 ListView 冗余?谢谢!
  • 当然,如果你用ItemsControl 替换ListView,你必须提供你自己的滚动处理,ScrollViewer 在那里完全有意义。您仍然可以覆盖ItemsControl.OnItemsChanged(我喜欢这种方法,因为它允许将 UI 控件代码与模型完全分离),但如果您采用这种方式,您需要将 ScrollViewer 引用传递给该方法或查找它在可视化树中,然后滚动到底部。
  • 请看一下上面的编辑问题。我正在使用 Behavior(为了可重用性)来处理它,并且 TransformPoint 调用总是返回点 0,0,所以它不起作用。
【解决方案2】:

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

【讨论】:

  • 我不明白我必须如何引入任务。在那里延迟。对我来说这似乎是一个黑客攻击
  • 另外,我认为直接访问 ViewModel 会过多地耦合逻辑,因此这种行为将无法重用。
  • 您可以在收到消息的事件中发布您的代码吗?
  • 如果你知道你可以写一个逻辑来获取listview项而不访问vm
【解决方案3】:

这一行代码将自动转到您请求的列表中的项目。例如,如果您有一个聊天客户端,并且您的 ListView 会填充每个条目。这将在列表底部显示最新条目。因此,用户不必每次都滚动到底部。此处设置为转到最后一项。在 UWP 应用中工作。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 2017-02-20
    • 1970-01-01
    相关资源
    最近更新 更多