WPF: How to make canvas auto-resize?

I would like my Canvas to automatically resize to the size of its items, so that the ScrollViewer scroll bars have the correct range. Can this be done in 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.


Solution 1:

No this is not possible (see snippet from MSDN below). However, if you want to have scrollbars and auto-resizing, consider using a Grid instead, 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.. :)

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.

From 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.

Hope this helps

Solution 2:

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

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.

            <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.

Solution 3:

I think you can resize Canvas by overriding MeasureOverride or ArrangeOverride methods.

This job is not difficult.

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

I hope this helps you.

Thank you.