【问题标题】:ScrollViewer doesn't scroll for custom panelScrollViewer 不滚动自定义面板
【发布时间】:2014-11-17 14:40:56
【问题描述】:

我正在制作自己的自定义面板,当内容不适合可用空间时,它应该垂直滚动,所以我将它放在 ScrollViewer 中。 现在,当里面的面板比 ScrollViewer 本身大时,我无法让 ScrollViewer 激活滚动条。

permille 函数获得附加属性,告诉孩子必须与可用大小(不滚动)相比有多大,也就是 ViewPort。 由于在 MeasureOverride 中传递的大小是无限的,我认为我不能在那里使用 permille 函数。 这就是为什么我在 ArrangeOverride 中测量我的孩子(我猜这不是最佳实践)但这样滚动查看器不会滚动。

我如何让它工作?

我的 XAML 代码:

<ScrollViewer>
    <controls:TilePanel x:Name="TilePanel" PreviewMouseLeftButtonDown="TilePanel_PreviewMouseLeftButtonDown" PreviewMouseLeftButtonUp="TilePanel_PreviewMouseLeftButtonUp" 
                        PreviewMouseMove="TilePanel_PreviewMouseMove" DragEnter="TilePanel_DragEnter" Drop="TilePanel_Drop" AllowDrop="True" />
</ScrollViewer>

我的自定义面板类:

/// <summary>
/// A Panel Showing Tiles
/// </summary>
public class TilePanel : PermillePanel
{
        public TilePanel()
        {
        }

        protected override Size MeasureOverride(Size constraint)
        {
            //here constraint width or height can be infinite.
            //as tiles are a permille of that height, they too can be infinite after measuring
            //this is unwanted behavior, so we measure in the ArrangeOverride method

            if (constraint.Width == double.PositiveInfinity)
            {
                return new Size(0, constraint.Height);
            }
            else if (constraint.Height == double.PositiveInfinity)
            {
                return new Size(constraint.Width, 0);
            }
            else
            {
                return constraint;
            }
        }

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            //return base.ArrangeOverride(arrangeSize);

            foreach (FrameworkElement child in InternalChildren)
            {
                Size availableSize = new Size();
                //set the width and height for the child
                availableSize.Width = arrangeSize.Width * TilePanel.GetHorizontalPermille(child) / 1000;
                availableSize.Height = arrangeSize.Height * TilePanel.GetVerticalPermille(child) / 1000;

                child.Measure(availableSize);

            }

            // arrange the children on the panel
            // fill lines horizontally, when we reach the end of the current line, continue to the next line

            Size newSize = new Size(arrangeSize.Width, arrangeSize.Height);

            double xlocation = 0;
            double ylocation = 0;

            double ystep = 0;

            double maxYvalue = 0;

            foreach (FrameworkElement child in InternalChildren)
            {
                double endxlocation = xlocation + child.DesiredSize.Width;

                double constrainedWidth = arrangeSize.Width * TilePanel.GetHorizontalPermille(child) / 1000;
                double constrainedHeight = arrangeSize.Height * TilePanel.GetVerticalPermille(child) / 1000;

                if (TilePanel.GetVerticalPermille(child) != 0 && TilePanel.GetHorizontalPermille(child) != 0)
                {
                    //horizontal overflow -> next line
                    if (endxlocation >= this.DesiredSize.Width *1.01)
                    {
                        ylocation += ystep;
                        xlocation = 0;
                    }
                }

                Rect rect = new Rect(xlocation, ylocation, constrainedWidth, constrainedHeight);
                child.Arrange(rect);
                xlocation += constrainedWidth;
                ystep = Math.Max(ystep, constrainedHeight);
                maxYvalue = Math.Max(maxYvalue, ystep + constrainedHeight);
            }

            if (maxYvalue > newSize.Height)
            {
                newSize.Height = maxYvalue;
            }

            return newSize;
        }
    }

【问题讨论】:

    标签: c# wpf scrollviewer


    【解决方案1】:

    ArrangeOverride() 内部调用Measure() 会导致问题。框架检测到这一点并强制重新测量。在MeasureOverride() 中设置一个跟踪点,我敢打赌你会看到它不断地被调用,即使布局没有改变1

    如果您绝对必须从ArrangeOverride() 调用Measure(),则需要有条件地这样做,以便仅当自上次调用Measure() 后可用大小实际发生变化时才强制重新测量。然后,只要布局无效,您就会有效地结束两次测量 + 排列,而不是只有一次。但是,这样的方法很老套,我建议坚持仅在 MeasureOverride() 内进行测量的最佳做法。


    1有趣的是,尽管布局中存在明显的“无限循环”,您的 UI 仍可能会响应输入。

    【讨论】:

    • 实际上,让我的控件在 ArrayngeOverride 函数中进行 Measue 不会调用无限循环(通过代码中的断点验证)。条件方法没有用。我同意在 MeasureOverride 中调用 Measure,但我不知道如何根据视口缩放我的孩子。
    • 即使它不会让你陷入布局循环,它仍然会导致奇怪的行为,特别是对于嵌套在 ScrollViewer 中的控件。如果您将可用空间限制为某个大但非无限的值,或祖先ScrollViewer 的视口大小,您可以将度量代码移回MeasureOverride。或者,您可以计算ArrangeOverride 中的“理想尺寸”,将其存储在一个字段中,并使度量无效以强制第二次布局传递(然后从MeasureOverride() 返回理想的宽度/高度而不是零)。不过要小心避免循环。
    • 用循环尝试过,非常接近,但仍然有一些奇怪的行为。我最终使用 Canvas 并通过设置高度、宽度、左侧和顶部来放置我的控件。没那么漂亮,也可能没那么快,但我猜它可以完成这项工作
    【解决方案2】:

    如果您想在ScrollViewer 中使用自定义Panel,则必须添加执行实际滚动的代码。您可以通过在您的自定义 Panel 中实现 IScrollInfo Interface 来做到这一点。

    您可以在 Tech Pro 网站的WPF Tutorial - Implementing IScrollInfo 页面中找到解释此接口并提供示例代码实现的教程。这是一个相当简单的过程,看起来有点像这样:

    public void LineDown() { SetVerticalOffset(VerticalOffset + LineSize); }
    
    public void LineUp() { SetVerticalOffset(VerticalOffset - LineSize); }
    
    public void MouseWheelDown() { SetVerticalOffset(VerticalOffset + WheelSize); }
    
    public void MouseWheelUp() { SetVerticalOffset(VerticalOffset - WheelSize); }
    
    public void PageDown() { SetVerticalOffset(VerticalOffset + ViewportHeight); }
    
    public void PageUp() { SetVerticalOffset(VerticalOffset - ViewportHeight); }
    
    ...
    

    【讨论】:

    • 只有当 OP 想要在 ScrollViewer.CanContentScroll="True" 时支持自定义滚动行为时,才需要实现 IScrollInfo。简单的像素级滚动应该可以工作。
    • 这不是我想要的,但也许我可以将它作为最后的手段。
    • 如果你想滚动自定义数量,你只需要使用IScrollInfo,而不是使用默认的滚动行为。例如,您可能希望LineDown()(单击“向下”滚动按钮)将一行图块滚动到视图中。这不是您当前问题的理想解决方案,但了解它很有用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多