开发者

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

开发者 https://www.devze.com 2023-02-04 08:02 出处:网络
I am trying to use a Canvas to display objects that have \"world\" location (rather than \"screen\" location). The canvas is defined like this:

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


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>


You can apply this conversion within a value converter in your binding. Value converters implement the IValueConverter interface (http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx). The problem is that your conversion requires both the X and Y component of your item. A simple solution to this would be to bind to MyItem, rather than MyItem.WorldX. You can achieve this by using "Path=.", if you then create the following value converter ...

public class CoordinateLeftConverter: IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        MyItem item = value as MyItem;
        return worldToScreen(item.WorldX, item.WorldY).X;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
    }
}

You can use it in your binding as follows:

<TextBlock Canvas.Left="{Binding Path=.,Converter={StaticResource CoordinateLeftConverter}" ... />

Where you create an instance of CoordinateLeftConverter in your page Resources:

<UserControl.Resources>
  <CoordinateLeftConverter x:Key="CoordinateLeftConverter"/> 
</UserControl.Resources>

You would then of course need to add another converter for the Canvas.Top property, or supply a ConverterParameter to switch between the X / Y property of the transformed Point.

However, a simpler solution might be to perform the conversion within your MyItem class, removing the need for a converter!


I had a similar problem when I was trying to bind the Canvas.Top property to a ViewModel's object that has a CanvasTop property, the Canvas.Top property would first get the value, but then it gets reset somehow and loses the binding expression. But I did a little work around from the code here. And since I'm using Silverlight, there's no ItemsContainerStyle property, so I used ItemsControl.Resources instead, so given the above example, my code looks something like this:

<Grid>
<Image x:Name="image" Height="100" Width="Auto" Source="http://thecybershadow.net/misc/stackoverflow.png"/>
<ItemsControl Name="myItemsControl">
    <ItemsControl.Resources>
        <Style TargetType="ContentPresenter">
            <Setter Property="Canvas.Left" Value="{Binding X}"/>
            <Setter Property="Canvas.Top" Value="{Binding Y}"/>
        </Style>
    </ItemsControl.Resources>
    <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>

0

精彩评论

暂无评论...
验证码 换一张
取 消