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
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.
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.
I guess another option would be to extend canvas and override the measure / arrange to make it behave the way you want.
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/
Here's an answer which describes a Canvas
extension method that allows you to apply a Cartesian coordinate system. I.e.:
canvas.SetCoordinateSystem(-10, 10, -10, 10)
will set the coordinate system of canvas
so that x
goes from -10 to 10 and y
goes from -10 to 10.
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:
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.
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:
<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.
I was able to get it to by creating my own custom canvas and overriding the ArrangeOverride function like so:
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.