【问题标题】:How to scroll to the next logical page in a ListBox with physical scrolling如何通过物理滚动滚动到 ListBox 中的下一个逻辑页面
【发布时间】:2011-11-07 16:09:42
【问题描述】:

我确实有一个必须具有 CanContentScroll==false 的列表框,因为我需要能够顺利滚动它。这可以实现物理滚动。

我也想逐页滚动列表框,但如果我在列表框内部 ScrollViewer 上调用 PageDown 方法,第一行会被剪切,因为列表框高度不是行高的倍数。

我希望第一行始终完全可见,就像使用逻辑滚动时一样。

有人可以给我一个提示吗?

【问题讨论】:

    标签: wpf listbox scroll


    【解决方案1】:

    如果您向下滚动然后用鼠标选择上方可见容器,则非虚拟化ItemsControl (ScrollViewer.CanContentScroll="False") 将获得与虚拟化相同的效果。这也可以在代码中完成。

    当 CanContentScroll 设置为 false 时,虚拟化将关闭,因此所有容器都将始终生成。为了获得顶部可见的容器,我们可以从顶部迭代容器,直到到达ScrollViewer 的VerticalOffset。一旦我们得到它,我们可以简单地在它上面调用BringIntoView,它会在顶部很好地对齐,就像使用虚拟化时一样。

    例子

    <ListBox ItemsSource="{Binding MyCollection}"
             ScrollViewer.CanContentScroll="False"
             ScrollViewer.ScrollChanged="listBox_ScrollChanged" >
    

    在事件处理程序的顶部可见容器上调用BringIntoView

    private void listBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        ItemsControl itemsControl = sender as ItemsControl;
        ScrollViewer scrollViewer = e.OriginalSource as ScrollViewer;
        FrameworkElement lastElement = null;
        foreach (object obj in itemsControl.Items)
        {
            FrameworkElement element = itemsControl.ItemContainerGenerator.ContainerFromItem(obj) as FrameworkElement;
            double offset = element.TransformToAncestor(scrollViewer).Transform(new Point(0, 0)).Y + scrollViewer.VerticalOffset;
            if (offset > e.VerticalOffset)
            {
                if (lastElement != null)
                    lastElement.BringIntoView();
                break;
            }
            lastElement = element;
        }
    }
    

    要仅在您想调用PageDown 时实现此效果,例如在按钮单击中,您可以为ListBox 创建一个名为LogicalPageDown 的扩展方法。

    listBox.LogicalPageDown();
    

    ListBoxExtensions

    public static class ListBoxExtensions
    {
        public static void LogicalPageDown(this ListBox listBox)
        {
            ScrollViewer scrollViewer = VisualTreeHelpers.GetVisualChild<ScrollViewer>(listBox);
            ScrollChangedEventHandler scrollChangedHandler = null;
            scrollChangedHandler = (object sender2, ScrollChangedEventArgs e2) =>
            {
                scrollViewer.ScrollChanged -= scrollChangedHandler;
                FrameworkElement lastElement = null;
                foreach (object obj in listBox.Items)
                {
                    FrameworkElement element = listBox.ItemContainerGenerator.ContainerFromItem(obj) as FrameworkElement;
                    double offset = element.TransformToAncestor(scrollViewer).Transform(new Point(0, 0)).Y + scrollViewer.VerticalOffset;
                    if (offset > scrollViewer.VerticalOffset)
                    {
                        if (lastElement != null)
                            lastElement.BringIntoView();
                        break;
                    }
                    lastElement = element;
                }
            };
            scrollViewer.ScrollChanged += scrollChangedHandler;
            scrollViewer.PageDown();
        }
    }
    

    我在您的问题中注意到您已经获得了ScrollViewer,但如果其他人遇到此问题,我将向GetVisualChild 添加一个实现

    public static T GetVisualChild<T>(DependencyObject parent) where T : Visual
    {
        T child = default(T);
    
        int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < numVisuals; i++)
        {
            Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
            child = v as T;
            if (child == null)
            {
                child = GetVisualChild<T>(v);
            }
            if (child != null)
            {
                break;
            }
        }
        return child;
    }
    

    【讨论】:

    • @Zmaster:忘了Margin等在查找容器的相对位置时会涉及到。相应地更新了我的答案
    • 最后我决定使用 ScrollToOffset() 通过计算目标偏移量并假设每个项目具有相同的高度(这是我的情况)。它比迭代项目更有效。另一方面,您的解决方案也适用于不同的项目高度,所以我接受了它作为答案。
    【解决方案2】:

    如果您将“新页面”的顶部项目滚动到视图中,这会实现您所寻找的吗? 见ScrollIntoView。

    【讨论】:

    • 我认为该项目会出现在列表框的底部而不是顶部。另外,我不知道如何识别该项目。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-19
    • 2013-09-14
    • 2020-10-18
    • 2022-01-20
    相关资源
    最近更新 更多