How to convert X/Y position to Canvas Left/Top properties when using ItemsControl
coordinates, data-binding, itemscontrol, wpf
Solution
The main problem with the code you presented is that the `Canvas.Left` and `Canvas.Top` properties are relative to a `Canvas` that is in the `DataTemplate` for the `ItemsControl`. This keeps "resetting" the origin. Instead you can:
- remove the `Canvas` from the `DataTemplate`
- make the `ItemsPanel` for the `ListBox` a `Canvas`
- position the `ItemsPresenter` that wraps the `ItemsControl` items with `Canvas.Top` and `Canvas.Left`
- ensure that the `Image` and the `Canvas` have the same coordinates, or switch to using the `Canvas
Here is a complete XAML-only example of positioning `ItemsControl` items on a `Canvas` with an `Image` behind the `Canvas`:
<Grid>
<Image x:Name="image" Height="100" Width="Auto" Source="http://thecybershadow.net/misc/stackoverflow.png"/>
<ItemsControl Name="myItemsControl">
<ItemsControl.ItemsSource>
<PointCollection>
<Point X="10" Y="10"/>
<Point X="30" Y="30"/>
</PointCollection>
</ItemsControl.ItemsSource>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="Text" Foreground="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
Problem
I am trying to use a Canvas to display objects that have "world" location (rather than "screen" location). The canvas is defined like this: ``` <Canvas Background="AliceBlue"> <ItemsControl Name="myItemsControl" ItemsSource="{Binding MyItems}"> <Image x:Name="myMapImage" Panel.ZIndex="-1" /> <ItemsControl.ItemTemplate> <DataTemplate> <Canvas> <TextBlock Canvas.Left="{Binding WorldX}" Canvas.Top="{Binding WorldY}" Text="{Binding Text}" Width="Auto" Height="Auto" Foreground="Red" /> </Canvas> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Canvas> ``` MyItem is defined like this: ``` public class MyItem { public MyItem(double worldX, double worldY, string text) { WorldX = worldX; WorldY = worldY; Text = text; } public double WorldX { get; set; } public double WorldY { get; set; } public string Text { get; set; } } ``` In addition, I have a method to convert between world and screen coordinates: ``` Point worldToScreen(double worldX, double worldY) { // Note that the conversion uses an internal m_mapData object var size = m_mapData.WorldMax - m_mapData.WorldMin; var left = ((worldX - m_currentMap.WorldMin.X) / size.X) * myMapImage.ActualWidth; var top = ((worldY - m_currentMap.WorldMin.Y) / size.Y) * myMapImage.ActualHeight; return new Point(left, top); } ``` With the current implementation, the items are positioned in the wrong location, because their location is not converted to screen coordinates. How can I apply the worldToScreen method on the MyItem objects before they are added to the canvas? Edit: I got a little confused whether I'm going in the right way, so I posted another question: How to use WPF to visualize a simple 2D world (map and elements) There is a helpful and complete answer there also for this question