项目使用VS2019,采用三层架构,UI层,绘图层,插补算法层 。(本人为机械专业初学者,为数控编程课开发的辅助教学小程序,存在诸多不足,请各位大佬多多指正)
一.UI设计
根据扩展需求,设计若干子窗体嵌入进主窗体中,主窗体只负责打开子窗体
附:打开子窗体的代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace InterpolationDemo
{
public partial class FrmMain : Form
{
public FrmMain()
{
InitializeComponent();
}
/// <summary>
/// 通用打开子窗体方法
/// </summary>
/// <param name="frm"></param>
public void OpenForm(Form frm)
{
foreach (Control item in this.panel1.Controls)
{
if(item is Form)
{
Form currentFrm = (Form)item;
currentFrm.Close();
}
}
frm.TopLevel = false;
frm.FormBorderStyle = FormBorderStyle.None;
frm.Parent = this.panel1;
frm.Dock = DockStyle.Fill;
frm.Show();
}
private void 圆弧插补ToolStripMenuItem_Click(object sender, EventArgs e)
{
}
private void 直线插补LToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenForm(new FrmLinearInterpolation());
}
}
}
直线插补子窗体代码,主要是获取UI输入信息,然后调用自己写的绘图类的方法进行绘制。
因为实现了分层架构,对象职责明确,UI中的代码不涉及算法与具体的GDI功能,只负责调用与绘制
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using DrawingByGDI;
namespace InterpolationDemo
{
public partial class FrmLinearInterpolation : Form
{
private DrawingCoordinate drawingCoordinate = new DrawingCoordinate();
private DrawingGraph drawingGraph = new DrawingGraph();
Thread thread = null;
public FrmLinearInterpolation()
{
InitializeComponent();
btnViewCoordinate_Click(null, null);
//ViewCoordinate();
}
private void ViewCoordinate()
{
int maxValue1 = Math.Abs(
Math.Max(
Math.Abs(Convert.ToInt32(txtDestinationX.Text)), Math.Abs(Convert.ToInt32(this.txtDestinationY.Text))
)
);
int maxValue2 = Math.Abs(
Math.Max(
Math.Abs(Convert.ToInt32(txtStartX.Text)), Math.Abs(Convert.ToInt32(this.txtStartY.Text))
)
);
int maxValue = Math.Max(maxValue1, maxValue2);
drawingCoordinate.DrawCoordinate(this.panelGraphics, maxValue, 10);
}
private void btnView_Click(object sender, EventArgs e)
{
#region 数据验证
if (this.txtDestinationX.Text.Trim().Length == 0)
{
MessageBox.Show("输入不能为空!");
this.txtDestinationX.Focus();
return;
}
if (this.txtDestinationY.Text.Trim().Length == 0)
{
MessageBox.Show("输入不能为空!");
this.txtDestinationY.Focus();
return;
}
if (this.txtStartX.Text.Trim().Length == 0)
{
MessageBox.Show("输入不能为空!");
this.txtStartX.Focus();
return;
}
if (this.txtStartY.Text.Trim().Length == 0)
{
MessageBox.Show("输入不能为空!");
this.txtStartY.Focus();
return;
}
if (!IsInteger(this.txtDestinationX.Text.Trim()))
{
MessageBox.Show("必须输入一个整数");
this.txtDestinationX.Focus();
return;
}
if (!IsInteger(this.txtDestinationY.Text.Trim()))
{
MessageBox.Show("必须输入一个整数");
this.txtDestinationY.Focus();
return;
}
if (!IsInteger(this.txtStartX.Text.Trim()))
{
MessageBox.Show("必须输入一个整数");
this.txtStartX.Focus();
return;
}
if (!IsInteger(this.txtStartY.Text.Trim()))
{
MessageBox.Show("必须输入一个整数");
this.txtStartY.Focus();
return;
}
#endregion
try
{
ViewCoordinate();
Point pi = new Point(Convert.ToInt32(this.txtStartX.Text), Convert.ToInt32(this.txtStartY.Text));
Point pe = new Point(Convert.ToInt32(this.txtDestinationX.Text), Convert.ToInt32(this.txtDestinationY.Text));
thread = new Thread(()=>
{
drawingCoordinate.DrawPoint(this.panelGraphics, pe, pi);
}
);
thread.IsBackground = false;
thread.Start();
}
catch (Exception ex)
{
throw ex;
}
}
private static bool IsInteger(string txt)
{
Regex objReg = new Regex("^-?[0-9]+[0-9]*$");
return objReg.IsMatch(txt);
}
private void btnRecover_Click(object sender, EventArgs e)
{
thread.Abort();
drawingCoordinate.DrawClear(this.panelGraphics);
ViewCoordinate();
}
private void FrmLinearInterpolation_Load(object sender, EventArgs e)
{
ViewCoordinate();
}
private void btnViewCoordinate_Click(object sender, EventArgs e)
{
ViewCoordinate();
}
}
}
二、逐点比较法算法层
算法简单,主要是四个节拍,网上有大佬解析,这里只粘贴代码。
基本原理,是根据起点和终点利用算法得到中途的点的集合,并返回给GDI,让GDI根据点集进行绘制
这里只做了直线算法的类,圆弧算法还没做
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AlgorithmInterpolation
{
public class LinearInterpolation
{
public enum Quadrant
{
一,
二,
三,
四,
X正,
X负,
Y正,
Y负,
原点
}
/// <summary>
/// 只关心从原点开始,通过坐标平移的方式将原直线平移至原点,最后平移回去
/// </summary>
/// <param name="pe">终点</param>
/// <param name="pi">原点</param>
/// <returns></returns>
public List<Point> DisEquation(Point peBefore,Point piBefore)
{
//1将直线平移到原点
Point pStart = piBefore;
Point pe = Translation(peBefore, pStart, 0);
Point pi = Translation(piBefore, pStart, 0);
List<Point> pList = new List<Point>();
pList.Add(pi);
//终点判别计数器
int totalCount = Math.Abs(pe.X) + Math.Abs(pe.Y);
int f = pi.Y * pe.X - pi.X * pe.Y;
Quadrant quadrant = JudgeQuadrant(pe, pi);
switch (quadrant)
{
case Quadrant.一:
for (int i = 0; i < totalCount; i++)
{
if (f >= 0)
{
pi.X++;
f -= pe.Y;
pList.Add(pi);
continue;
}
else if (f < 0)
{
pi.Y++;
f += pe.X;
pList.Add(pi);
continue;
}
}
break;
case Quadrant.二:
for (int i = 0; i < totalCount; i++)
{
if (f >= 0)
{
pi.X--;
f -= pe.Y;
pList.Add(pi);
continue;
}
else if (f < 0)
{
pi.Y++;
f -= pe.X;
pList.Add(pi);
continue;
}
}
break;
case Quadrant.三:
for (int i = 0; i < totalCount; i++)
{
if (f >= 0)
{
pi.X--;
f += pe.Y;
pList.Add(pi);
continue;
}
else if (f < 0)
{
pi.Y--;
f -= pe.X;
pList.Add(pi);
continue;
}
}
break;
case Quadrant.四:
for (int i = 0; i < totalCount; i++)
{
if (f >= 0)
{
pi.X++;
f += pe.Y;
pList.Add(pi);
continue;
}
else if (f < 0)
{
pi.Y--;
f += pe.X;
pList.Add(pi);
continue;
}
}
break;
case Quadrant.X正:
for (int i = 0; i < totalCount; i++)
{
pi.X++;
pList.Add(pi);
}
break;
case Quadrant.X负:
for (int i = 0; i < totalCount; i++)
{
pi.X--;
pList.Add(pi);
}
break;
case Quadrant.Y正:
for (int i = 0; i < totalCount; i++)
{
pi.Y++;
pList.Add(pi);
}
break;
case Quadrant.Y负:
for (int i = 0; i < totalCount; i++)
{
pi.Y--;
pList.Add(pi);
}
break;
case Quadrant.原点:
break;
default:
break;
}
//2将直线平移回去
for (int i = 0; i < pList.Count; i++)
{
pList[i] = Translation(pList[i], pStart, 1);
}
return pList;
}
private Quadrant JudgeQuadrant(Point pe,Point pi)
{
//集合的嵌套与多分支哪个算法更好
if (pe.X > pi.X && pe.Y > pi.Y)
{
return Quadrant.一;
}
else if (pe.X < pi.X && pe.Y > pi.Y)
{
return Quadrant.二;
}
else if (pe.X < pi.X && pe.Y < pi.Y)
{
return Quadrant.三;
}
else if (pe.X > pi.X && pe.Y < pi.Y)
{
return Quadrant.四;
}
else if (pe.X == pi.X)
{
if (pe.Y > pi.Y)
{
return Quadrant.Y正;
}
else
{
return Quadrant.Y负;
}
}
else if (pe.Y == pi.Y)
{
if (pe.X > pi.X)
{
return Quadrant.X正;
}
else
{
return Quadrant.X负;
}
}
else
{
return Quadrant.原点;
}
}
/// <summary>
/// 单个点的平移
/// </summary>
/// <param name="pBefore">平移之前的点</param>
/// <param name="pStart">起始点坐标</param>
/// <param name="plusOrMinus">正平移或负平移</param>
/// <returns></returns>
private Point Translation(Point pBefore,Point pStart,int plusOrMinus)
{
Point pLater = new Point();
if (plusOrMinus == 0)
{
pLater.X = pBefore.X - pStart.X;
pLater.Y = pBefore.Y - pStart.Y;
}
else
{
pLater.X = pBefore.X + pStart.X;
pLater.Y = pBefore.Y + pStart.Y;
}
return pLater;
}
}
}
三.绘图层
绘制一个四象限坐标系,并且根据输入的极限值自动缩放
根据点的集合绘制插补路径
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Windows.Forms;
using AlgorithmInterpolation;
using System.Threading;
namespace DrawingByGDI
{
public class DrawingCoordinate
{
//整体内缩50像素,边距,边距可以用来写字,先保留,不是没有意义
//定义成静态字段,全局使用
private float move = 30f;
private LinearInterpolation linear = new LinearInterpolation();
List<Point> pList = null;
float multipleX = 0;
float multipleY = 0;
/// <summary>
/// 绘制坐标轴
/// </summary>
/// <param name="panel">画的容器,画布</param>
private void DrawXY(Panel panel)
{
Graphics g = panel.CreateGraphics();
//原点坐标
float newX = panel.Width/2 ;
float newY = panel.Height/2 ;
//坐标轴长度
float lenX = panel.Width - 2 * move;
float lenY = panel.Height - 2 * move;
//绘制X轴 ,注意WinForm的坐标系方向
PointF px1 = new PointF(move, newY);
PointF px2 = new PointF(panel.Width-move, newY);
g.DrawLine(new Pen(Brushes.Black, 1), px1, px2);
//绘制Y轴
PointF py1 = new PointF(newX, move);
PointF py2 = new PointF(newX, panel.Height-move);
g.DrawLine(new Pen(Brushes.Black, 1), py1, py2);
g.DrawRectangle(new Pen(Brushes.Gray, 0.5f), 1, 1, panel.Width-2, panel.Height-2);
}
/// <summary>
/// 绘制X轴的分值线
/// </summary>
/// <param name="panel">容器</param>
/// <param name="maxX">最大值</param>
/// <param name="deivide">单侧几等分</param>
private void DrawXline(Panel panel,float maxValue,int divide)
{
//原点坐标
float newX = panel.Width / 2 ;
float newY = panel.Height / 2 ;
//坐标轴长度
float lenX = panel.Width - 2 * move;
float lenY = panel.Height - 2 * move;
multipleX = lenX / 2 / maxValue;
Graphics g = panel.CreateGraphics();
Pen penG1 = new Pen(Color.Gray, 1);
Pen penB1 = new Pen(Color.Black, 1.5f);
///绘制正x,原点使用另外的方法绘制
for (int i = 1; i<=divide; i++)
{
PointF p1 = new PointF(lenX/2 * i / divide + newX, newY-2);
PointF p2 = new PointF(lenX/2 * i / divide + newX, newY + 2);
g.DrawLine(penG1, p1, p2);
string xValue = (maxValue * i/ divide).ToString();
g.DrawString(xValue, new Font("宋体", 8f), Brushes.Black,
new PointF(lenX/2 / divide * i + newX+2, newY+8));
}
///绘制负x,原点使用另外的方法绘制
for (int i = 1; i <= divide; i++)
{
PointF p1 = new PointF(newX-lenX / 2 / divide * i , newY - 2);
PointF p2 = new PointF(newX - lenX / 2 / divide * i, newY + 2);
g.DrawLine(penG1, p1, p2);
string xValue = (maxValue * (-i) / divide).ToString();
g.DrawString(xValue, new Font("宋体", 8f), Brushes.Black,
new PointF(newX - lenX / 2 / divide * i + 2, newY + 8));
}
//绘制坐标正值箭头
PointF arrows1 = new PointF(move +lenX,newY);
PointF arrows2 = new PointF(move + lenX-15, newY+5);
PointF arrows3 = new PointF(move + lenX-15, newY-5);
g.DrawLine(penB1, arrows1, arrows2);
g.DrawLine(penB1, arrows1, arrows3);
g.DrawString("X", new Font("宋体", 10f), Brushes.Black,
new PointF(move + lenX, newY - 20));
}
/// <summary>
/// 绘制Y轴的分值线
/// </summary>
/// <param name="panel">容器</param>
/// <param name="maxY">最大值</param>
/// <param name="deivide">单侧几等分</param>
private void DrawYline(Panel panel, float maxValue, int divide)
{
//原点坐标
float newX = panel.Width / 2;
float newY = panel.Height / 2;
//坐标轴长度
float lenX = panel.Width - 2 * move;
float lenY = panel.Height - 2 * move;
multipleY = lenY / 2 / maxValue;
Graphics g = panel.CreateGraphics();
Pen penG1 = new Pen(Color.Gray, 1);
Pen penB1 = new Pen(Color.Black, 1.5f);
///绘制负Y,原点使用另外的方法绘制(坐标轴相反)
for (int i = 1; i <= divide; i++)
{
PointF p1 = new PointF(newX-2, lenY/2 / divide * i + newY);
PointF p2 = new PointF(newX+2, lenY/2 / divide * i + newY);
g.DrawLine(penG1, p1, p2);
string yValue = (maxValue * -i / divide).ToString();
g.DrawString(yValue, new Font("宋体", 8f), Brushes.Black,
new PointF(newX + 2, lenY/2 / divide * i + newY+4));
}
///绘制正Y,原点使用另外的方法绘制
for (int i = 1; i <= divide; i++)
{
PointF p1 = new PointF(newX - 2, newY-lenY / 2/ divide * i);
PointF p2 = new PointF(newX + 2, newY-lenY / 2 / divide * i );
g.DrawLine(penG1, p1, p2);
string yValue = (maxValue * i / divide).ToString();
g.DrawString(yValue, new Font("宋体", 8f), Brushes.Black,
new PointF(newX + 2, newY - lenY / 2 / divide * i+4));
}
//绘制坐标正值箭头
PointF arrows1 = new PointF(newX, move);
PointF arrows2 = new PointF(newX-5, move+15);
PointF arrows3 = new PointF(newX+5, move+15);
g.DrawLine(penB1, arrows1, arrows2);
g.DrawLine(penB1, arrows1, arrows3);
g.DrawString("Y", new Font("宋体", 10f), Brushes.Black,
new PointF(newX-20, move));
}
public void DrawCoordinate(Panel panel,float maxValue,int divide)
{
Graphics g = panel.CreateGraphics();
g.Clear(Color.White);
DrawXY(panel);
DrawXline(panel, maxValue, divide);
DrawYline(panel, maxValue, divide);
}
public void DrawPoint(Panel panel, Point pe, Point pi)
{
//原点坐标
float newX = panel.Width / 2;
float newY = panel.Height / 2;
Graphics g = panel.CreateGraphics();
Pen pen = new Pen(Brushes.Blue, 0.5f);
Pen pen2 = new Pen(Brushes.Red,1f);
Pen pen3 = new Pen(Brushes.Blue, 1f);
g.DrawLine(pen2, pe.X * multipleX+ newX, -pe.Y * multipleY + newY,
pi.X * multipleX + newX, -pi.Y * multipleY + newY);
pList = linear.DisEquation(pe, pi);
foreach (Point item in pList)
{
g.DrawEllipse(pen, item.X * multipleX + newX-2, -item.Y * multipleY-2 + newY, 4, 4);
}
for (int i = 0; i <pList.Count-1; i++)
{
g.DrawLine(pen3, pList[i].X * multipleX + newX, -pList[i].Y * multipleY + newY
, pList[i + 1].X * multipleX + newX, -pList[i + 1].Y * multipleY + newY);
Thread.Sleep(300);
}
}
public void DrawClear(Panel panel)
{
Graphics g = panel.CreateGraphics();
g.Clear(Color.White);
}
}
}
本人尚处学习过程,请大佬多多指正。