54

我希望我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>

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

4

14 回答 14

53

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

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

来自MSDN

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

希望这可以帮助

于 2009-05-14T12:43:01.797 回答
44

我只是在这里复制 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);
    }
}

然后在您的 XAML 中使用 CanvasAutoSize。

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

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

于 2011-08-19T00:38:43.413 回答
9

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

这份工作并不难。

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

我希望这可以帮助你。

谢谢你。

于 2010-07-15T08:50:44.617 回答
7

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

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

重新实现 ArrangeOverride 和 MeasureOverride 并不复杂。而且你必须在其他地方编写至少一样多的代码来处理 Grid/Margin 的愚蠢问题。所以你来了。

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

警告:您必须使用 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));



    }


}
于 2014-02-03T16:39:38.173 回答
6

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

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

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

于 2009-10-30T01:00:23.807 回答
4
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;
}

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

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

于 2015-03-18T01:32:06.907 回答
3

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

        <ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible">
            <Canvas Height="{Binding ElementName=myListBox, Path=ActualHeight}"
                    Width="{Binding ElementName=myListBox, Path=ActualWidth}">
                <ListBox x:Name="myListBox" />
            </Canvas>
        </ScrollViewer>
于 2012-06-20T12:17:14.900 回答
3

作为对@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);
    }
}
于 2015-07-15T18:13:41.637 回答
0

我也遇到过这个问题,我的问题是由于覆盖的 MeasureOverride 函数,当 Canvas 确实调整大小时,网格没有自动调整大小。

我的问题: WPF MeasureOverride 循环

于 2015-06-09T08:29:31.457 回答
0

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

text2.SizeChanged += (s, e) => { DrawingCanvas.Height = e.NewSize.Height; 
                                 DrawingCanvas.Width = e.NewSize.Width; };
于 2018-05-02T22:43:20.907 回答
0

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

于 2020-05-24T12:48:05.787 回答
0

使用Grid将自动调整内容大小而不设置额外参数,而只是VerticalAlignmentand HorizontalAlignment。例如:

<Grid VerticalAlignment="Top" HorizontalAlignment="Center">
    <Polygon Points="0, 0, 0, 100, 80, 100" Panel.ZIndex="6" Fill="Black" Stroke="Black" StrokeStartLineCap="Round" StrokeDashCap="Round" StrokeLineJoin="Round" StrokeThickness="10"/>
</Grid>
于 2021-11-11T13:48:51.670 回答
0

据我了解,当将控件添加到画布以增加画布的大小时,画布不会通知滚动查看器。滚动查看器不知道画布大小增加了。对我有用的只是在添加控件后以编程方式更改画布的宽度。

mycanvas.Children.Add(myTextBlock);
mycanvas.Width += 10.0

;

于 2021-11-11T19:08:24.893 回答
-2
<viewbox>
    <canvas>
        <uielements /> 
    </canvas>
</viewbox>
于 2010-02-15T05:44:00.830 回答