.net WPF:如何使画布自动调整大小?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/855334/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 12:41:24  来源:igfitidea点击:

WPF: How to make canvas auto-resize?

.netwpfresizewpf-controlsscrollviewer

提问by Qwertie

I would like my Canvasto automatically resize to the size of its items, so that the ScrollViewerscroll bars have the correct range. Can this be done in XAML?

我希望我Canvas自动调整其项目的大小,以便ScrollViewer滚动条具有正确的范围。这可以在 XAML 中完成吗?

<ScrollViewer HorizontalScrollBarVisibility="Auto" x:Name="_scrollViewer">
    <Grid x:Name ="_canvasGrid" Background="Yellow">
        <Canvas x:Name="_canvas" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Green"></Canvas>
        <Line IsHitTestVisible="False" .../>
    </Grid>
</ScrollViewer>

In the above code the canvas always has size 0, though it doesn't clip its children.

在上面的代码中,画布的大小始终为 0,但它不会剪裁其子项。

回答by Arcturus

No this is not possible (see snippet from MSDN below). However, if you want to have scrollbars and auto-resizing, consider using a Gridinstead, and use the Margin property to position your items on this Grid.. Grid will tell the ScrollViewer how big he wants to be, and you will get the scrollbars.. Canvas will always tells the ScrollViewer he doesn't need any size.. :)

不,这是不可能的(请参阅下面的 MSDN 片段)。但是,如果您想要滚动条和自动调整大小,请考虑使用Grid,并使用 Margin 属性将您的项目定位在此 Grid 上。 Grid 将告诉 ScrollViewer 他想要多大,您将获得滚动条 .. Canvas 将始终告诉 ScrollViewer 他不需要任何大小.. :)

Grid lets you enjoy both worlds - As long as you're putting all elements into a single cell, you get both: Arbitrary positioning and auto-sizing. In general it is good to remember that most panel controls (DockPanel, StackPanel, etc) can be implemented via a Grid control.

网格让您同时享受两个世界 - 只要您将所有元素放入一个单元格中,您就可以同时获得:任意定位和自动调整大小。一般来说,最好记住大多数面板控件(DockPanel、StackPanel 等)可以通过 Grid 控件实现。

From MSDN:

MSDN

Canvas is the only panel element that has no inherent layout characteristics. A Canvas has default Height and Width properties of zero, unless it is the child of an element that automatically sizes its child elements. Child elements of a Canvas are never resized, they are just positioned at their designated coordinates. This provides flexibility for situations in which inherent sizing constraints or alignment are not needed or wanted. For cases in which you want child content to be automatically resized and aligned, it is usually best to use a Grid element.

Canvas 是唯一没有固有布局特征的面板元素。Canvas 的默认 Height 和 Width 属性为零,除非它是自动调整其子元素大小的元素的子元素。Canvas 的子元素永远不会调整大小,它们只是定位在指定的坐标处。这为不需要或不需要固有大小限制或对齐的情况提供了灵活性。对于您希望子内容自动调整大小和对齐的情况,通常最好使用 Grid 元素。

Hope this helps

希望这可以帮助

回答by MikeKulls

I'm just copying illef's answer here but in answer to PilotBob, you just define a canvas object like this

我只是在这里复制了illef的答案,但在回答PilotBob时,您只需定义一个这样的画布对象

public class CanvasAutoSize : Canvas
{
    protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint)
    {
        base.MeasureOverride(constraint);
        double width = base
            .InternalChildren
            .OfType<UIElement>()
            .Max(i => i.DesiredSize.Width + (double)i.GetValue(Canvas.LeftProperty));

        double height = base
            .InternalChildren
            .OfType<UIElement>()
            .Max(i => i.DesiredSize.Height + (double)i.GetValue(Canvas.TopProperty));

        return new Size(width, height);
    }
}

and then use CanvasAutoSize in your XAML.

然后在 XAML 中使用 CanvasAutoSize。

            <local:CanvasAutoSize VerticalAlignment="Top" HorizontalAlignment="Left"></local:CanvasAutoSize>

I prefer this solution to the one presented above that uses the grid as it works through attached properties and just requires setting less properties on the elements.

我更喜欢这个解决方案,而不是上面介绍的使用网格的解决方案,因为它通过附加属性工作,并且只需要在元素上设置较少的属性。

回答by illef

I think you can resize Canvasby overriding MeasureOverrideor ArrangeOverridemethods.

我认为您可以Canvas通过覆盖MeasureOverrideArrangeOverride方法来调整大小。

This job is not difficult.

这份工作并不难。

You can see this post. http://illef.tistory.com/entry/Canvas-supports-ScrollViewer

你可以看看这个帖子。http://illef.tistory.com/entry/Canvas-supports-ScrollViewer

I hope this helps you.

我希望这可以帮助你。

Thank you.

谢谢你。

回答by Redsplinter

I see you've got a workable solution, but I thought I'd share.

我看到你有一个可行的解决方案,但我想我会分享。

<Canvas x:Name="topCanvas">
    <Grid x:Name="topGrid" Width="{Binding ElementName=topCanvas, Path=ActualWidth}" Height="{Binding ElementName=topCanvas, Path=ActualHeight}">
        ...Content...
    </Grid>
</Canvas>

The above technique will allow you to nest a grid inside a canvas and have dynamic resizing. Further use of dimension binding makes it possible to mix dynamic material with static material, perform layering, etc. There are too many possibilities to mention, some harder than others. For example I use the approach to simulate animatating content moving from one grid location to another - doing the actual placement at the animation's completion event. Good luck.

上述技术将允许您在画布内嵌套网格并动态调整大小。进一步使用尺寸绑定可以将动态材料与静态材料混合,进行分层等。有太多的可能性要提及,有些比其他的更难。例如,我使用这种方法来模拟从一个网格位置移动到另一个网格位置的动画内容 - 在动画的完成事件中进行实际放置。祝你好运。

回答by Robin Davies

Essentially it requires a complete rewrite of Canvas. Previous proposed solutions that override MeasureOverride fail because the default Canvas.Left/.Top &c properties invalidate Arrangment, but ALSO need to invalidate measure. (You get the right size the first time, but the size doesn't change if you move elements after the initial layout).

从本质上讲,它需要完全重写 Canvas。之前提出的覆盖 MeasureOverride 的解决方案失败,因为默认的 Canvas.Left/.Top &c 属性使 Arrangment 无效,但也需要使 measure 无效。(您第一次获得正确的尺寸,但如果您在初始布局后移动元素,尺寸不会改变)。

The Grid solution is more-or-less reasonable but binding to Margins in order to get x-y displacement can wreak havoc on other code (particalary in MVVM). I struggled with the Grid view solution for a while, but complications with View/ViewModel interactions and scrolling behaviour finally drove me to this. Which is simple and to the point, and Just Works.

Grid 解决方案或多或少是合理的,但为了获得 xy 位移而绑定到 Margins 可能会对其他代码造成严重破坏(尤其是在 MVVM 中)。我在 Grid 视图解决方案上挣扎了一段时间,但 View/ViewModel 交互和滚动行为的复杂性最终驱使我这样做。这很简单,也很重要,而且 Just Works。

It's not THAT complicated to re-implement ArrangeOverride and MeasureOverride. And you're bound to write at least as much code elsewhere dealing with Grid/Margin stupidity. So there you are.

重新实现ArrangeOverride 和MeasureOverride 并没有那么复杂。并且您必须至少在其他地方编写尽可能多的代码来处理 Grid/Margin 愚蠢问题。所以你来了。

Here's a more complete solution. non-zero Margin behaviour is untested. If you need anything other than Left and Top, then this provides a starting point, at least.

这是一个更完整的解决方案。非零保证金行为未经测试。如果您需要 Left 和 Top 以外的任何东西,那么这至少提供了一个起点。

WARNING: You must use AutoResizeCanvas.Left and AutoResizeCanvas.Top attached properties instead of Canvas.Left and Canvas.Top. Remaining Canvas properties have not been implemented.

警告:您必须使用 AutoResizeCanvas.Left 和 AutoResizeCanvas.Top 附加属性,而不是 Canvas.Left 和 Canvas.Top。剩余的 Canvas 属性尚未实现。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Mu.Controls
{
    public class AutoResizeCanvas : Panel
    {



        public static double GetLeft(DependencyObject obj)
        {
            return (double)obj.GetValue(LeftProperty);
        }

        public static void SetLeft(DependencyObject obj, double value)
        {
            obj.SetValue(LeftProperty, value);
        }

        public static readonly DependencyProperty LeftProperty =
            DependencyProperty.RegisterAttached("Left", typeof(double),
            typeof(AutoResizeCanvas), 
            new FrameworkPropertyMetadata(0.0, OnLayoutParameterChanged));

        private static void OnLayoutParameterChanged(
                DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // invalidate the measure of the enclosing AutoResizeCanvas.
            while (d != null)
            {
                AutoResizeCanvas canvas = d as AutoResizeCanvas;
                if (canvas != null)
                {
                    canvas.InvalidateMeasure();
                    return;
                }
                d = VisualTreeHelper.GetParent(d);
            }
        }




        public static double GetTop(DependencyObject obj)
        {
            return (double)obj.GetValue(TopProperty);
        }

        public static void SetTop(DependencyObject obj, double value)
        {
            obj.SetValue(TopProperty, value);
        }

        public static readonly DependencyProperty TopProperty =
            DependencyProperty.RegisterAttached("Top", 
                typeof(double), typeof(AutoResizeCanvas),
                new FrameworkPropertyMetadata(0.0, OnLayoutParameterChanged));





        protected override Size MeasureOverride(Size constraint)
        {
            Size availableSize = new Size(double.MaxValue, double.MaxValue);
            double requestedWidth = MinimumWidth;
            double requestedHeight = MinimumHeight;
            foreach (var child in base.InternalChildren)
            {
                FrameworkElement el = child as FrameworkElement;

                if (el != null)
                {
                    el.Measure(availableSize);
                    Rect bounds, margin;
                    GetRequestedBounds(el,out bounds, out margin);

                    requestedWidth = Math.Max(requestedWidth, margin.Right);
                    requestedHeight = Math.Max(requestedHeight, margin.Bottom);
                }
            }
            return new Size(requestedWidth, requestedHeight);
        }
        private void GetRequestedBounds(
                            FrameworkElement el, 
                            out Rect bounds, out Rect marginBounds
                            )
        {
            double left = 0, top = 0;
            Thickness margin = new Thickness();
            DependencyObject content = el;
            if (el is ContentPresenter)
            {
                content = VisualTreeHelper.GetChild(el, 0);
            }
            if (content != null)
            {
                left = AutoResizeCanvas.GetLeft(content);
                top = AutoResizeCanvas.GetTop(content);
                if (content is FrameworkElement)
                {
                    margin = ((FrameworkElement)content).Margin;
                }
            }
            if (double.IsNaN(left)) left = 0;
            if (double.IsNaN(top)) top = 0;
            Size size = el.DesiredSize;
            bounds = new Rect(left + margin.Left, top + margin.Top, size.Width, size.Height);
            marginBounds = new Rect(left, top, size.Width + margin.Left + margin.Right, size.Height + margin.Top + margin.Bottom);
        }


        protected override Size ArrangeOverride(Size arrangeSize)
        {
            Size availableSize = new Size(double.MaxValue, double.MaxValue);
            double requestedWidth = MinimumWidth;
            double requestedHeight = MinimumHeight;
            foreach (var child in base.InternalChildren)
            {
                FrameworkElement el = child as FrameworkElement;

                if (el != null)
                {
                    Rect bounds, marginBounds;
                    GetRequestedBounds(el, out bounds, out marginBounds);

                    requestedWidth = Math.Max(marginBounds.Right, requestedWidth);
                    requestedHeight = Math.Max(marginBounds.Bottom, requestedHeight);
                    el.Arrange(bounds);
                }
            }
            return new Size(requestedWidth, requestedHeight);
        }

        public double MinimumWidth
        {
            get { return (double)GetValue(MinimumWidthProperty); }
            set { SetValue(MinimumWidthProperty, value); }
        }

        public static readonly DependencyProperty MinimumWidthProperty =
            DependencyProperty.Register("MinimumWidth", typeof(double), typeof(AutoResizeCanvas), 
            new FrameworkPropertyMetadata(300.0,FrameworkPropertyMetadataOptions.AffectsMeasure));



        public double MinimumHeight
        {
            get { return (double)GetValue(MinimumHeightProperty); }
            set { SetValue(MinimumHeightProperty, value); }
        }

        public static readonly DependencyProperty MinimumHeightProperty =
            DependencyProperty.Register("MinimumHeight", typeof(double), typeof(AutoResizeCanvas), 
            new FrameworkPropertyMetadata(200.0,FrameworkPropertyMetadataOptions.AffectsMeasure));



    }


}

回答by user500099

Binding the Height/Width to the actual size of the control within the canvas worked for me:

将高度/宽度绑定到画布内控件的实际大小对我有用:

        <ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible">
            <Canvas Height="{Binding ElementName=myListBox, Path=ActualHeight}"
                    Width="{Binding ElementName=myListBox, Path=ActualWidth}">
                <ListBox x:Name="myListBox" />
            </Canvas>
        </ScrollViewer>

回答by Sayka

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    autoSizeCanvas(canvas1);
}

void autoSizeCanvas(Canvas canv)
{
    int height = canv.Height;
    int width = canv.Width;
    foreach (UIElement ctrl in canv.Children)
    {
        bool nullTop = ctrl.GetValue(Canvas.TopProperty) == null || Double.IsNaN(Convert.ToDouble(ctrl.GetValue(Canvas.TopProperty))),
                nullLeft = ctrl.GetValue(Canvas.LeftProperty) == null || Double.IsNaN(Convert.ToDouble(ctrl.GetValue(Canvas.LeftProperty)));
        int curControlMaxY = (nullTop ? 0 : Convert.ToInt32(ctrl.GetValue(Canvas.TopProperty))) +
            Convert.ToInt32(ctrl.GetValue(Canvas.ActualHeightProperty)
            ),
            curControlMaxX = (nullLeft ? 0 : Convert.ToInt32(ctrl.GetValue(Canvas.LeftProperty))) +
            Convert.ToInt32(ctrl.GetValue(Canvas.ActualWidthProperty)
            );
        height = height < curControlMaxY ? curControlMaxY : height;
        width = width < curControlMaxX ? curControlMaxX : width;
    }
    canv.Height = height;
    canv.Width = width;
}

In the function, i'm trying to find the maximum X position and Y position, where controls in the canvas can reside.

在该函数中,我试图找到画布中控件可以驻留的最大 X 位置和 Y 位置。

Use the function only in Loaded event or later and not in constructor. The window has to be measured before loading..

仅在 Loaded 事件或更高版本中使用该函数,而不是在构造函数中使用。加载前必须测量窗口..

回答by Asaf

As an improvement to @MikeKulls's answer, here's a version which does not throw an exception when there are no UI elements in the canvas or when there are UI elements without Canvas.Top or Canvas.Left properties:

作为对@MikeKulls 答案的改进,这里有一个版本,当画布中没有 UI 元素或存在没有 Canvas.Top 或 Canvas.Left 属性的 UI 元素时,它不会引发异常:

public class AutoResizedCanvas : Canvas
{
    protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint)
    {
        base.MeasureOverride(constraint);
        double width = base
            .InternalChildren
            .OfType<UIElement>()
            .Where(i => i.GetValue(Canvas.LeftProperty) != null)
            .Max(i => i.DesiredSize.Width + (double)i.GetValue(Canvas.LeftProperty));

        if (Double.IsNaN(width))
        {
            width = 0;
        }

        double height = base
            .InternalChildren
            .OfType<UIElement>()
            .Where(i => i.GetValue(Canvas.TopProperty) != null)
            .Max(i => i.DesiredSize.Height + (double)i.GetValue(Canvas.TopProperty));

        if (Double.IsNaN(height))
        {
            height = 0;
        }

        return new Size(width, height);
    }
}

回答by Hyperion753

I was able to achieve the result you are looking for by simply adding a new size changed event to the control which contained the data that was causing the canvas to grow. After the canvas reaches the extent of the scroll viewer it will cause the scroll bars to appear. I just assigned the following lambda expression to the size changed event of the control:

我能够通过简单地向包含导致画布增长的数据的控件添加一个新的大小更改事件来实现您正在寻找的结果。在画布到达滚动查看器的范围后,它将导致滚动条出现。我刚刚将以下 lambda 表达式分配给控件的大小更改事件:

text2.SizeChanged += (s, e) => { DrawingCanvas.Height = e.NewSize.Height; 
                                 DrawingCanvas.Width = e.NewSize.Width; };

回答by DSchmidt

What worked for me is the following: Like the original poster's example in their question, I nested a canvas in a grid. The grid is within a scrollviewer. Instead of attempting to change the canvas size, I changed the grid size, both height and width in my case, and the canvas followed the size of the grid minus any margins. I set the grid size programmatically, although I would think binding would work as well. I got the desired size of the grid programmatically as well.

对我有用的是以下内容:就像他们问题中的原始海报示例一样,我在网格中嵌套了一个画布。网格位于滚动查看器中。我没有尝试更改画布大小,而是更改了网格大小,在我的情况下包括高度和宽度,并且画布遵循网格的大小减去任何边距。我以编程方式设置网格大小,尽管我认为绑定也可以。我也以编程方式获得了所需的网格大小。