带有Canvas的ScrollViewer,自动调整画布到包含负空间的内容

时间:2010-11-21 23:37:26

标签: wpf canvas scrollviewer

我在滚动查看器中有一个画布。 为了能够使用scrollviewer,我重写了Canvas的MeasureOverride方法以返回所有子节点的大小。

这很好用,除了画布只能包含正空间中的项目以使scrollviewer正常工作。

我希望用户能够拖动元素而不必限制它们必须位于画布原点的正面。

基本上我希望能够将元素定位在任何位置,例如(-200,10)或(500,-20),并且能够从最左边的元素(-200)滚动到最右边(500),从上到下。

使问题复杂化可以使用LayoutTransform缩放画布,并且我想在滚动条中包含视图区域,因此不仅滚动条会考虑子画面的最小/最大边界,而且也是当前视图区域的最小值/最大值。

有人知道如何使这项工作吗?

2 个答案:

答案 0 :(得分:1)

今天我只处理这个问题:) 很高兴分享有效的代码:

public void RepositionAllObjects(Canvas canvas)
{
    adjustNodesHorizontally(canvas);
    adjustNodesVertically(canvas);
}

private void adjustNodesVertically(Canvas canvas)
{
    double minLeft = Canvas.GetLeft(canvas.Children[0]);
    foreach (UIElement child in canvas.Children)
    {
        double left = Canvas.GetLeft(child);
        if (left < minLeft)
            minLeft = left;
    }

    if (minLeft < 0)
    {
        minLeft = -minLeft;
        foreach (UIElement child in canvas.Children)
            Canvas.SetLeft(child, Canvas.GetLeft(child) + minLeft);
    }
}

private void adjustNodesHorizontally(Canvas canvas)
{
    double minTop = Canvas.GetTop(canvas.Children[0]);
    foreach (UIElement child in canvas.Children)
    {
        double top = Canvas.GetTop(child);
        if (top < minTop)
            minTop = top;
    }

    if (minTop < 0)
    {
        minTop = -minTop;
        foreach (UIElement child in canvas.Children)
            Canvas.SetTop(child, Canvas.GetTop(child) + minTop);
    }
}

现在调用RepositionAllObjects方法在需要时重新对齐对象。

当然,您需要拥有自己的Canvas画布。这个方法是必需的(如果你愿意,你可以调整它):

    public static Rect GetDimension(UIElement element)
    {
        Rect box = new Rect();
        box.X = Canvas.GetLeft(element);
        box.Y = Canvas.GetTop(element);
        box.Width = element.DesiredSize.Width;
        box.Height = element.DesiredSize.Height;
        return box;
    }

    protected override Size MeasureOverride(Size constraint)
    {
        Size availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
        double minX = 900000; //some dummy high number
        double minY = 900000; //some dummy high number
        double maxX = 0;
        double maxY = 0;

        foreach (UIElement element in this.Children)
        {
            element.Measure(availableSize);

            Rect box = GetDimension(element);
            if (minX > box.X) minX = box.X;
            if (minY > box.Y) minY = box.Y;
            if (maxX < box.X + box.Width) maxX = box.X + box.Width;
            if (maxY < box.Y + box.Height) maxY = box.Y + box.Height;
        }

        if (minX == 900000) minX = 0;
        if (minY == 900000) minY = 0;

        return new Size { Width = maxX - minX, Height = maxY - minY };
    }

现在,您需要做的就是将此画布包装在scrollviewer中。

希望它有所帮助!

答案 1 :(得分:0)

我认为ScrollViewer假定原点位于(0,0) - 毕竟,没有办法告诉它; Canvas是唯一知道指定和非零起源的面板。

一个简单的选择可能是忘记ScrollViewer,并实现自己的平移功能 - 可能是拖动,或者可能在视图左侧有一个大的“向左滚动”按钮,“向右滚动”按钮在右边等等 - 并根据对内容的转换来实现它。

但是如果你需要坚持使用经典的滚动条比喻,我认为你可以创建自己的面板(或者覆盖ArrangeOverride,这相当于同样的事情)并偏移所有位置 - 使它们从零开始。因此,如果在(20,-20)处有一个元素而在(0,5)处有另一个元素,则需要将所有内容向下偏移20,以使其适合从零开始的空间;当你布置你的孩子时,第一个将在(20,0)和第二个在(0,25)。

但现在滚动很奇怪。如果用户一直滚动到底部,并从底部边缘拖出一些东西,则视图保持不变;与右边相同。但如果它们一直滚动到顶部,并从顶部边缘拖出一些东西,突然一切都向下跳,因为ScrollViewer的VerticalOffset之前为零并且仍然为零,但零意味着因布局偏移而有所不同。 / p>

您可以通过绑定Horizo​​ntalOffset和VerticalOffset来绕过滚动怪异。如果ViewModel在控件移动“否定”时自动修复了这些属性(并触发了PropertyChanged),那么即使您现在正在告诉WPF的布局,您也可以将视图滚动到相同的逻辑内容引擎,一切都在别的地方。即,您的ViewModel将跟踪逻辑滚动位置(零,在拖动元素负数之前),并将基于零的滚动位置报告给ScrollViewer(因此在拖动之后,您将告诉ScrollViewer其VerticalOffset现在为20 )。