如何允许 UWP ListView 滚动到最后一项? [英] How do I allow a UWP ListView to scroll past the last item?

查看:36
本文介绍了如何允许 UWP ListView 滚动到最后一项?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a ListView with a bunch of irregularly sized items. As you scroll the ListView, the bottom of the last item will end up at the bottom of the control: you can't keep scrolling.

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

If the last item is smaller than the control, I want the top of the last item to be able to scroll to the top of the control (if the item is larger than the control, I am fine with the default behavior; in this case, the top of the item will have scrolled past the top of the ListView control which satisfies my design). (This is similar to the behavior in something like Visual Studio Code with editor.scrollBeyondLastLine setting set to true)

几乎已经开始工作了,但还没有完全到位.在我的尝试中,我将 ListView 子类化,以便我可以覆盖 PrepareContainerForItemOverride.

I've almost got this working but it's not quite there. In my attempt, I subclassed ListView so I could override PrepareContainerForItemOverride.

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

I then attempt to add enough extra space to the item's bottom margin so the item's top will rest at the ListView's top.

  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.)

(In this code, this.ScrollViewer() is just a little extension method that fishes out the ScrollViewer from any ListView. I'm using it here to try to pass something sensible to lvi.Measure.)

这不太有效,因为 ListViewItemDesiredSize.Height 最终比呈现 ListViewItem (我不知道为什么).

This doesn't quite work because the DesiredSize.Height of the ListViewItem ends up being a good bit larger than the Height of the rendered ListViewItem (I don't know why).

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

I could actually live with this behavior as a compromise if I knew that the DesiredSize.Height would always be larger than needed...but I bet that's not actually true.

所以我显然需要插入渲染管道的不同部分来管理这个.但我想不通在哪里.

So I clearly need to plug into a different part of the render pipeline to manage this. But I can't figure out where.

我是否应该以某种方式对 ListView's ScrollViewer 进行子类化(因为我认为 ScrollViewer 实际上是布置子类 ListViewItem 控件?)?有没有其他方法可以做到这一点?或者这是不可能的?

Should I be subclassing the ListView's ScrollViewer somehow (since I think the ScrollViewer is what is actually laying out the child ListViewItem controls?)? Is there some other way to do this? Or is it just impossible?

推荐答案

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

Ideally it would be ScrollViewer's job to add extra spacing after it had arranged its child (panel in this case). Still it will have to look into panel's last child size to know exactly how much more space it should add.

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

But since ScrollViewer is sealed, I don't know if that's actually possible.

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

Another Hack, would be to create a custom StackPanel where we can adjust the measured height so that the last item will be scrolled on top.

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

The actual padding on bottom depends on the ScrollViewer's ViewportHeight hence the multiple passes through measure layout step.

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

设置为列表的布局面板:

Set as the list's layout panel:

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

这篇关于如何允许 UWP ListView 滚动到最后一项?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆