首先来一发图
绘制XY的坐标主要是利用Canvas setLeft和setBottom功能(Canvas内置坐标的功能)
1.首先WPF中的坐标系都是从左到右,从上到下的 即左上角位置(0,0)点,所以XY的Canvas要以(RenderTransformOrigin="0,0",为中心点)进行270°旋转,然后平移<TranslateTransform Y="{Binding ActualHeight,ElementName=canvasInPath}"/>
就是如上所图的XY坐标(绿色的)Line
不旋转的图如下:
<Canvas x:Name="canvasInPath" RenderTransformOrigin="0,0"> <Canvas.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform Angle="270"/> <TranslateTransform Y="{Binding ActualHeight,ElementName=canvasInPath}"/> </TransformGroup> </Canvas.RenderTransform> <!--线和点--> <Canvas x:Name="canvasLinePoint"></Canvas> <!--Y--> <Line X1="0" X2="0" Y1="0" Y2="{Binding ActualWidth,ElementName=canvasInPath}" Stroke="Green" StrokeThickness="1" Width="1" ></Line> <!--X--> <Line X1="0" X2="{Binding ActualHeight,ElementName=canvasInPath}" Y1="0" Y2="0" Stroke="Green" StrokeThickness="1" Height="1" Canvas.Top="0" ></Line> </Canvas>
2.如果以XY的Canvas要以(RenderTransformOrigin="0.5,0.5",为中心点)旋转,如果Canvas是正方形,那么只需要旋转270可以了,如果是长方形那么就会出现如下图情况:
3.因为Canvas是旋转的,X和Y的网格线就是蓝色的线,就不在旋转的Canvas中进行画线了(注:在旋转后的Canvas再放置控件都要旋转才能正常)
跟Canvas同一个级别放置两个X和Y网格线的Canvas
Line和TextBlock如何画,看上面的测试代码,然后转换成Code,动态绘制出来。
4.如果按照Canvas 100X100的坐标系绘制出来的图像特别密集下图:
所以我对此做了一个原始坐标和实际绘制坐标进行相应的扩大倍数计算,
/// <summary> /// 宽度 /// </summary> public double XWidth { get { return _xWidth; } set { _xWidth = value; this.Width = value; //预留100的line长度 scaleNumX = (value - xyShorten) / scaleStandard / (xTotal/ scaleStandard); }
当前宽度-预留线的长度/基础倍数/(标尺总值/基础倍数),假如当前宽度是x=700,预留100宽度,基础倍数100,x标尺总刻度是200
那么计算出的scaleNumX=(700-100)/100/(200/100)=3
同理计算出 scaleNumY=(500-100)/100/(100/100)=4 (y=500 预留100 基础倍数100 y标尺总刻度是100)
原始坐标 (20,90)=>真实绘制坐标(60,360) x*scaleNumX,y*scaleNumY 下图:
完整的xaml代码如下:
<UserControl x:Class="CoordinateXY.UserControlXY" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:CoordinateXY" mc:Ignorable="d" x:Name="ucontrol" d:DesignHeight="600" d:DesignWidth="600"> <Grid Background="Wheat"> <Canvas x:Name="canvasInPath" RenderTransformOrigin="0,0"> <Canvas.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform Angle="270"/> <TranslateTransform Y="{Binding ActualHeight,ElementName=canvasInPath}"/> </TransformGroup> </Canvas.RenderTransform> <!--线和点--> <Canvas x:Name="canvasLinePoint"></Canvas> <!--Y--> <Line X1="0" X2="0" Y1="0" Y2="{Binding ActualWidth,ElementName=canvasInPath}" Stroke="Green" StrokeThickness="1" Width="1" ></Line> <!--X--> <Line X1="0" X2="{Binding ActualHeight,ElementName=canvasInPath}" Y1="0" Y2="0" Stroke="Green" StrokeThickness="1" Height="1" Canvas.Top="0" ></Line> </Canvas> <!--X坐标尺度--> <Canvas x:Name="canvasXRuler" Visibility="Visible" Panel.ZIndex="-1"> <!--以下为测试代码--> <Line X1="0" X2="0" Y1="0" Y2="260" Stroke="Blue" StrokeThickness="1" Canvas.Bottom="-8" Canvas.Left="40"></Line> <TextBlock Text="12" RenderTransformOrigin="0,0" Canvas.Bottom="-25" Canvas.Left="32"> </TextBlock> <Line X1="0" X2="0" Y1="0" Y2="260" Stroke="Blue" StrokeThickness="1" Canvas.Bottom="-8" Canvas.Left="50"></Line> <TextBlock Text="45" RenderTransformOrigin="0,0" Canvas.Bottom="-25" Canvas.Left="42"> </TextBlock> <Ellipse Width="2" Height="2" Canvas.Left="49" Canvas.Top="49" Stroke="Red" StrokeThickness="1"></Ellipse> </Canvas> <!--Y坐标尺度--> <Canvas x:Name="canvasYRuler" Panel.ZIndex="-1"> <!--以下为测试代码--> <Line X1="0" X2="260" Y1="0" Y2="0" Stroke="Blue" StrokeThickness="1" Canvas.Bottom="10" Canvas.Left="-8"></Line> <TextBlock Text="Y2" RenderTransformOrigin="0,0" Canvas.Bottom="2" Canvas.Left="-25"> </TextBlock> <Line X1="0" X2="260" Y1="0" Y2="0" Stroke="Blue" StrokeThickness="1" Canvas.Bottom="20" Canvas.Left="-8"></Line> <TextBlock Text="Y1" RenderTransformOrigin="0,0" Canvas.Bottom="12" Canvas.Left="-25"> </TextBlock> </Canvas> </Grid> </UserControl>
完整的Code代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace CoordinateXY { /// <summary> /// UserControlXY.xaml 的交互逻辑 /// </summary> public partial class UserControlXY : UserControl { public UserControlXY() { InitializeComponent(); this.Loaded += UserControlXY_Loaded; } private void UserControlXY_Loaded(object sender, RoutedEventArgs e) { InitXRuler(); InitYRuler(); } /// <summary> /// 放大倍数 防止坐标尺子重叠 /// </summary> private static double scaleNumX = 5; /// <summary> /// 放大倍数 防止坐标尺子重叠 /// </summary> private static double scaleNumY = 5; /// <summary> /// 按照宽度和高度计算放大倍数 /// </summary> private double scaleStandard = 40; /// <summary> /// x坐标尺度 /// </summary> private double xTotal = 200; /// <summary> /// Y坐标尺度 /// </summary> private double yTotal = 126; /// <summary> /// 刻度间隔 10刻度显示一个网格线 /// </summary> private double scaleInterval = 10; /// <summary> /// 网格刻度线延长出来的长度值 /// 修改此长度看效果图 /// </summary> private int xyLine = 0; /// <summary> /// xy坐标线长比网格绘制长度长多少 /// </summary> private int xyShorten = 40; /// <summary> /// 文本距离xy坐标线的位置 /// </summary> private int txtDis = 20; /// <summary> /// 宽度 /// </summary> private double _xWidth; /// <summary> /// 高度 /// </summary> private double _yHeight; /// <summary> /// 初始化X坐标尺 /// </summary> private void InitXRuler() { canvasXRuler.Children.Clear(); var xtotal = xTotal+1; for (int i = 1; i < xtotal; i++) { if (i % scaleInterval != 0 && i + 1 != xtotal) { continue; } Line xLine = new Line(); xLine.X1 = 1; xLine.X2 = 0; xLine.Y1 = 0; xLine.Y2 = this.Height - xyShorten + xyLine;//柱状线图形高度; xLine.Stroke = new SolidColorBrush(Color.FromRgb(0, 0, 255));//蓝色 xLine.StrokeThickness = 1; xLine.IsHitTestVisible = false; Canvas.SetLeft(xLine, i * scaleNumX); Canvas.SetBottom(xLine, -xyLine);//延迟8长度刻度 TextBlock txtBlock = new TextBlock(); txtBlock.Text = (i).ToString();//文本内容 Canvas.SetLeft(txtBlock, i * scaleNumX - 8);//两位数的文本平移8 让文本居中显示 Canvas.SetBottom(txtBlock, -txtDis);//刻度下方文本 canvasXRuler.Children.Add(xLine); canvasXRuler.Children.Add(txtBlock); } } /// <summary> /// 初始化Y坐标尺 /// </summary> private void InitYRuler() { canvasYRuler.Children.Clear(); var ytotal = yTotal+1; for (int i = 1; i < ytotal; i++) { if (i % scaleInterval != 0 && i + 1 != ytotal) { continue; } Line yLine = new Line(); yLine.X1 = 1; yLine.X2 = this.Width - xyShorten + xyLine;//柱状线图形长度; yLine.Y1 = 0; yLine.Y2 = 0; yLine.Stroke = new SolidColorBrush(Color.FromRgb(0, 0, 255));//蓝色 yLine.StrokeThickness = 1; yLine.IsHitTestVisible = false; Canvas.SetLeft(yLine, -xyLine);//刻度值 Canvas.SetBottom(yLine, i * scaleNumY); TextBlock txtBlock = new TextBlock(); txtBlock.Text = (i).ToString();//文本内容 Canvas.SetLeft(txtBlock, -txtDis - 2); Canvas.SetBottom(txtBlock, i * scaleNumY - 8);//两位数的文本平移8 让文本居中显示 canvasXRuler.Children.Add(yLine); canvasXRuler.Children.Add(txtBlock); } } private static UserControlXY uControlXY; /// <summary> /// 创建点的位置 /// </summary> /// <param name="point"></param> static void InCanvasPoint(Point point) { var temp = CreatePointEllipse(); //temp.ToolTip = point.X / scaleNumX + "," + point.Y / scaleNumY; temp.ToolTip = point.Y / scaleNumX + "," + point.X / scaleNumY + " " + "(" + point.Y + "," + point.X + ")"; uControlXY.canvasLinePoint.Children.Add(temp); Panel.SetZIndex(temp, 100); Canvas.SetLeft(temp, point.X - temp.Height / 2); Canvas.SetTop(temp, point.Y - temp.Width / 2); } /// <summary> /// 创建Point /// </summary> static void CreatePoint(List<Point> itemList) { if (itemList != null && itemList.Count > 0) { for (int i = 0; i < itemList.Count; i++) { var startPoint = itemList[i]; var tmpPoint = new Point(); tmpPoint.X = startPoint.Y * scaleNumY; tmpPoint.Y = startPoint.X * scaleNumX; InCanvasPoint(tmpPoint); if (i + 1 == itemList.Count) { break; } var endPoint = itemList[i + 1]; var tmpEndPoint = new Point(); tmpEndPoint.X = endPoint.Y * scaleNumY; tmpEndPoint.Y = endPoint.X * scaleNumX; CreateLine(tmpPoint, tmpEndPoint); } } } /// <summary> /// 创建连接的直线 /// </summary> /// <param name="startPoint"></param> /// <param name="endPoint"></param> static void CreateLine(Point startPoint, Point endPoint) { PathGeometry pg = new PathGeometry();//组合绘制的线段 Path pa = new Path();//绘制轨迹曲线的容器,用于显示 pa.Stroke = new SolidColorBrush(Color.FromRgb(0, 0, 0)); pa.StrokeThickness = 1; PathFigure pf = new PathFigure(); pf.StartPoint = startPoint; LineSegment line = new LineSegment(); line.Point = endPoint; pf.Segments.Add(line); pg.Figures.Add(pf); pa.Data = pg; uControlXY.canvasLinePoint.Children.Add(pa); } /// <summary> /// 创建圆点 /// </summary> /// <returns></returns> static Ellipse CreatePointEllipse() { Ellipse ell = new Ellipse(); ell.Stroke = new SolidColorBrush(Color.FromRgb(255, 0, 0)); ell.Fill = new SolidColorBrush(Color.FromRgb(255, 0, 0)); ell.Height = 8; ell.Width = 8; return ell; } public List<Point> ItemsSource { get { return (List<Point>)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } /// <summary> /// 宽度 /// </summary> public double XWidth { get { return _xWidth; } set { _xWidth = value; this.Width = value; //预留100的line长度 scaleNumX = (value - xyShorten) / scaleStandard / (xTotal/ scaleStandard); } } /// <summary> /// 求两点之间的弧线 /// item1 开始坐标 item2 结束坐标 item3 弧度值 /// </summary> public Tuple<Point, Point, double> PointArc { get { return (Tuple<Point, Point, double>)GetValue(PointArcProperty); } set { SetValue(PointArcProperty, value); } } /// <summary> /// 高度 /// </summary> public double YHeight { get { return _yHeight; } set { _yHeight = value; this.Height = value; //预留100的line长度 scaleNumY = (value - xyShorten) / scaleStandard / (yTotal / scaleStandard); } } public void Refresh(List<Point> _itemsSource) { canvasLinePoint.Children.Clear(); CreatePoint(_itemsSource); InitXRuler(); InitYRuler(); } // Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc... public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(List<Point>), typeof(UserControlXY), new PropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChangedCallback))); public static void OnItemsSourceChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (e.NewValue != null) { uControlXY = d as UserControlXY; CreatePoint(e.NewValue as List<Point>); } } // Using a DependencyProperty as the backing store for PointArc. This enables animation, styling, binding, etc... public static readonly DependencyProperty PointArcProperty = DependencyProperty.Register("PointArc", typeof(Tuple<Point, Point, double>), typeof(UserControlXY), new PropertyMetadata(null)); } }
使用方式:
<UserControl x:Class="CoordinateXY.UserControlShow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:CoordinateXY" mc:Ignorable="d"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="34" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid> <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="X Y Demos" FontSize="20"/> <DockPanel VerticalAlignment="Center"> <TextBox x:Name="txtboxWH" Text="600,600" Width="60"></TextBox> <Button Content="设置宽度和高度" Width="120" Margin="10 0 0 0" VerticalAlignment="Center" HorizontalAlignment="Left" Click="BtnRefresh_Click"></Button> </DockPanel> </Grid> <Grid Grid.Row="2" Background="DarkOrange"> <Viewbox> <local:UserControlXY x:Name="uControlXY" XWidth="600" YHeight="600" ItemsSource="{Binding XyList,Mode=TwoWay}" Margin="20"></local:UserControlXY> </Viewbox> </Grid> </Grid> </UserControl>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.ComponentModel; namespace CoordinateXY { /// <summary> /// UserControlShow.xaml 的交互逻辑 /// </summary> public partial class UserControlShow : UserControl { ViewMode vModel = new ViewMode(); public UserControlShow() { InitializeComponent(); this.DataContext = vModel; } private void BtnRefresh_Click(object sender, RoutedEventArgs e) { //uControlXY.Width = uControlXY.Width * 1.1; //uControlXY.Height = uControlXY.Height * 1.1; var txt = txtboxWH.Text.Trim(); string[] whs = txt.Split(‘,‘); if (whs.Length != 2) { return; } double w; double h; double.TryParse(whs[0], out w); double.TryParse(whs[1], out h); if (w != 0 && h != 0) { this.uControlXY.XWidth = w; this.uControlXY.YHeight = h; this.uControlXY.Refresh(vModel.XyList); } } } public class ViewMode : INotifyPropertyChanged { public ViewMode() { _xyList = new List<Point>(); XyList.Add(new Point(10, 10)); XyList.Add(new Point(40, 50)); XyList.Add(new Point(30, 40)); XyList.Add(new Point(90, 10)); XyList.Add(new Point(20, 90)); XyList.Add(new Point(45.5, 73.2)); } private List<Point> _xyList; public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(PropertyChangedEventArgs e) { if (PropertyChanged != null) { PropertyChanged(this, e); } } public List<Point> XyList { get { return _xyList; } set { _xyList = value; OnPropertyChanged(new PropertyChangedEventArgs("XyList")); } } } }
此代码textblock文本显示的地方存在一定的问题,就是文本字数不确定性,x坐标Center对齐有问题,y坐标文本会向右(仔细看y=100那个标尺,如果y=1000就会跑到绿色线里面去了),求大神们给个思路。