【问题标题】:How do I allow a UWP ListView to scroll past the last item?如何允许 UWP ListView 滚动到最后一项?
【发布时间】:2018-01-04 21:38:48
【问题描述】:

我有一个 ListView 有一堆不规则大小的项目。当您滚动 ListView 时,最后一项的底部将最终位于控件的底部:您无法继续滚动。

如果最后一项小于控件,我希望最后一项的top能够滚动到控件顶部(如果该项大于控件,我我对默认行为很好;在这种情况下,项目的顶部将滚动到满足我设计的 ListView 控件的顶部)。 (这类似于 editor.scrollBeyondLastLine 设置为 true 的 Visual Studio Code 中的行为)

我已经几乎完成了这项工作,但还没有完成。在我的尝试中,我将ListView 子类化,这样我就可以覆盖PrepareContainerForItemOverride

然后我尝试在项目的底部边距中添加足够的额外空间,以便项目的顶部位于 ListView 的顶部。

  protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);

        var lvi = element as ListViewItem;
        if (lvi == null) { return; }

        var currentIndex = this.Items.IndexOf(item);
        if (currentIndex == (this.Items.Count - 1))
        {
            var sv = this.ScrollViewer();
            lvi.Measure(new Size(sv.ViewportWidth, double.PositiveInfinity));

            var slackSpace = sv.ViewportHeight - lvi.DesiredSize.Height;
            if (slackSpace > 0)
            {
                lvi.Margin = new Thickness(lvi.Margin.Left, lvi.Margin.Top, lvi.Margin.Right, slackSpace + lvi.Margin.Bottom);
            }                
        }
    }
}

(在这段代码中,this.ScrollViewer() 只是一个小扩展方法,可以从任何ListView 中找出ScrollViewer。我在这里使用它来尝试将一些合理的东西传递给lvi.Measure。)

这不太行,因为ListViewItemDesiredSize.Height 最终比渲染的ListViewItemHeight 大一点(我不知道为什么)。

如果我知道DesiredSize.Height总是 比需要的大,我实际上可以忍受这种行为作为妥协...但我敢打赌这实际上不是真的。

所以我显然需要插入渲染管道的不同部分来管理它。但我不知道在哪里。

我是否应该以某种方式继承ListView's ScrollViewer(因为我认为ScrollViewer 是实际布置子ListViewItem 控件的原因?)?还有其他方法可以做到这一点吗?还是只是不可能?

【问题讨论】:

  • 你能放一些视觉效果更好地理解吗?

标签: uwp windows-10-universal


【解决方案1】:

理想情况下,ScrollViewer 的工作是在安排其子项(本例中的面板)后添加额外的间距。它仍然必须查看面板的最后一个子大小才能确切知道它应该增加多少空间。

但是由于ScrollViewer是密封的,我不知道这是否真的可能。

另一个技巧是创建一个自定义StackPanel,我们可以在其中调整测量的高度,以便最后一项将滚动到顶部。

底部的实际填充取决于ScrollViewer's ViewportHeight 因此多次通过测量布局步骤。

public class BottomPaddedStackPanel: StackPanel
{
    private ScrollViewer scroll = null;

    public BottomPaddedStackPanel()
    {
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        if (scroll == null)
            InitScrollViewerData();

        Size panelSize = base.MeasureOverride(availableSize);
        var lastChild = this.Children[this.Children.Count - 1];

        if (scroll == null || scroll.ViewportHeight == 0)
            return panelSize;

        // add more space at the bottom to be able to scroll the last item at the top
        if (lastChild.DesiredSize.Height < scroll.ViewportHeight)
            panelSize.Height += scroll.ViewportHeight - lastChild.DesiredSize.Height;

        return panelSize;
    }

    private void InitScrollViewerData()
    {
        var lastChild = this.Children[this.Children.Count - 1];
        var lv = ItemsControl.ItemsControlFromItemContainer(lastChild);
        Border rootBorder = VisualTreeHelper.GetChild(lv, 0) as Border;
        scroll = rootBorder.Child as ScrollViewer;
        scroll.SizeChanged += (o, ev) => InvalidateMeasure();
    }
}

设置为列表的布局面板:

<ListView ItemsSource="{x:Bind Items}">
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <local:BottomPaddedStackPanel />
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
</ListView>

【讨论】:

  • 这是完美的。现在我对 ItemsPanel 和 ItemsPanelTemplate 有了更多了解。谢谢。
猜你喜欢
  • 1970-01-01
  • 2021-02-26
  • 2010-10-29
  • 1970-01-01
  • 1970-01-01
  • 2018-04-09
  • 2017-10-02
  • 1970-01-01
  • 2019-03-15
相关资源
最近更新 更多