Search code examples
c#wpfxamlcanvas

Drawing multiple items on wpf canvas results in each subsequent item being drawn below the previous even if using the same position values


I'm trying to draw circles at places a user clicks on an image. I've got a canvas the same size as the image to draw them. I'm getting the position of the clicks correctly, and I'm using the below code to draw the ellipses which almost works, just that each subsequent click the next ellipses is drawn below the previous, even if you move along the x axis, it still draws it below the previous. The amount it's drawn below is the size of the ellipse. Each click I'm adding a position to the observablecollection called ClickedPositions.

<Canvas>
<ItemsControl ItemsSource="{Binding ClickedPositions}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Ellipse Width="5" Height="5" Fill="Red">
                <Ellipse.RenderTransform>
                    <TranslateTransform X="{Binding X}" Y="{Binding Y}" />
                </Ellipse.RenderTransform>
            </Ellipse>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>

enter image description here

Here you can see I clicked in the same place 6 times, the first circle is correct, then each subsequent circle is drawn 'below' the previous. Then I moved the mouse right and clicked twice, and you can see it's still being drawn 'below' the previous.

What am I missing?

Thanks


Solution

  • The Canvas needs to be declared as the ItemsPanel of the ItemsControl. The default ItemsPanel is a vertical StackPanel.

    <ItemsControl ItemsSource="{Binding ClickedPositions}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Ellipse Width="5" Height="5" Fill="Red">
                    <Ellipse.RenderTransform>
                        <TranslateTransform X="{Binding X}" Y="{Binding Y}" />
                    </Ellipse.RenderTransform>
                </Ellipse>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    Instead of setting the Ellipse's RenderTransform, you may also set the Canvas.Left and Canvas.Top properties of the item container:

    <ItemsControl ItemsSource="{Binding ClickedPositions}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding X}"/>
                <Setter Property="Canvas.Top" Value="{Binding Y}"/>
            </Style>
        </ItemsControl.ItemContainerStyle>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Ellipse Width="5" Height="5" Fill="Red"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    Finally be aware that X and Y are the coordinates of the upper left corner of the Ellipse's bounding box. In order to draw a centered shape, use a Path with an EllipseGeometry:

    <ItemsControl ItemsSource="{Binding ClickedPositions}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding X}"/>
                <Setter Property="Canvas.Top" Value="{Binding Y}"/>
            </Style>
        </ItemsControl.ItemContainerStyle>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Path Fill="Red">
                    <Path.Data>
                        <EllipseGeometry RadiusX="2.5", RadiusY="2.5"/>
                    </Path.Data>  
                </Path>  
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>