使用 ItemsControl 时如何将 X/Y 位置转换为 Canvas Left/Top 属性

发布于 2024-10-12 04:33:45 字数 1859 浏览 8 评论 0原文

我正在尝试使用画布来显示具有“世界”位置(而不是“屏幕”位置)的对象。画布是这样定义的:

<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 是这样定义的:

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; }
}

另外,我有一个在世界坐标和屏幕坐标之间进行转换的方法:

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);
}

在当前的实现中,项目被定位在错误的位置,因为它们的位置没有转换为屏幕坐标。

在将 MyItem 对象添加到画布之前,如何在它们上应用 worldToScreen 方法?


编辑:

我有点困惑我是否走正确的路,所以我发布了另一个问题: 如何使用 WPF 可视化一个简单的 2D 世界(地图和元素)

对于这个问题,这里也有一个有用且完整的答案

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

云巢 2024-10-19 04:33:45

您提供的代码的主要问题是 Canvas.LeftCanvas.Top 属性与 Canvas 中的 Canvas 相关。 ItemsControl 的 code>DataTemplate。这会不断“重置”原点。相反,您可以:

  • DataTemplate 中删除 Canvas
  • 使 ListBoxItemsPanel 成为 Canvas< /code>
  • 使用 Canvas.TopCanvas.Left 定位包裹 ItemsControl 项的 ItemsPresenter
  • ,确保ImageCanvas 具有相同的坐标,或者切换到使用 `Canvas

这里是一个完整的仅 XAML 示例,用于将 ItemsControl 项定位在一个 CanvasCanvas 后面有一个 Image

<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>

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>
窗影残 2024-10-19 04:33:45

您可以在绑定中的值转换器中应用此转换。值转换器实现 IValueConverter 接口 (http://msdn. microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx)。问题是您的转换需要项目的 X 和 Y 分量。对此的一个简单解决方案是绑定到 MyItem,而不是 MyItem.WorldX。您可以通过使用“Path=.”来实现此目的,如果您创建以下值转换器...

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)
    {
    }
}

您可以在绑定中使用它,如下所示:

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

在页面资源中创建 CooperativeLeftConverter 实例的位置:

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

您当然可以需要为 Canvas.Top 属性添加另一个转换器,或者提供 ConverterParameter 以在转换后的 Point 的 X / Y 属性之间切换。

然而,更简单的解决方案可能是在 MyItem 类中执行转换,从而无需转换器!

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!

骄傲 2024-10-19 04:33:45

当我尝试将 Canvas.Top 属性绑定到具有 CanvasTop 属性的 ViewModel 对象时,我遇到了类似的问题,Canvas.Top 属性将首先获取该值,但随后它会以某种方式重置并丢失绑定表达式。但我从这里的代码中做了一些工作。由于我使用的是 Silverlight,因此没有 ItemsContainerStyle 属性,因此我使用了 ItemsControl.Resources,因此鉴于上面的示例,我的代码如下所示:

<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>

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>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文