带 Canvas 的 ScrollViewer,自动调整画布大小以适应内容(包括负空间)

发布于 2024-10-04 01:03:21 字数 395 浏览 0 评论 0原文

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

这工作得很好,只是画布只能包含正空间中的项目,以便滚动查看器正常工作。

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

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

让事情变得复杂的是,可以使用 LayoutTransform 缩放画布,并且我想在滚动条中包含视图区域,因此滚动条不仅考虑子项的最小/最大边界,而且还考虑子项的最小/最大边界。 /当前视图区域的最大值。

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

I have a canvas in a scrollviewer.
To be able to use the scrollviewer, I've overridden the Canvas's MeasureOverride method to return the size of all children.

This works fine, except that the canvas can only contain items in positive space for the scrollviewer to work correctly.

I'd like the user to be able to drag elements around without the restriction of them having to be on the positive side of the canvas's origin.

So basicly I want to be able to position the elements anywhere, like at (-200, 10) or (500, -20) , and be able to scroll from the most left element (-200) to the most right(500), and from top to bottom.

To complicate matters the canvas can be scaled using the LayoutTransform, and I'd like to include the view-area in the scrollbars, so not only the min/max bounds of the children are taken into account by the scrollbars, but also the min/max of the current view-area.

Does anybody know how to make this work ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

不美如何 2024-10-11 01:03:21

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

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

现在,您需要做的就是将此画布包装在滚动查看器中。

希望有帮助!

Today I worked on this problem only :)
Happy to share the code which works:

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

Now call RepositionAllObjects method to realign the objects as and when required.

Of course, you need to have your own canvas derived from Canvas. And this method is required (you can tweak it if you like):

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

Now, all you need to do is wrap this canvas inside a scrollviewer.

Hope it helps!

∞梦里开花 2024-10-11 01:03:21

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

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

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

但现在滚动很奇怪。如果用户一直滚动到底部,并将某些内容拖离底部边缘,视图将保持不变;与右边相同。但是,如果它们一直滚动到顶部,并将某些内容拖离顶部边缘,突然所有内容都会向下跳跃,因为 ScrollViewer 的 VerticalOffset 之前为零,现在仍然为零,但由于布局偏移,零意味着不同的东西。

您也许可以通过绑定 Horizo​​ntalOffset 和 VerticalOffset 来解决滚动问题。如果您的 ViewModel 自动修复了这些属性(并触发了 PropertyChanged< /a>) 每当控件移动到“负”时,您就可以使视图滚动到相同的逻辑内容,即使您现在告诉 WPF 的布局引擎一切都在其他地方。即,您的 ViewModel 将跟踪逻辑滚动位置(零,在将元素拖动为负之前),并将从零开始的滚动位置报告给 ScrollViewer (因此在拖动之后,您将告诉 ScrollViewer 它的 VerticalOffset 现在是 20 )。

I think the ScrollViewer assumes that the origin will be at (0, 0) -- after all, there's no way to tell it otherwise; Canvas is the only panel that knows about specified-and-nonzero origins.

A simple option might be to forget about a ScrollViewer, and implement your own panning functionality -- perhaps with dragging, or perhaps with a big "scroll left" button on the left side of the view, a "scroll right" button on the right, etc. -- and implement it in terms of a transform on the content.

But if you need to stick with the classic scrollbar metaphor, I think you could make your own panel (or override ArrangeOverride too, which amounts to the same thing) and offset all the positions -- make them zero-based. So if you have an element at (20, -20) and another at (0, 5), you would need to offset everything downward by 20 to make it fit in a zero-based space; and when you lay out your children, the first would go at (20, 0) and the second at (0, 25).

But now scrolling is weird. If the user is scrolled all the way to the bottom, and drags something off the bottom edge, the view stays put; same with the right. But if they're scrolled all the way to the top, and drag something off the top edge, suddenly everything jumps downward, because the ScrollViewer's VerticalOffset was zero before and is still zero, but zero means something different because of your layout offset.

You might be able to get around the scrolling weirdness by binding the HorizontalOffset and VerticalOffset. If your ViewModel automatically fixed up those properties (and fired PropertyChanged) whenever a control moves "negative", then you might be able to keep the view scrolled to the same logical content, even though you're now telling WPF's layout engine that everything is somewhere else. I.e., your ViewModel would track the logical scroll position (zero, before you drag the element negative), and would report the zero-based scroll position to the ScrollViewer (so after the drag, you would tell the ScrollViewer that its VerticalOffset is now 20).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文