C# 在 WPF 中更改 Canvas 的坐标系

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/254125/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 20:05:39  来源:igfitidea点击:

Change the coordinate system of a Canvas in WPF

c#wpfxamlwpf-controls

提问by Dylan

I'm writing a mapping app that uses a Canvas for positioning elements. For each element I have to programatically convert element's Lat/Long to the canvas' coordinate, then set the Canvas.Top and Canvas.Left properties.

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

If I had a 360x180 Canvas, can I convert the coordinates on the canvas to go from -180 to 180 rather than 0 to 360 on the X axis and 90 to -90 rather than 0 to 180 on the Y axis?

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

Scaling requirements:

缩放要求:

  • The canvas can be any size, so should still work if it's 360x180 or 5000x100.
  • The Lat/Long area may not always be (-90,-180)x(90,180), it could be anything (ie (5,-175)x(89,-174)).
  • Elements such as PathGeometry which are point base, rather than Canvas.Top/Left based need to work.
  • 画布可以是任何尺寸,因此如果它是 360x180 或 5000x100,它仍然可以工作。
  • Lat/Long 区域可能并不总是 (-90,-180)x(90,180),它可以是任何东西(即 (5,-175)x(89,-174))。
  • 像 PathGeometry 这样的元素是基于点的,而不是基于 Canvas.Top/Left 的元素需要工作。

回答by MojoFilter

I'm pretty sure you can't do that exactly, but it would be pretty trivial to have a method which translated from lat/long to Canvas coordinates.

我很确定你不能完全做到这一点,但是拥有一种从纬度/经度转换为画布坐标的方法将是非常微不足道的。

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

(Or something along those lines)

(或类似的规定)

回答by MojoFilter

I guess another option would be to extend canvas and override the measure / arrange to make it behave the way you want.

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

回答by Dylan

I was able to get it to by creating my own custom canvas and overriding the ArrangeOverride function like so:

我能够通过创建我自己的自定义画布并像这样覆盖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);
        }
    }

Which works for my described problem, but it probably would not work for another need I have, which is a PathGeometry. It wouldn't work because the points are not defined as Top and Left, but as actual points.

这适用于我描述的问题,但它可能不适用于我的另一个需求,即 PathGeometry。这是行不通的,因为这些点没有定义为 Top 和 Left,而是定义为实际点。

回答by Ryan Lundy

Here's an all-XAML solution. Well, mostly XAML, because you have to have the IValueConverter in code. So: Create a new WPF project and add a class to it. The class is MultiplyConverter:

这是一个全 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();
        }
    }
}

Then use this XAML for your Window. Now you should see the results right in your XAML preview window.

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

EDIT: You can fix the Background problem by putting your Canvas inside another Canvas. Kind of weird, but it works. In addition, I've added a ScaleTransform which flips the Y-axis so that positive Y is up and negative is down. Note carefully which Names go where:

编辑:您可以通过将您的画布放在另一个画布中来解决背景问题。有点奇怪,但它有效。此外,我添加了一个 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>

As for your new requirements that you need varying ranges, a more complex ValueConverter would probably do the trick.

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

回答by Nir

You can use transform to translate between the coordinate systems, maybe a TransformGroup with a TranslateTranform to move (0,0) to the center of the canvas and a ScaleTransform to get the coordinates to the right range.

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

With data binding and maybe a value converter or two you can get the transforms to update automatically based on the canvas size.

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

The advantage of this is that it will work for any element (including a PathGeometry), a possible disadvantage is that it will scale everything and not just points - so it will change the size of icons and text on the map.

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

回答by FTLPhysicsGuy

Another possible solution:

另一种可能的解决方案:

Embed a custom canvas (the draw-to canvas) in another canvas (the background canvas) and set the draw-to canvas so that it is transparent and does not clip to bounds. Transform the draw-to canvas with a matrix that makes y flip (M22 = -1) and translates/scales the canvas inside the parent canvas to view the extend of the world you're looking at.

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

In effect, if you draw at -115, 42 in the draw-to canvas, the item you are drawing is "off" the canvas, but shows up anyway because the canvas is not clipping to bounds. You then transform the draw-to canvas so that the point shows up in the right spot on the background canvas.

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

This is something I'll be trying myself soon. Hope it helps.

这是我很快就会尝试的东西。希望能帮助到你。

回答by dharmatech

Here's an answerwhich describes a Canvasextension method that allows you to apply a Cartesian coordinate system. I.e.:

这是一个答案,它描述了Canvas允许您应用笛卡尔坐标系的扩展方法。IE:

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

will set the coordinate system of canvasso that xgoes from -10 to 10 and ygoes from -10 to 10.

将设置坐标系,canvas以便x从 -10 到 10 和y从 -10 到 10。

回答by Morten Brask Jensen

i have nearly the same problem. so i went online. and this guy uses matrix to transform from 'device pixel' to what he calls 'world coordinates' and by that he means real world numbers instead of 'device pixels' see the link

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

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

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