在 WPF 中更改画布的坐标系

Posted

技术标签:

【中文标题】在 WPF 中更改画布的坐标系【英文标题】:Change the coordinate system of a Canvas in WPF 【发布时间】:2010-09-20 05:27:25 【问题描述】:

我正在编写一个使用 Canvas 定位元素的地图应用程序。对于每个元素,我必须以编程方式将元素的 Lat/Long 转换为画布的坐标,然后设置 Canvas.Top 和 Canvas.Left 属性。

如果我有一个 360x180 的画布,我可以将画布上的坐标转换为 X 轴上从 -180 到 180 而不是 0 到 360 以及 Y 轴上从 90 到 -90 而不是 0 到 180 的坐标吗?

扩展要求:

画布可以是任意大小,因此在 360x180 或 5000x100 的情况下仍然可以使用。 纬度/经度区域可能并不总是 (-90,-180)x(90,180),它可以是任何值(即 (5,-175)x(89,-174))。 PathGeometry 等基于点而不是 Canvas.Top/Left 的元素需要工作。

【问题讨论】:

【参考方案1】:

我很确定你不能完全做到这一点,但是有一个从 lat/long 转换为 Canvas 坐标的方法将非常简单。

Point ToCanvas(double lat, double lon) 
  double x = ((lon * myCanvas.ActualWidth) / 360.0) - 180.0;
  double y = ((lat * myCanvas.ActualHeight) / 180.0) - 90.0;
  return new Point(x,y);

(或类似的东西)

【讨论】:

我有那个功能,我只是希望不需要一直在画布上转换点。 这样做确实具有使您的画布美观且可扩展的额外好处。【参考方案2】:

我想另一种选择是扩展画布并覆盖度量/安排以使其按照您想要的方式运行。

【讨论】:

这更符合我的想法,但我不确定如何实现它。 这真的不是太简单。这个想法是你覆盖 ArrangeOverride() 和 MeasureOverride()。【参考方案3】:

我可以通过创建自己的自定义画布并像这样覆盖 ArrangeOverride 函数来实现它:

    public class CustomCanvas : Canvas
    
        protected override Size ArrangeOverride(Size arrangeSize)
        
            foreach (UIElement child in InternalChildren)
            
                double left = Canvas.GetLeft(child);
                double top = Canvas.GetTop(child);
                Point canvasPoint = ToCanvas(top, left);
                child.Arrange(new Rect(canvasPoint, child.DesiredSize));
            
            return arrangeSize;
        
        Point ToCanvas(double lat, double lon)
        
            double x = this.Width / 360;
            x *= (lon - -180);
            double y = this.Height / 180;
            y *= -(lat + -90);
            return new Point(x, y);
        
    

这适用于我描述的问题,但它可能不适用于我的另一个需求,即 PathGeometry。它不起作用,因为这些点不是定义为顶部和左侧,而是定义为实际点。

【讨论】:

【参考方案4】:

这是一个全 XAML 解决方案。好吧,主要是 XAML,因为您必须在代码中包含 IValueConverter。所以:创建一个新的 WPF 项目并向其中添加一个类。该类是MultiplyConverter:

namespace YourProject

    public class MultiplyConverter : System.Windows.Data.IValueConverter
    
        public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        
            return AsDouble(value)* AsDouble(parameter);
        
        double AsDouble(object value)
        
            var valueText = value as string;
            if (valueText != null)
                return double.Parse(valueText);
            else
                return (double)value;
        

        public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        
            throw new System.NotSupportedException();
        
    

然后将此 XAML 用于您的窗口。现在您应该可以在 XAML 预览窗口中看到结果。

编辑:您可以通过将 Canvas 放入另一个 Canvas 来解决背景问题。有点奇怪,但它有效。此外,我添加了一个 ScaleTransform 翻转 Y 轴,使正 Y 向上,负 Y 向下。仔细注意哪些名称去哪里:

<Canvas Name="canvas" Background="Moccasin">
    <Canvas Name="innerCanvas">
        <Canvas.RenderTransform>
            <TransformGroup>
                <TranslateTransform x:Name="translate">
                    <TranslateTransform.X>
                        <Binding ElementName="canvas" Path="ActualWidth"
                                Converter="StaticResource multiplyConverter" ConverterParameter="0.5" />
                    </TranslateTransform.X>
                    <TranslateTransform.Y>
                        <Binding ElementName="canvas" Path="ActualHeight"
                                Converter="StaticResource multiplyConverter" ConverterParameter="0.5" />
                    </TranslateTransform.Y>
                </TranslateTransform>
                <ScaleTransform ScaleX="1" ScaleY="-1" CenterX="Binding ElementName=translate,Path=X"
                        CenterY="Binding ElementName=translate,Path=Y" />
            </TransformGroup>
        </Canvas.RenderTransform>
        <Rectangle Canvas.Top="-50" Canvas.Left="-50" Height="100" Width="200" Fill="Blue" />
        <Rectangle Canvas.Top="0" Canvas.Left="0" Height="200" Width="100" Fill="Green" />
        <Rectangle Canvas.Top="-25" Canvas.Left="-25" Height="50" Width="50" Fill="HotPink" />
    </Canvas>
</Canvas>

至于您需要不同范围的新要求,更复杂的 ValueConverter 可能会解决问题。

【讨论】:

这非常接近我的需要,但我认为这还不够好。如果坐标系是 Lat=(2, -74) Lon=(-180, -175) 或者 Canvas 的大小发生变化 (h=50,w=100),它将不起作用。此外,Background 属性不可用。 各种变换是你想要的,但是找到它们的正确组合和顺序有点痛苦。 ...XAML 预览窗口通常不会按比例显示内容,这并没有使事情变得更容易。如果在预览窗口中应该是正确的东西消失了,您可能需要运行应用程序才能看到它并没有真正关闭。【参考方案5】:

您可以使用 transform 在坐标系之间进行转换,可以使用带有 TranslateTranform 的 TransformGroup 将 (0,0) 移动到画布的中心,并使用 ScaleTransform 将坐标移到正确的范围内。

通过数据绑定和一两个值转换器,您可以根据画布大小自动更新转换。

这样做的好处是它适用于任何元素(包括 PathGeometry),一个可能的缺点是它会缩放所有内容而不仅仅是点 - 所以它会改变地图上图标和文本的大小。

【讨论】:

【参考方案6】:

另一种可能的解决方案:

在另一个画布(背景画布)中嵌入自定义画布(绘图画布),并将绘图画布设置为透明且不剪裁到边界。使用使 y 翻转 (M22 = -1) 并在父画布内平移/缩放画布以查看您正在查看的世界的范围的矩阵转换绘图画布。

实际上,如果您在 draw-to 画布中的 -115、42 处绘制,则您正在绘制的项目“不在”画布上,但无论如何都会显示出来,因为画布没有剪裁到边界。然后,您转换绘制到的画布,使该点显示在背景画布上的正确位置。

这是我很快就会尝试的事情。希望对您有所帮助。

【讨论】:

注意:我试过这个,但发现了一个奇怪的地方。如果您按比例放大直到一米大约为几十个像素,并且您不在绘制画布的实际 0,0 点附近,则移动绘制画布(通过变换或通过 Canvas.SetTop/SetLeft)似乎被量化。即使您将顶部/左侧或 M11/M22 更改为小的值,视觉绘图也不会移动,除非是“量化”值(不确定这些值是什么)。我想知道它是否以双精度进行计算,但将它们传递给一些转换为浮点数的较低级别的例程,从而“量化”大数字。【参考方案7】:

这里是an answer,它描述了一个Canvas 扩展方法,允许您应用笛卡尔坐标系。即:

canvas.SetCoordinateSystem(-10, 10, -10, 10)

将设置canvas 的坐标系,使x 从-10 变为10,y 从-10 变为10。

【讨论】:

【参考方案8】:

我几乎有同样的问题。所以我上网了。这家伙使用矩阵将“设备像素”转换为他所谓的“世界坐标”,他指的是真实世界的数字而不是“设备像素”,请参阅链接

http://csharphelper.com/blog/2014/09/use-transformations-draw-graph-wpf-c/

【讨论】:

以上是关于在 WPF 中更改画布的坐标系的主要内容,如果未能解决你的问题,请参考以下文章

如何在 UWP 中将画布的 x、y 坐标更改为右下角?

几何画板怎么设置坐标的刻度/大小/文本等属性?

使用 EaselJS 围绕画布构建坐标系

Android UICanvas 画布 ⑨ ( Canvas 绘图坐标系平移实例 )

悟透Qt—求解画布经任意中心点旋转后相对旋转前坐标系的坐标

悟透Qt—求解画布经任意中心点旋转后相对旋转前坐标系的坐标