Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert X/Y position to Canvas Left/Top properties when using ItemsControl

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

like image 826
kshahar Avatar asked Feb 26 '23 09:02

kshahar


1 Answers

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>
like image 124
Rick Sladkey Avatar answered Apr 06 '23 17:04

Rick Sladkey