WPF Standard Control Demo App › Canvas

Canvas Graphics

Canvas places each child at the coordinates given by the attached properties Canvas.Left, Top, Right, and Bottom, at the child's own size.

Children keep their own size

A Canvas measures its children, but with unlimited space: a child was measured with Infinity × Infinity. Each child therefore keeps its own size. In a Canvas 300 wide, a TextBlock was 60.23 wide, and a Border with HorizontalAlignment="Stretch" was only as wide as its text, 37.29. Alignment does not stretch a child in a Canvas, so give a child an explicit size if it should fill something.

Which of Left and Right wins

A child with no position was placed at (0, 0). With the demo app’s initial Canvas.Top 20 and Canvas.Left 20, rectangle A was at (20, 20), 100 × 100, and a negative Canvas.Left such as -30 put a child partly left of the Canvas.

Canvas.Right and Canvas.Bottom lose to Left and Top: with all four set to 20, the rectangle stayed at (20, 20) and 100 × 100, neither moved nor stretched. Set either Left or Right, not both. The demo app binds them with FallbackValue NaN, so an empty text box leaves them unset (NaN), and “30” sets 30.

Where Right and Bottom measure from

Right and Bottom measure from the Canvas’s actual size, so the Canvas needs no Width for them. With only Right 20 and Bottom 20, in a Canvas stretched to 300 × 200, the rectangle was at (180, 80), which keeps a badge at a fixed distance from a corner. In a Canvas of size 0, the same rectangle was at (-120, -120).

Why a Canvas of size 0 still shows its children

The Canvas itself asks for no space. With a rectangle at (20, 20) inside it, a Canvas in a horizontal StackPanel had a DesiredSize of 0 × 0, so an auto-sized parent gives it no space; size a Canvas from its parent or with its own Width and Height. Its children are still shown, because ClipToBounds is False by default. In a Canvas 200 wide, a rectangle from 150 to 250 was hit at x=230, outside the Canvas. With ClipToBounds="True", which the demo app sets, nothing was hit there. Set it to keep children inside.

ZIndex

The demo app starts with rectangle A at ZIndex 0 and rectangle B at 1; at (85, 85), where they overlap, B was on top. With A at 2, A was on top. With both at 0, B, the later child, was on top.

case measured
size given to a child / width of a TextBlock / width of a Stretch Border (Canvas 300 wide) Infinity x Infinity / 60.23 / 37.29
Canvas with a child at (20, 20), in a horizontal StackPanel: DesiredSize / ClipToBounds default 0 x 0 / False
demo's initial values Top 20, Left 20 x=20 y=20 w=100 h=100
Left 20 and Right 20, Top 20 and Bottom 20 (Canvas 300 x 200) x=20 y=20 w=100 h=100
only Right 20, Bottom 20, Canvas stretched to 300 x 200 (no Width set) x=180 y=80 w=100 h=100
same in a Canvas of size 0 (inside StackPanels) x=-120 y=-120 w=100 h=100
no position set / Left -30 x=0 y=0 w=100 h=100 / x=-30 y=150 w=100 h=100
Canvas 200 wide, child at Left 150 (to 250), ClipToBounds=False: hit test at x=230 A
Canvas 200 wide, child at Left 150 (to 250), ClipToBounds=True: hit test at x=230 (nothing)
demo's rectangles A (20, 20) and B (50, 50), ZIndex A=0, B=1: on top at (85, 85) B
demo's rectangles A (20, 20) and B (50, 50), ZIndex A=2, B=1: on top at (85, 85) A
demo's rectangles A (20, 20) and B (50, 50), ZIndex A=0, B=0: on top at (85, 85) B
demo binding with FallbackValue=NaN: Canvas.Right for text "" / "30" NaN / 30

Sizes, positions, clipping, and ZIndex. Measured on .NET 10 / Windows 11.

Trying it in the demo app

The Canvas page of the demo app, with the control list on the left and the first section, Top / Left / Right / Bottom / ZIndex(Panel)

The Canvas page of the demo app has one section with two rectangles 100 × 100: rectangle A, whose Top, Left, Right, Bottom, and ZIndex are bound to text boxes, and rectangle B at (50, 50) with its own ZIndex. The “Show Code” link under the section displays its XAML. The following XAML is the result area (CanvasUsageControl.xaml), with the text boxes the rectangles are bound to, and the namespace declarations added. The TargetNullValue and FallbackValue of the bindings, the styles, the input behavior of the text boxes, and the GroupBoxes are left out, and the surrounding layout is replaced by a DockPanel 300 high:

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           Height="300">
  <UniformGrid DockPanel.Dock="Top" Columns="6">
    <TextBox x:Name="TopTextBox" Text="20" />
    <TextBox x:Name="LeftTextBox" Text="20" />
    <TextBox x:Name="RightTextBox" Text="" />
    <TextBox x:Name="BottomTextBox" Text="" />
    <TextBox x:Name="ZIndexTextBox" Text="0" />
    <TextBox x:Name="ZIndexTextBoxRed" Text="1" />
  </UniformGrid>

  <Canvas Background="#F0F0F0" ClipToBounds="True">
    <Rectangle x:Name="TargetRect"
               Canvas.Left="{Binding Text, ElementName=LeftTextBox}"
               Canvas.Top="{Binding Text, ElementName=TopTextBox}"
               Canvas.Right="{Binding Text, ElementName=RightTextBox}"
               Canvas.Bottom="{Binding Text, ElementName=BottomTextBox}"
               Width="100" Height="100"
               Panel.ZIndex="{Binding Text, ElementName=ZIndexTextBox}"
               Fill="SkyBlue" Stroke="DodgerBlue" StrokeThickness="2" />
    <Rectangle Canvas.Left="50" Canvas.Top="50"
               Width="100" Height="100"
               Panel.ZIndex="{Binding Text, ElementName=ZIndexTextBoxRed}"
               Fill="LightCoral" Stroke="IndianRed" StrokeThickness="2" />
  </Canvas>
</DockPanel>

Source code and how it was measured

Every behavior on this page was measured by running it on .NET 10 / Windows 11, using CanvasDemoScene in this site’s screenshot tool, with rectangles 100 × 100 like the demo app’s. The Canvas was laid out with Measure and Arrange, and the element on top was found by a hit test at the given point. Sizes are the values on the measuring machine.

View Canvas source code on GitHub →