using Io.Github.Kerwinxu.LibShapes.Core.Command; using Io.Github.Kerwinxu.LibShapes.Core.Event; using Io.Github.Kerwinxu.LibShapes.Core.Shape; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Text; using System.Windows.Forms; /** 这个画布需要对外联系的是 1. 各种属性 2. objectSelected : 选择的对象更改事件。 * * **/ namespace Io.Github.Kerwinxu.LibShapes.Core { /// /// 画布 /// public partial class UserControlCanvas : UserControl { #region 构造函数 public UserControlCanvas() { InitializeComponent(); init(); } private void init() { // 这里设置默认颜色 GriddingInterval = 2; // 默认网格2mm commandRecorder = new CommandRecorder(); // 默认的命令记录器 shapes = new Shapes(); // 默认的形状 state = new State.StateStandby(this); // 默认是待机状态。 // 这里先取得dpi var g = this.CreateGraphics(); var _dpix = g.DpiX; var _dpiy = g.DpiY; g.Dispose(); // 一堆的事件 // 如下的鼠标事件,先将坐标转换成毫米了。 bool isLeftDown = false; // 左键是否按下。移动的时候需要判断是否按下的。 // todo 加入键盘事件的处理, this.MouseDown += (sender, e) => { if (!IsEdit) return; isLeftDown = true; if (e.Button == MouseButtons.Left) this.state.LeftMouseDown(PointTransform.pixToMM(_dpix, _dpiy, new PointF(e.X, e.Y))); this.Refresh(); }; this.MouseMove += (sender, e) => { if (!IsEdit) return; if (!isLeftDown) return; if (e.Button == MouseButtons.Left) this.state.LeftMouseMove(PointTransform.pixToMM(_dpix, _dpiy, new PointF(e.X, e.Y))); this.Refresh(); }; this.MouseUp += (sender, e) => { if (!IsEdit) return; isLeftDown = false; if (e.Button == MouseButtons.Left) this.state.LeftMouseUp(PointTransform.pixToMM(_dpix, _dpiy, new PointF(e.X, e.Y))); this.Refresh(); }; this.MouseClick += (sender, e) => { if (!IsEdit) return; if (e.Button == MouseButtons.Right) this.state.RightMouseClick(PointTransform.pixToMM(_dpix, _dpiy, new PointF(e.X, e.Y))); this.Refresh(); }; // 双缓冲,这里需要打开。 this.DoubleBuffered = true; // 说是推荐如上的这个, //this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true); // 这里有一个特殊的设置,如果偏移为空的话,这里默认的偏移是有这个刻度尺的 if (shapes.pointTransform == null || (shapes.pointTransform != null && shapes.pointTransform.OffsetX == 0 && shapes.pointTransform.OffsetY == 0) ) { // 稍微多留一点出来。 shapes.pointTransform.OffsetX = scaleWidth * 1.5f; shapes.pointTransform.OffsetY = scaleWidth * 1.5f; } } #endregion #region 一堆的常量 private float scaleWidth = 6; // 刻度尺的宽度,这个是固定的,单位是mm private float scaleLineWidth = 0.1f; // 刻度尺上的刻度线的宽度。 private Font scaleTextFont = new Font("Arial", 6); // 刻度尺上文字的字体。 private float minIntervalpixel = 5; // 比如网格,如果小于这个,就不打印了。 private Color scaleBackColor = Color.Orange; private float grid_width = 0.25f; // 绘制的网格的宽度 private Brush grid_brush = new SolidBrush(Color.Black); // 网格的 private Pen penSelectShape = new Pen(Color.Pink) { // 选择框的画笔 DashStyle=DashStyle.Dash, Width = 1f }; #endregion #region 属性 // 如下的很多设置成属性是因为我方便从别的地方设置,这个属性其实是有个set方法的。 /// /// 是否可以编辑 /// public bool IsEdit { get; set; } /// /// 形状 /// public Shapes shapes; /// /// 显示网格 /// public bool isDrawDridding { get; set; } /// /// 网格间隔 /// public int GriddingInterval { get; set; } // 默认是1,2,5mm /// /// 对齐网格,这个在移动和更改尺寸的时候用到。 /// public bool isAlignDridding { get; set; } /// /// 命令记录的 /// internal ICommandRecorder commandRecorder { get; set; } /// /// 是否安装了shift键 /// public bool isShift { get; set; } private State.State _state; /// /// 现在的状态 /// public State.State state { get { return _state; } set { _state = value; onStateChanged(); } } /// /// 当前选择的图形 /// public ShapeEle SelectShape { get; set; } #endregion #region 事件 #region 选择更改事件 // 委托 public delegate void ObjectSelected(object sender, ObjectSelectEventArgs e); // 事件 public event ObjectSelected objectSelected; private void onObjectSelected(Object obj) { // 首先生成参数 var args = new ObjectSelectEventArgs(obj); // 然后看看是否有监听的。 if (objectSelected != null) { objectSelected(this, args); } } /// /// 给外部调用的,可以更改当前的选择。 /// /// public void changeSelect(Object obj) { SelectShape = obj as ShapeEle; this.oldShapes = this.shapes.DeepClone(); if (SelectShape == null) { // 这里是设置纸张。 onObjectSelected(shapes.Paper); }else { onObjectSelected(obj); } } #endregion #region 状态更改事件 // 委托 public delegate void StateChanged(object sender, StateChangedEventArgs e); // 事件 public event StateChanged stateChanged; private void onStateChanged() { // 首先生成参数 var args = new StateChangedEventArgs(this.state); // 然后看看是否有监听的。 if (stateChanged != null) { stateChanged(this, args); } } #endregion #region 属性更改事件处理, private Shapes oldShapes; public void propertyValueChanged(object s, PropertyValueChangedEventArgs e) { this.Refresh(); // 这里时在发送给propertyGrid前就有保存。 this.commandRecorder.addCommand(new Command.CommandShapesChanged() { canvas =this, OldShapes = this.oldShapes, NewShapes = this.shapes.DeepClone(), }) ; } #endregion #endregion #region 绘图相关 /// /// 绘图 /// /// /// private void UserControlCanvas_Paint(object sender, PaintEventArgs e) { if (this.shapes == null) {return; } // 这里要重新更新一下变量,这样相当于更新一遍。 this.shapes.Vars = this.shapes.Vars; // 如下的取消了双缓冲,是因为并不能达到预期,反倒是非常闪烁。 // 最简单的方式是设置属性 this.DoubleBuffered = true; // 这里重新绘图用上了双缓冲 // 1. 首先在内存中建立Graphics对象 //BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current; //BufferedGraphics myBuffer = currentContext.Allocate(e.Graphics, e.ClipRectangle); //Graphics g = myBuffer.Graphics; // 1. 取得Graphics对象 Graphics g = e.Graphics; // 全局的偏移和缩放,全局不涉及旋转,因为刻度尺。 var matrix = shapes.GetMatrix(); // 背景色,默认用这个控件的背景色填充。 g.Clear(this.BackColor); // 2. 形状绘图 if (this.shapes != null) { // 绘图,并且用上了偏移和缩放。 shapes.Draw(g, matrix); } // 3. 绘制网格 if (isDrawDridding) { drawGrid(g); } // 4. 绘制选择框 drawSelectRect(g); // 5. 绘制刻度尺 drawScale(g); // 6. 这里绘制虚线,要选择哪些东西的 if (this.state is State.ShapeRectSelect) { ((State.ShapeRectSelect)this.state).Draw(g); } // 7. 刷新双缓冲 //myBuffer.Render(e.Graphics); //g.Dispose(); //myBuffer.Dispose();//释放资源 } /// /// 绘制网格 /// /// private void drawGrid(Graphics g) { // 我的思路是,首先判断是否小于最小 if (GriddingInterval / 25.4 * g.DpiX * shapes.pointTransform.Zoom < minIntervalpixel) return; // 如下是计算范围 PointF p1 = new PointF(0, 0); PointF p2 = new PointF(this.Width / g.DpiX * 25.4f, this.Height / g.DpiY * 25.4f); var point1 = shapes.pointTransform.CanvasToVirtualPoint(p1); // 左上角画布的坐标 var point2 = shapes.pointTransform.CanvasToVirtualPoint(p2); // 右下角的坐标 // 画笔 Pen pen = new Pen(Color.Black); pen.Width = 0.1f; // 画笔的用很细的。 int x_start = (int)point1.X / GriddingInterval * GriddingInterval; int y_start = (int)point1.Y / GriddingInterval * GriddingInterval; int i_count = (int)(point2.X - point1.X) / GriddingInterval; int j_count = (int)(point2.Y - point1.Y) / GriddingInterval; // 然后这里绘制 for (int i = 0; i < i_count; i++) { for (int j = 0; j < j_count; j++) { // 我这里是将坐标转成画布的坐标 var point3 = new PointF( x_start + i * GriddingInterval, y_start + j * GriddingInterval); var point4 = shapes.pointTransform.VirtualToCanvasPoint(point3); // 这里进行绘制,这里实际上是绘制一个圆 var point5 = new PointF(point4.X - grid_width/2, point4.Y - grid_width/2); var rect = new RectangleF(point5, new SizeF(grid_width, grid_width)); // g.FillEllipse(grid_brush, rect); } } } /// /// 绘制选择框 /// /// private void drawSelectRect(Graphics g) { if(this.SelectShape != null) { // 如果有图形,就取得边框,这个边框的尺寸控件是画布的尺寸控件,已经做过转换了。 var rect = SelectShape.GetBounds(shapes.GetMatrix()); // 然后绘制这个边框就是了。 GraphicsPath path = new GraphicsPath(); path.AddRectangle(rect); g.DrawPath(penSelectShape, path); } } /// /// 绘制刻度。 /// /// private void drawScale(Graphics g) { // 这个刻度值是在上边和左边的。 // 1 . 首先绘制刻度的背景 // 如下是计算范围 var point1 = shapes.pointTransform.CanvasToVirtualPoint(new PointF(0, 0)); // 左上角虚拟的坐标 var point2 = shapes.pointTransform.CanvasToVirtualPoint(new PointF(this.Width / g.DpiX * 25.4f, this.Height / g.DpiY * 25.4f)); // 右下角的坐标 // 如下的是计算这个自定义控件的宽度和高度,请注意,这里不算缩放。 float w = this.Width / g.DpiX * 25.4f; float h = this.Height / g.DpiY * 25.4f ; // 控件大小不变,这个如下的就是固定的。 g.FillRectangle( new SolidBrush(this.scaleBackColor), new RectangleF(0,0, w, scaleWidth) ); g.FillRectangle( new SolidBrush(this.scaleBackColor), new RectangleF(0, 0, scaleWidth, h) ); // 2.然后是各个刻度,先水平,后竖直 Pen pen = new Pen(Color.Black); pen.Width = scaleLineWidth; // 2.1 水平刻度 horizontal_scale_line(g, pen, point1.X, point2.X, 1, 0.5f); horizontal_scale_line(g, pen, point1.X, point2.X, 2, 1); horizontal_scale_line(g, pen, point1.X, point2.X, 5, 2); horizontal_scale_line(g, pen, point1.X, point2.X, 10, 3); horizontal_scale_text(g, new SolidBrush(Color.Black), point1.X, point2.X); // 2.2 垂直刻度 vertical_scale_line(g, pen, point1.Y, point2.Y, 1, 0.5f); vertical_scale_line(g, pen, point1.Y, point2.Y, 2, 1); vertical_scale_line(g, pen, point1.Y, point2.Y, 5, 2); vertical_scale_line(g, pen, point1.Y, point2.Y, 10, 3); vertical_scale_text(g, new SolidBrush(Color.Black), point1.Y, point2.Y); // 3. 左上角有重复的,这里直接空白出来 g.FillRectangle( new SolidBrush(this.scaleBackColor), new RectangleF(0, 0, scaleWidth, scaleWidth) ); } /// /// 水平刻度尺 /// /// /// 开始的x坐标 /// 结束的x坐标 /// y坐标 /// 间隔 /// 线的长度 private void horizontal_scale_line(Graphics g, Pen pen, float start_x, float end_x, int intInterval, float line_length) { // 变量 int x_start = (int)(start_x-1)/ intInterval * intInterval; int x_end = (int)(end_x+1)/ intInterval * intInterval; int i_count = (x_end - x_start) / intInterval +1; // 这里要计算一下是否太密了。 if (intInterval / 25.4 * g.DpiX * shapes.pointTransform.Zoom < minIntervalpixel) return; if (intInterval / 25.4 * g.DpiY * shapes.pointTransform.Zoom < minIntervalpixel) return; // 这里是按照画布的坐标绘制的 // 其y坐标为y,另外向上有line_length的长度 // 这个首先算出起始的坐标是多少 for (int i = 0; i < i_count; i++) { PointF p1 = new PointF(x_start + i * intInterval, 0); // 虚拟的坐标。 // 这里计算在画布中的坐标是多少 var p3 = shapes.pointTransform.VirtualToCanvasPoint(p1); p3.Y = scaleWidth; // 这个是固定的。 var p4 = new PointF(p3.X, p3.Y - line_length); g.DrawLine(pen, p3, p4); } } /// /// 这个是绘制文本的 /// /// /// /// /// private void horizontal_scale_text(Graphics g, Brush brush, float start_x, float end_x) { int intInterval = 10; int x_start = (int)(start_x - 1) / intInterval * intInterval; int x_end = (int)(end_x + 1) / intInterval * intInterval; int i_count = (x_end - x_start) / intInterval + 1; for (int i = 0; i < i_count; i++) { // 这里写文字 int j = x_start + i * intInterval ; string _text = (j/ intInterval).ToString(); // 显示的是cm,默认的是毫米 // 然后求x坐标 var p1 = new PointF(j, 0); var p2 = shapes.pointTransform.VirtualToCanvasPoint(p1); p2.Y = 0;// 手动更改成0 // 这个p2坐标就是文字中线的位置,这里还要往左边移动一些。 var _size = g.MeasureString(_text, scaleTextFont); // 然后这个转成 p2.X -= _size.Width / 2; g.DrawString(_text, scaleTextFont, brush, p2); } } /// /// 竖直的刻度尺 /// /// /// 开始的x坐标 /// 结束的x坐标 /// y坐标 /// 间隔 /// 线的长度 private void vertical_scale_line(Graphics g, Pen pen, float start_y, float end_y, int intInterval, float line_length) { // 变量 int y_start = (int)(start_y - 1) / intInterval * intInterval; int y_end = (int)(end_y + 1) / intInterval * intInterval; int i_count = (y_end - y_start) / intInterval + 1; // 这里要计算一下是否太密了。 if (intInterval / 25.4 * g.DpiY * shapes.pointTransform.Zoom < minIntervalpixel) return; if (intInterval / 25.4 * g.DpiY * shapes.pointTransform.Zoom < minIntervalpixel) return; // 这里是按照画布的坐标绘制的 // 其y坐标为y,另外向上有line_length的长度 // 这个首先算出起始的坐标是多少 for (int i = 0; i < i_count; i++) { PointF p1 = new PointF(0, y_start + i * intInterval); // 虚拟的坐标。 // 这里计算在画布中的坐标是多少 var p3 = shapes.pointTransform.VirtualToCanvasPoint(p1); p3.X = scaleWidth; // 这个是固定的。 var p4 = new PointF(p3.X - line_length, p3.Y); g.DrawLine(pen, p3, p4); } } /// /// 这个是绘制文本的 /// /// /// /// /// private void vertical_scale_text(Graphics g, Brush brush, float start_y, float end_y) { int intInterval = 10; int y_start = (int)(start_y - 1) / intInterval * intInterval; int y_end = (int)(end_y + 1) / intInterval * intInterval; int i_count = (y_end - y_start) / intInterval + 1; for (int i = 0; i < i_count; i++) { // 这里写文字 int j = y_start + i * intInterval; string _text = (j / intInterval).ToString(); // 显示的是cm,默认的是毫米 // 然后求x坐标 var p1 = new PointF(0, j); var p2 = shapes.pointTransform.VirtualToCanvasPoint(p1); p2.X = 0;// 手动更改成0 // 这个p2坐标就是文字中线的位置,这里还要往左边移动一些。 var _size = g.MeasureString(_text, scaleTextFont); // 然后这个转成 p2.Y -= _size.Height / 2; g.DrawString(_text, scaleTextFont, brush, p2); } } #endregion #region 请注意,如下的几个操作都只是处理顶层的 /// /// 以某个点为中心放大 /// /// public void zoom(PointF point) { // 1. 取得这个点在虚拟画布上的地址 var point2 = this.shapes.pointTransform.CanvasToVirtualPoint(point); // 2. 更新偏移 this.shapes.pointTransform.OffsetX -= point2.X * (this.shapes.pointTransform.Zoom ); this.shapes.pointTransform.OffsetY -= point2.Y * (this.shapes.pointTransform.Zoom ); // 3 放大 this.shapes.pointTransform.Zoom *= 2; // 4. 刷新 this.Refresh(); } /// /// 以某个点为中心缩小。 /// /// public void reduce(PointF point) { // 1. 取得这个点在虚拟画布上的地址 var point2 = this.shapes.pointTransform.CanvasToVirtualPoint(point); // 2. 更新偏移 this.shapes.pointTransform.OffsetX += point2.X * (this.shapes.pointTransform.Zoom/2); this.shapes.pointTransform.OffsetY += point2.Y * (this.shapes.pointTransform.Zoom/2); // 3 放大 this.shapes.pointTransform.Zoom /= 2; // 4. 刷新 this.Refresh(); } /// /// 放大到屏幕 /// public void zoomToScreen() { if (shapes != null) { var g = this.CreateGraphics(); shapes.zoomTo( g.DpiX, g.DpiY, this.Width, this.Height, scaleWidth/25.4f*g.DpiX* 1.5f // scaleWidth/25.4f*g.DpiX表示刻度尺实际的像素宽度, // 而*1.5,表示两边流出1.5倍数的距离,这样,图形也不会太靠近边界。 ); this.Refresh(); } } /// /// 添加一个新的形状 /// /// public void addShape(ShapeEle shape) { // 只是在顶层添加。 shapes.lstShapes.Add(shape); changeSelect(shape); // 添加后是当作当前选择的。 } /// /// 删除, /// public void deleteShapes(ShapeEle shape) { //返回的是第几个项目删除的。 if (shape is ShapeMultiSelect) // 这里表示选择了多个。 { // 这里说明是选择的一堆,这里一个一个的删除 foreach (var item in ((ShapeMultiSelect)shape).shapes) { deleteShapes(item); } } else { // 这里说明可能是一个形状。 var index = shapes.lstShapes.IndexOf(shape); if (index == -1) return; var oldShapes = this.shapes.DeepClone(); shapes.lstShapes.Remove(shape);// 请注意,这里只是删除顶层的。 // 这里添加command commandRecorder.addCommand(new Command.CommandShapesChanged() { canvas=this, OldShapes = oldShapes, NewShapes = this.shapes.DeepClone() }); } this.Refresh();// 刷新。 } /// /// 删除已经选中的形状 /// public void deleteShapes() { // 这里要判断一下是否是群组 if (SelectShape != null) { deleteShapes(SelectShape); } } // todo 请注意,如下的几个全部要添加命令 public void forward(int id) { // todo 添加命令。 var shape = shapes.getShape(id); int index = shapes.lstShapes.IndexOf(shape);// 取得下标 if (index > 0) { // 如果不是最前面的 shapes.lstShapes.Remove(shape); shapes.lstShapes.Insert(index - 1, shape); } } public void forwardToFront(int id) { // todo 添加命令。 var shape = shapes.getShape(id); int index = shapes.lstShapes.IndexOf(shape);// 取得下标 if (index > 0) { // 如果不是最前面的 shapes.lstShapes.Remove(shape); shapes.lstShapes.Insert(0, shape); } } public void backward(int id) { // todo 添加命令。 var shape = shapes.getShape(id); int index = shapes.lstShapes.IndexOf(shape);// 取得下标 if (index >= 0 && index < shapes.lstShapes.Count-1) { // 如果不是最前面的 shapes.lstShapes.Remove(shape); shapes.lstShapes.Insert(index + 1, shape); } } public void backwardToEnd(int id) { // todo 添加命令。 var shape = shapes.getShape(id); int index = shapes.lstShapes.IndexOf(shape);// 取得下标 if (index >= 0 && index < shapes.lstShapes.Count - 1) { // 如果不是最前面的 shapes.lstShapes.Remove(shape); shapes.lstShapes.Insert(shapes.lstShapes.Count-1, shape); } } public void forward() { //这里用当前的 if (SelectShape != null) { forward(SelectShape.ID); } } public void forwardToFront() { if (SelectShape != null) { forwardToFront(SelectShape.ID); } } public void backward() { //这里用当前的 if (SelectShape != null) { backward(SelectShape.ID); } } public void backwardToEnd() { //这里用当前的 if (SelectShape != null) { backwardToEnd(SelectShape.ID); } } /// /// 组成群组 /// /// public ShapeGroup mergeGroup(List shapes) { // todo 添加命令。 ShapeGroup group = new ShapeGroup(); group.shapes.AddRange(shapes); group.ID = this.shapes.getNextId(); foreach (var item in shapes) { this.shapes.lstShapes.Remove(item); // 从旧的里边删除。 } addShape(group); return group; } /// /// 取消这个群组 /// /// public void cancelGroup(ShapeGroup group) { this.shapes.lstShapes.Remove(group); foreach (var item in group.shapes) { this.shapes.lstShapes.Add(item); } } /// /// 将选择的组成群组 /// public void mergeGroup() { if (SelectShape is ShapeGroup) { var group = mergeGroup(((ShapeGroup)SelectShape).shapes); changeSelect(group); } } /// /// 将选择的取消群组 /// public void cancelGroup() { // 得判断 if (SelectShape is ShapeGroup) { cancelGroup((ShapeGroup)SelectShape); changeSelect(null);// 取消选择 } } #region 各种对齐 /// /// 上对齐 /// public void align_top() { if (this.SelectShape is Shape.ShapeMultiSelect) { // float _y = ((Shape.ShapeMultiSelect)this.SelectShape).shapes[0].Y; foreach (var item in ((Shape.ShapeMultiSelect)this.SelectShape).shapes) { item.Y = _y; } this.Refresh(); } } /// /// 下对齐 /// public void align_bottom() { if (this.SelectShape is Shape.ShapeMultiSelect) { // float _y = ((Shape.ShapeMultiSelect)this.SelectShape).shapes[0].Y; float _height = ((Shape.ShapeMultiSelect)this.SelectShape).shapes[0].Height; float _y_height = _y + _height; foreach (var item in ((Shape.ShapeMultiSelect)this.SelectShape).shapes) { item.Y = _y_height - item.Height; // 减去高度,得到新的y } this.Refresh(); } } /// /// 左对齐 /// public void align_left() { if (this.SelectShape is Shape.ShapeMultiSelect) { // float _x = ((Shape.ShapeMultiSelect)this.SelectShape).shapes[0].X; foreach (var item in ((Shape.ShapeMultiSelect)this.SelectShape).shapes) { item.X = _x; } this.Refresh(); } } /// /// 右对齐 /// public void align_right() { if (this.SelectShape is Shape.ShapeMultiSelect) { float _x = ((Shape.ShapeMultiSelect)this.SelectShape).shapes[0].X; float _width = ((Shape.ShapeMultiSelect)this.SelectShape).shapes[0].Width; float _y_width = _x + _width; foreach (var item in ((Shape.ShapeMultiSelect)this.SelectShape).shapes) { item.X = _y_width - item.Width; // 减去高度,得到新的y } this.Refresh(); } } /// /// 水平居中。 /// public void align_center() { if (this.SelectShape is ShapeMultiSelect) { // 首先取得哪个的宽度是最宽的 float _x=0, _width=0; foreach (var item in ((Shape.ShapeMultiSelect)this.SelectShape).shapes) { if (item.Width > _width) { _x = item.X; _width = item.Width; } } // 然后所有的都超这个对齐 float _center = _x + _width / 2; foreach (var item in ((Shape.ShapeMultiSelect)this.SelectShape).shapes) { item.X = _center - item.Width / 2; } this.Refresh(); } } /// /// 垂直居中。 /// public void align_midele() { if (this.SelectShape is ShapeMultiSelect) { // 首先取得哪个的宽度是最宽的 float _y = 0, _height = 0; foreach (var item in ((Shape.ShapeMultiSelect)this.SelectShape).shapes) { if (item.Height > _height) { _y = item.Y; _height = item.Height; } } // 然后所有的都超这个对齐 float _midele = _y + _height / 2; foreach (var item in ((Shape.ShapeMultiSelect)this.SelectShape).shapes) { item.Y = _midele - item.Height / 2; } this.Refresh(); } } #endregion #region 撤销重做 /// /// 撤销 /// public void undo() { this.commandRecorder.Undo(); this.Refresh(); } public void redo() { this.commandRecorder.Redo(); this.Refresh(); } #endregion #region 剪切复制粘贴 /// /// 复制的保存在这里 /// private ShapeEle _copy_shape; /// /// 剪切 /// public void cut() { // if (SelectShape != null) { _copy_shape = SelectShape.DeepClone();// 先保存 deleteShapes(SelectShape);// 删除 } } /// /// 复制 /// public void copy() { if (SelectShape != null) { _copy_shape = SelectShape.DeepClone();// 跟剪切的区别是这里不删除。 } } /// /// 粘贴 /// public void paste() { // 我这里先思考一下思路,这个本质上是复制, // 首先要判断一下是否是ShapeMultiSelect,如果是,表示要复制内部的,而如果不是,那么只是复制这一个。 if (_copy_shape is Shape.ShapeMultiSelect) { ShapeMultiSelect shapeMultiSelect = new ShapeMultiSelect(); // 这个里边所有的形状都复制 foreach (var item in ((ShapeMultiSelect)_copy_shape).shapes) { // 说明这里就一个形状 addShape(item); currectIdAndYadd(item, 5); // 这个会更新id以及向下移动 shapeMultiSelect.shapes.Add(item); } // changeSelect(shapeMultiSelect); } else { // 说明这里就一个形状 addShape(_copy_shape); currectIdAndYadd(_copy_shape, 5); // 这个会更新id以及向下移动 changeSelect(_copy_shape); // 这个是当前的id } // 设置新的。 _copy_shape = _copy_shape.DeepClone(); this.Refresh(); } /// /// 矫正id的,并且矫正的需要y加上一个指定的数字的。 /// /// /// private void currectIdAndYadd(ShapeEle shape, float y_add) { if (shape is ShapeMulti) { foreach (var item in ((ShapeMultiSelect)shape).shapes) { currectIdAndYadd(item, y_add); } } else { shape.ID = shapes.getNextId(); shape.Y += y_add; } } #endregion public void selectAll() { // todo 全选 } #endregion public void setVars(Dictionary dict) { this.shapes.Vars = dict; this.Refresh(); } #region 小函数 /// /// 对齐网格后的坐标,这个主要是提供给更改尺寸的。 /// /// /// public PointF gridAlign(PointF point) { // 这里首先判断是否需要对齐网格 if (isAlignDridding) { // 这里需要转换坐标 var p1 = this.shapes.pointTransform.CanvasToVirtualPoint(point); // 先转成虚拟的坐标 var p2 = new PointF() { X = ((int)Math.Round(p1.X / GriddingInterval, 0)) * GriddingInterval, // 对齐,四舍五入五 Y = ((int)Math.Round(p1.Y / GriddingInterval, 0)) * GriddingInterval, }; return this.shapes.pointTransform.VirtualToCanvasPoint(p2); // 转成画布的坐标 } else { return point; // 不用对齐,直接返回原先的。 } } #endregion } }