GDI+画工作流图的一些总结
流程图由节点跟连线组成,先画节点,再根据节点做连线。
1:连线没有可用的控件,而节点可以直接使用Button类充当,使用Panel做画版,拖动button后会自动出现滚动条,且不需要自己做坐标转换。
1.1:不方便的地方在于如果让节点基础Button,那么属性设置面板里会出现一堆的控件属性,需要再定义个装饰类进行包装,
1.2:不继承Button类可以通过让节点包含Button控件来实现,将Button的一些必要属性暴露出去,不过跟继承也差不多
2.节点跟连线都自己画出来(通过Panel.Paint 进行渲染),这个方式最灵活,但是需要自己处理节点的坐标变换,与鼠标事件转发,很多坑,但是可以考虑不添加Panel的滚动条
这样就可以避免很多麻烦,现在的27寸显示器,画个几十个节点也还好。
一些向量点积,叉积,点到线段距离,向量夹角等。。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class Vertex { public double x; public double y; public Vertex(double x, double y) { this.x = x; this.y = y; } public Vertex(Point p) { this.x = p.X; this.y = p.Y; } //扩展计算两点距离方法(点选查询时会用) public double Distance(Vertex another) { //勾股定理求两点距离 return Math.Sqrt(Math.Pow(x - another.x, 2) + Math.Pow(y - another.y, 2)); } } public class Vector { public double X; public double Y; public Vector() { } public Vector(double x, double y) { this.X = x; this.Y = y; } public Vector(PointF p0, PointF p1) { this.X= p1.X - p0.X; this.Y = p1.Y - p0.Y; } public static double Dot(Vector v1, Vector v2) { return v1.X * v2.X + v1.Y * v2.Y; } public static double Cross(Vector v1, Vector v2) { return v1.X * v2.Y - v1.Y * v2.X; } /// <summary> /// 转单位向里 /// </summary> /// <param name="v"></param> /// <param name="fac"></param> /// <returns></returns> public static Vector Unit(Vector v, double fac = 1.0d) { var v1 = new Vector(); var a = Math.Sqrt(Math.Pow(v.X, 2) + Math.Pow(v.Y, 2)); v1.X = (float)((double)v.X * fac / a); v1.Y = (float)((double)v.Y * fac / a); return v1; } } public class Range { public int Area { get; set; } public double L { get; set; } public double H { get; set; } public Range(int area, double l, double h) { Area = area; L = l; H = h; } } public class Algorithm { public static double PointToSegment(Point P, Point A, Point B) { return PointToSegment(new Vertex(P), new Vertex(A), new Vertex(B)); } //点至线段距离 public static double PointToSegment( Vertex P, Vertex A, Vertex B) { if (A.Equals(B)) return P.Distance(A); //一种钝角情况 double dp1 = DotProduct( CalcVector(B, A), CalcVector(B, P)); if (dp1 <= 0) return P.Distance(B); //另一种钝角情况 double dp2 = DotProduct( CalcVector(A, B), CalcVector(A, P)); if (dp2 <= 0) return P.Distance(A); //平行四边形面积/底长度 double signedDistance = CrossProduct(A, B, P) / A.Distance(B); //平行四边形高(垂线高) return Math.Abs(signedDistance); } //叉积 public static double CrossProduct( Vertex p1, Vertex p2, Vertex p3) { Vertex v1 = CalcVector(p1, p2); Vertex v2 = CalcVector(p1, p3); return v1.x * v2.y - v2.x * v1.y; } //计算向量 public static Vertex CalcVector(Vertex start, Vertex end) { return new Vertex( end.x - start.x, end.y - start.y); } public static PointF CalcVector(PointF start, PointF end) { return new PointF( end.X - start.X, end.Y - start.Y); } public static Point CalcVector(Point start, Point end) { return new Point( end.X - start.X, end.Y - start.Y); } /// 点乘 public static double DotProduct(Vertex p1, Vertex p2) { return p1.x * p2.x + p1.y * p2.y; } #region 坐标旋转 /// <summary> /// 给定一线段坐标点 /// 计算p1点两边箭头坐标点 /// 左右按15度角偏转 /// </summary> /// <param name="p0"></param> /// <param name="p1"></param> /// <returns></returns> public static PointF[] CalcArrowPoint(PointF p0, PointF p1) { var v1 = CalcVector(p1, p0); //计算单位向量 var v1_1 = CalcUV(v1, 10.0d);//单位向量放大10个像素 var c=0.9659d;//cos(15) var s=0.2588d; //sin(15) var p_s=new PointF(); p_s.X =(float)( c * v1_1.X - s * v1_1.Y + p1.X); p_s.Y =(float)( s * v1_1.X + c * v1_1.Y + p1.Y); //sin是奇函数要变符号 var p_e = new PointF(); p_e.X = (float)(c * v1_1.X + s * v1_1.Y + p1.X); p_e.Y = (float)(-s * v1_1.X + c * v1_1.Y + p1.Y); return new PointF[2] { p_s, p_e }; } public static PointF TransCoordinate(PointF p,double theta,double x_offset=0,double y_offset=0 ) { #region double[,] a = { {Math.Cos(theta), -Math.Sin(theta) ,0 ,x_offset }, {Math.Sin(theta) , Math.Cos(theta) ,0 ,y_offset }, {0 , 0 ,1 , 0}, {0, 0 ,0 ,1} }; var _M = DenseMatrix.OfArray(a); #endregion var B = DenseVector.OfEnumerable(new double[] { p.X, p.Y, 0,1}); var C = _M.Multiply(B); return new PointF((float)C[0], (float)C[1]); } public static double _theta = Math.PI / 12;//15度角 private static PointF P(int x, int y) { return TransCoordinate(new PointF(x, y), _theta); } public static PointF CalcUV(PointF p,double fac =1.0d) { var p1 = new PointF(); var a =Math.Sqrt( Math.Pow( p.X,2) +Math.Pow( p.Y,2)); p1.X =(float)( (double)p.X * fac/ a); p1.Y =(float)( (double)p.Y *fac / a); return p1; } /// <summary> /// 长方形,右边:0区,上边:1区,左边:2区,下边3区 /// </summary> /// <param name="theta"></param> /// <returns></returns> public static List<Range> GenAngleRanges(double theta) { var list = new List<Range>(); list.Add(new Range(0, 0, theta)); list.Add(new Range(1, theta, Math.PI - theta)); list.Add(new Range(2, Math.PI - theta, Math.PI + theta)); list.Add(new Range(3, Math.PI + theta, Math.PI * 2 - theta)); list.Add(new Range(0, Math.PI * 2 - theta, Math.PI * 2 + 0.1));//最后区域再加一点 return list; } #endregion }
分类等基本结构:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public enum NodeTypes { Start, Task, End } public enum WFObjectType { Link, Start, Task, End } public delegate void LinkSelectedHandler(Link link,EventArgs arg); public delegate void NodeSelectedHandler(Node node,EventArgs arg); public delegate void WFLinkSelectedHandler(WFLink link, EventArgs arg); public delegate void WFNodeSelectedHandler(WFNode node, EventArgs arg); public class PtyChangedEventArgs : EventArgs { public String PropertyName { get; set; } public Object Value { get; set; } public PtyChangedEventArgs() { } public PtyChangedEventArgs(string ptyName,Object v) { PropertyName = ptyName; Value = v; } }
基础类定义:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class WFObject { public static Font C_Font_Tahoma_9 = new Font("Tahoma", 9.0f); public static Font C_Font_Tahoma_8 = new Font("Tahoma", 8.0f); public static Pen _Pen1 = new Pen(Color.Tan) { Width = 1 }; public static Pen _Pen2 = new Pen(Color.Red) { Width = 2 }; #region 按这样的方式声明事件方便子类调用 protected EventHandler<PtyChangedEventArgs> OnObjPtyChanged = null; public event EventHandler<PtyChangedEventArgs> ObjPtyChanged { add { OnObjPtyChanged += value; } remove { OnObjPtyChanged -= value; } } #endregion #region 属性 #region 不可浏览属性 [Browsable(false)] public String Name { get; set; } [Browsable(false)] public WFObjectType ObjectType { get; set; } [Browsable(false)] public bool IsSelected { get; set; } #endregion #region 可浏览属性 private String _text = ""; [Category("Like")] [Description("显示的文本")] public String Text { get { return _text; } set { _text = value; if (OnObjPtyChanged!= null) { OnObjPtyChanged(this, new PtyChangedEventArgs() { PropertyName = "Text" }); } } } #endregion #endregion public virtual void Paint(PaintEventArgs pevent) { } #region Util public static void DrawLineSymbol(Graphics g) { g.DrawLine(Pens.Black, new Point(5, 5), new Point(15, 5)); } public static void DrawPointer(Graphics g) { g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; var pen = new Pen(Color.Black, 3); AdjustableArrowCap aac = new AdjustableArrowCap(4, 3); pen.CustomEndCap = aac;//定义线尾的样式为箭头 var p0 = new Point(12, 20); var p1 = new Point(5, 5); g.DrawLine(pen, p0, p1); } public static void DrawNodeSymbol(Graphics g, WFObjectType objType,PointF offset) { g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; if (objType == WFObjectType.Start) { var path = new GraphicsPath(); path.AddPolygon(new PointF[] { new PointF(offset.X + 5, offset.Y + 5), new PointF(offset.X + 5, offset.Y + 11), new PointF(offset.X + 10, offset.Y + 8) }); // 绘制三角形 g.FillPath(Brushes.Red, path); g.DrawPath(Pens.Red, path); } else if (objType == WFObjectType.End) { var path = new GraphicsPath(); path.AddRectangle(new RectangleF(offset.X+5, offset.Y+5, 5, 5)); // 绘制方块 g.FillPath(Brushes.Black, path); g.DrawPath(Pens.Black, path); } else { var path = new GraphicsPath(); path.AddEllipse(new RectangleF(offset.X + 5, offset.Y + 5, 5, 5)); // 绘制圆 g.FillPath(Brushes.Green, path); g.DrawPath(Pens.Green, path); } } #endregion }
节点类:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.Drawing; using System.ComponentModel; using System.Drawing.Drawing2D; namespace WindowsFormsApplication3 { public class WFNode : WFObject { #region 鼠标事件 // // 摘要: // 在鼠标单击该控件时发生。 [Category("CatAction")] [Description("ControlOnMouseClickDescr")] public event MouseEventHandler MouseClick; public void MouseClickAction(object sender, MouseEventArgs e) { if (MouseClick != null) { MouseClick(this, e); } } // // 摘要: // 当鼠标指针位于控件上并按下鼠标键时发生。 [Category("CatMouse")] [Description("ControlOnMouseDownDescr")] public event MouseEventHandler MouseDown; public void MouseDownAction(object sender, MouseEventArgs e) { if (MouseDown != null) { MouseDown(this, e); } } // // 摘要: // 在鼠标指针移到控件上时发生。 [Description("ControlOnMouseMoveDescr")] [Category("CatMouse")] public event MouseEventHandler MouseMove; public void MouseMoveAction(object sender, MouseEventArgs e) { if (MouseMove != null) { MouseMove(this, e); } } // // 摘要: // 在鼠标指针在控件上并释放鼠标键时发生。 [Description("ControlOnMouseUpDescr")] [Category("CatMouse")] public event MouseEventHandler MouseUp; public void MouseUpAction(object sender, MouseEventArgs e) { if (MouseUp != null) { MouseUp(this, e); } } #endregion [Browsable(false)] public bool MoveFlag { get; set; } [Browsable(false)] public Point MoveClickPoint { get; set; } private Size _size; public Size Size { get { return _size; } set { _size = value; if (OnObjPtyChanged!= null) { OnObjPtyChanged(this, new PtyChangedEventArgs("Size", value)); } } } public int Width { get { return Size.Width; } set { _size.Width = value; if (OnObjPtyChanged != null) { OnObjPtyChanged(this, new PtyChangedEventArgs("Size", value)); } } } public int Height { get { return Size.Height; } set { _size.Height = value; if (OnObjPtyChanged != null) { OnObjPtyChanged(this, new PtyChangedEventArgs("Size", value)); } } } private Point _location; public Point Location { get { return _location; } set { _location = value; if (OnObjPtyChanged != null) { OnObjPtyChanged(this, new PtyChangedEventArgs("Location", value)); } } } private Color _backColor=Color.FromArgb(0xF0,0xF0,0xF0); public Color BackColor { get { return _backColor; } set { _backColor = value; if (OnObjPtyChanged != null) { OnObjPtyChanged(this, new PtyChangedEventArgs("BackColor", value)); } } } public WFNode() { this.Size = new Size(100, 40); ObjectType = WFObjectType.Task; } public override void Paint(PaintEventArgs pevent) { var g = pevent.Graphics; g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; var brush = new SolidBrush(this.BackColor); var clientRect = new Rectangle(Location, Size); g.FillRectangle(brush, clientRect); g.DrawRectangle(Pens.LightGray, clientRect); var outSize = EnlargeSize(Size, 3); if (IsSelected) { var outLoc = Location; outLoc.X = outLoc.X - 3; outLoc.Y = outLoc.Y - 3; g.DrawRectangle(_Pen2, new Rectangle(outLoc, outSize)); } if (!string.IsNullOrWhiteSpace(Text)) { var textSize=g.MeasureString(Text,WFObject.C_Font_Tahoma_8); var p=new PointF(); p.X=Location.X + (Size.Width- textSize.Width)/2; p.Y=Location.Y + (Size.Height - textSize.Height)/2; g.DrawString(Text,WFObject.C_Font_Tahoma_8,Brushes.Black,p); } DrawNodeSymbol(g, ObjectType,(PointF)Location); } public static Size EnlargeSize(Size size, int v) { size.Height = size.Height + v*2; size.Width = size.Width + v*2; return size; } } }
连线类:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.Windows.Forms; using System.ComponentModel; using System.Drawing.Drawing2D; namespace WindowsFormsApplication3 { public class WFLink:WFObject { public Point P0 { get; set; } public Point P1 { get; set; } [TypeConverter(typeof(ExpandableObjectConverter))] [EditorBrowsable(EditorBrowsableState.Always)] public WFNode P0Node { get; set; } [TypeConverter(typeof(ExpandableObjectConverter))] [EditorBrowsable(EditorBrowsableState.Always)] public WFNode P1Node { get; set; } [Browsable(false)] public string Code { get { if (string.Compare(P0Node.Name, P1Node.Name) > 0) { return P0Node.Name + "_" + P1Node.Name; } else { return P1Node.Name + "_" + P0Node.Name; } } } public override void Paint(PaintEventArgs pevent) { base.Paint(pevent); var g = pevent.Graphics; var line = this; var p0 = line.P0Node.Location; var p1 = line.P1Node.Location; #region 方案1 WFDocDesign.AdjLineLink(line, ref p0, ref p1); g.DrawLine(line.IsSelected ? WFObject._Pen2 : WFObject._Pen1, p0, p1); var arrowPoints = Algorithm.CalcArrowPoint(p0, p1); g.DrawLine(line.IsSelected ? WFObject._Pen2 : WFObject._Pen1, arrowPoints[0], p1); g.DrawLine(line.IsSelected ? WFObject._Pen2 : WFObject._Pen1, arrowPoints[1], p1); #endregion #region 连线文字 if (!string.IsNullOrWhiteSpace(line.Text)) { var lambda = 0.7f; var pm = new PointF(0, 0); var v_Link_s = Vector.Unit(new Vector(p0, p1)); var v_base = new Vector(1, 0); var theta_r = Math.Acos(Vector.Dot(v_base, v_Link_s)); var theta_d = theta_r * 180 / Math.PI; if (Vector.Cross(v_base, v_Link_s) < 0.0d) { theta_d = (360 - theta_d); } Console.WriteLine(theta_d); #region 旋转角度 var state = g.Save(); #endregion if (theta_d > 100 && theta_d <= 250) { pm = new PointF(((float)p1.X * lambda + (float)p0.X * (1 - lambda)), ((float)p1.Y * lambda + (float)p0.Y * (1 - lambda))); Matrix matrix = g.Transform; matrix.RotateAt((float)theta_d + 180, pm); g.Transform = matrix; } else { pm = new PointF(((float)p0.X * lambda + (float)p1.X * (1 - lambda)), ((float)p0.Y * lambda + (float)p1.Y * (1 - lambda))); Matrix matrix = g.Transform; matrix.RotateAt((float)theta_d, pm); g.Transform = matrix; } g.DrawString(line.Text, WFObject.C_Font_Tahoma_9, Brushes.Black, pm); g.Restore(state); } #endregion } } }
文档类:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.Drawing; using System.Drawing.Drawing2D; namespace WindowsFormsApplication3 { public class WFDocDesign { public event WFLinkSelectedHandler LinkSelected; public event WFNodeSelectedHandler NodeSelected; public List<WFNode> _NodeList = new List<WFNode>(); public List<WFLink> _LinkList = new List<WFLink>(); public List<WFNode> _DrawingLinkNodes = new List<WFNode>(); public static List<Range> _AngleRanges = new List<Range>(); public System.Windows.Forms.Cursor Start_Cursor; public System.Windows.Forms.Cursor Task_Cursor; public System.Windows.Forms.Cursor End_Cursor; public System.Windows.Forms.Cursor Line_Cursor; public Form MainForm { get; set; } //最后选择节点 public WFNode _LastSelectedNode = null; public Panel _Panel = null; public WFNode SelectedNode { get { return _LastSelectedNode; } } public WFLink SelectedLink { get { return _LinkList.FirstOrDefault(ent => ent.IsSelected == true); } } static WFDocDesign() { #region 划分区间 { var node = new WFNode(); var p_s = Algorithm.CalcUV(new PointF(node.Width / 2, 0));//水平向量 var p_x = Algorithm.CalcUV(new PointF(node.Width / 2, node.Height / 2)); //右上半对角线 var theta = Math.Acos(p_s.X * p_x.X + p_s.Y * p_x.Y); WFDocDesign._AngleRanges = Algorithm.GenAngleRanges(theta); } #endregion } public WFDocDesign(Panel panel) { #region 加载光标文件 this.Start_Cursor = new Cursor(GetType(), "Cursors.Start.cur"); this.Task_Cursor = new Cursor(GetType(), "Cursors.Rectangle.cur"); this.End_Cursor = new Cursor(GetType(), "Cursors.End.cur"); this.Line_Cursor = new Cursor(GetType(), "Cursors.Line.cur"); #endregion _Panel = panel; #region 终结点 //var anchor = new UserControl() { Text = "终结点" }; //anchor.Enabled = false; //anchor.Location = new Point(2000, 1500); //_Panel.Controls.Add(anchor); #endregion _Panel.Paint += _Paint; _Panel.MouseClick += _MouseClick; _Panel.MouseMove += _MouseMove; _Panel.MouseDown += _MouseDown; _Panel.MouseUp +=_MouseUp; _Panel.Scroll += (s, e) => { }; } private Point TransPoint(Point p) { p.X = p.X + _Panel.AutoScrollPosition.X; p.Y = p.Y + _Panel.AutoScrollPosition.Y; return p; } private WFNode TryGetEventTarget(MouseEventArgs e) { foreach (var node in _NodeList) { var loc = TransPoint(node.Location); var rect = new Rectangle(loc, node.Size); if (rect.Contains(e.Location)) { return node; } } return null; } private void _MouseUp(object sender, MouseEventArgs e) { _FlyAway = null; var node = TryGetEventTarget(e); if (node != null) { var ex = TransEventArgs(e, node); node.MouseUpAction(this, ex); return; } } private MouseEventArgs TransEventArgs(MouseEventArgs e, WFNode node) { //var ex = new MouseEventArgs(e.Button, e.Clicks, e.Location.X - node.Location.X, e.Location.Y - node.Location.Y, e.Delta); var ex = new MouseEventArgs(e.Button, e.Clicks, e.Location.X - node.Location.X + _Panel.AutoScrollPosition.X, e.Location.Y - node.Location.Y + _Panel.AutoScrollPosition.Y, e.Delta); return ex; } /// <summary> /// 快速移动鼠标时会出现这样的情况 /// 鼠标已经离开Node的区域了 /// </summary> private WFNode _FlyAway = null; private void _MouseDown(object sender, MouseEventArgs e) { var node = TryGetEventTarget(e); if (node != null) { var ex = TransEventArgs(e, node); node.MouseDownAction(this, ex); _FlyAway = node; return; } } private void _MouseMove(object sender, MouseEventArgs e) { var node = TryGetEventTarget(e); if (node == null) { node = _FlyAway; } if (node != null) { var ex = TransEventArgs(e, node); node.MouseMoveAction(this, ex); return; } if (HasLineDrawing()) { _Panel.Invalidate(); } } private void _MouseClick(object sender, MouseEventArgs e) { #region 绘制面版事件处理 Console.WriteLine("_Panel.MouseClick" + e.Location); #region 识别 Node的_MouseClick事件 { var node = TryGetEventTarget(e); if (node != null) { var ex = TransEventArgs(e, node); node.MouseClickAction(this, ex); return; } } #endregion try { #region 左键单击,并且光标是[Start,Task,End,Line] if (e.Button == System.Windows.Forms.MouseButtons.Left) { if (_Panel.Cursor == Task_Cursor) { var node = NewNode(WFObjectType.Task); node.Location =TransPoint( e.Location); _Panel.Invalidate(); return; } else if (_Panel.Cursor == Start_Cursor) { var node = NewNode(WFObjectType.Start); node.Location =TransPoint( e.Location); _Panel.Cursor = Cursors.Default; _Panel.Invalidate(); return; } else if (_Panel.Cursor == End_Cursor) { var node = NewNode(WFObjectType.End); node.Location =TransPoint( e.Location); _Panel.Cursor = Cursors.Default; _Panel.Invalidate(); return; } else { } } #endregion #region 点击选中连线 var thisTimeSelected = false; if (e.Button == System.Windows.Forms.MouseButtons.Right || e.Button == System.Windows.Forms.MouseButtons.Left) { var p = _Panel.PointToClient(Cursor.Position); foreach (var line in _LinkList) { var p0 = line.P0Node.Location; var p1 = line.P1Node.Location; AdjLineLink(line, ref p0, ref p1); var dist = Algorithm.PointToSegment(p, p0, p1); Console.WriteLine("dist:" + dist); if (dist <= 5.0) { _NodeList.ForEach(ent => ent.IsSelected = false); _LinkList.ForEach(ent => ent.IsSelected = false); thisTimeSelected = true; line.IsSelected = true; if (LinkSelected != null) { LinkSelected(line, null); _Panel.Invalidate(); } break; } } _Panel.Invalidate(); } #endregion #region 右键菜单 if (e.Button == System.Windows.Forms.MouseButtons.Right) { var line = _LinkList.FirstOrDefault(ent => ent.IsSelected); if (line != null) { ShowLinkContextMenu(); return; } } #endregion #region 左右点击空白区域取消画连线与取消选择 if (thisTimeSelected) return; if (e.Button == System.Windows.Forms.MouseButtons.Right || e.Button==MouseButtons.Left) { if (HasLineDrawing()) { _DrawingLinkNodes.Clear(); _Panel.Invalidate(); return; } else { //取消当前操作 CancelCurrentOpt(); } } #endregion } catch (Exception ex) { ErrMsg(ex.Message); } #endregion } private void _Paint(object sender, PaintEventArgs e) { var g = e.Graphics; g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; g.TranslateTransform(_Panel.AutoScrollPosition.X, _Panel.AutoScrollPosition.Y); #region 画方块 foreach (var node in _NodeList) { node.Paint(e); } #endregion #region 画连接中的线 if (HasLineDrawing()) { var pen = new Pen(Color.Tan); AdjustableArrowCap aac = new AdjustableArrowCap(2, 5); pen.CustomEndCap = aac;//定义线尾的样式为箭头 var node0 = _DrawingLinkNodes[0]; var p0 = node0.Location; p0.X = p0.X + node0.Width / 2; p0.Y = p0.Y + node0.Height / 2; var p1 = _Panel.PointToClient(Cursor.Position); Console.WriteLine("{0},{1}", p0, p1); g.DrawLine(pen, p0, p1); } #endregion #region 画已连接的线 foreach (var link in _LinkList) { link.Paint(e); } #endregion } public bool HasLineDrawing() { return (_DrawingLinkNodes.Count == 1 && GetPanelCursor() == Line_Cursor); } public Cursor GetPanelCursor() { return _Panel.Cursor; } public void SetPanelCursor(System.Windows.Forms.Cursor cursor) { _Panel.Cursor = cursor; _Panel.Invalidate(); } public Cursor GetCursorByObjectType(WFObjectType oType) { if (oType == WFObjectType.Start) { return Start_Cursor; } else if (oType == WFObjectType.Task) { return Task_Cursor; } else if (oType == WFObjectType.End) { return End_Cursor; } return Cursors.Default; } public static void AdjLineLink(WFLink line, ref Point p0, ref Point p1) { //方块中点对中点 p0.X = p0.X + line.P0Node.Width / 2; p0.Y = p0.Y + line.P0Node.Height / 2; p1.X = p1.X + line.P1Node.Width / 2; p1.Y = p1.Y + line.P1Node.Height / 2; var v_Link_s = Vector.Unit(new Vector(p0, p1)); v_Link_s.Y = -v_Link_s.Y; //需要乘以一个绕x轴旋转180度的变换矩阵,相当于y分量取负 var v_base = new Vector(1, 0); var v_Link_t = Vector.Unit(new Vector(p1, p0)); v_Link_t.Y = -v_Link_t.Y; var theta_s = Math.Acos(Vector.Dot(v_base, v_Link_s)); var theta_t = Math.Acos(Vector.Dot(v_base, v_Link_t)); #region 源头端调整 { if (Vector.Cross(v_base, v_Link_s) < 0.0d) { theta_s = Math.PI * 2 - theta_s; } var area = 3; var fIt = _AngleRanges.FirstOrDefault(ent => theta_s >= ent.L && theta_s < ent.H); if (fIt != null) { area = fIt.Area; } if (area == 0) { p0.X = line.P0Node.Location.X + line.P0Node.Width; p0.Y = line.P0Node.Location.Y + line.P0Node.Height / 2; } else if (area == 1) { p0.X = line.P0Node.Location.X + line.P0Node.Width / 2; ; p0.Y = line.P0Node.Location.Y; } else if (area == 2) { p0.X = line.P0Node.Location.X; p0.Y = line.P0Node.Location.Y + line.P0Node.Height / 2; } else if (area == 3) { p0.X = line.P0Node.Location.X + line.P0Node.Width / 2; p0.Y = line.P0Node.Location.Y + line.P0Node.Height; } } #endregion #region 目标端调整 { if (Vector.Cross(v_base, v_Link_t) < 0.0d) { theta_t = Math.PI * 2 - theta_t; } var area = 3; var fIt = _AngleRanges.FirstOrDefault(ent => theta_t >= ent.L && theta_t < ent.H); if (fIt != null) { area = fIt.Area; } if (area == 0) { p1.X = line.P1Node.Location.X + line.P1Node.Width; p1.Y = line.P1Node.Location.Y + line.P1Node.Height / 2; } else if (area == 1) { p1.X = line.P1Node.Location.X + line.P1Node.Width / 2; ; p1.Y = line.P1Node.Location.Y; } else if (area == 2) { p1.X = line.P1Node.Location.X; p1.Y = line.P1Node.Location.Y + line.P1Node.Height / 2; } else if (area == 3) { p1.X = line.P1Node.Location.X + line.P1Node.Width / 2; p1.Y = line.P1Node.Location.Y + line.P1Node.Height; } } #endregion } public void BeginDrawStartNode() { if (_NodeList.Any(ent => ent.ObjectType == WFObjectType.Start)) throw new Exception("开始节点只允许添加一次"); SetPanelCursor(GetCursorByObjectType(WFObjectType.Start)); } public void BeginDrawEndNode() { if (_NodeList.Any(ent => ent.ObjectType == WFObjectType.End)) throw new Exception("结束节点只允许添加一次"); SetPanelCursor(GetCursorByObjectType(WFObjectType.End)); } public void BeginDrawTaskNode() { SetPanelCursor(GetCursorByObjectType(WFObjectType.Task)); } public void BeginDrawLink() { _DrawingLinkNodes.Clear(); SetPanelCursor(Line_Cursor); } public void RemoveLink(WFLink link) { _LinkList.Remove(link); _Panel.Invalidate(); } public void RemoveNode(WFNode selectedNode) { var lines4Remove = new List<WFLink>(); foreach (var line in _LinkList) { if (line.P0Node.Equals(selectedNode) || line.P1Node.Equals(selectedNode)) { lines4Remove.Add(line); } } foreach (var line in lines4Remove) { _LinkList.Remove(line); } _NodeList.Remove(selectedNode); _Panel.Invalidate(); } public void CancelCurrentOpt() { _DrawingLinkNodes.Clear(); //需要其他处理 _NodeList.ForEach(ent => ent.IsSelected = false); _LinkList.ForEach(ent => ent.IsSelected = false); SetPanelCursor(Cursors.Default); _Panel.Invalidate(); } public WFNode NewNode(WFObjectType oType = WFObjectType.Task) { if (oType != WFObjectType.Task) { if (_NodeList.Any(ent => ent.ObjectType == oType)) throw new Exception("开始与结束节点只允许添加一次"); } var node = new WFNode() { Name = "node_" + DateTime.Now.Ticks }; node.ObjPtyChanged += (s,e) => { _Panel.Invalidate(); }; node.MouseClick += (s, e) => { #region 无论左右都会选中 { _NodeList.ForEach(ent => ent.IsSelected = false); _LinkList.ForEach(ent => ent.IsSelected = false); node.IsSelected = true; if (NodeSelected != null) { NodeSelected(node, null); _Panel.Invalidate(); } } #endregion if (GetPanelCursor() == Line_Cursor && e.Button == System.Windows.Forms.MouseButtons.Left) { try { #region 画连线 if (_DrawingLinkNodes.Any(ent => ent.Equals(node))) return; if (_DrawingLinkNodes.Count == 0 && node.ObjectType == WFObjectType.End) { throw new Exception("结束节点不能有连出现"); } _DrawingLinkNodes.Add(node); if (_DrawingLinkNodes.Count >= 2) { if (_DrawingLinkNodes[1].ObjectType == WFObjectType.Start) { _DrawingLinkNodes.Remove(_DrawingLinkNodes[1]); throw new Exception("不能指向开始节点"); } var line = new WFLink() { P0Node = _DrawingLinkNodes[0], P1Node = _DrawingLinkNodes[1] }; if (_LinkList.Any(ent => ent.Code == line.Code)) { Console.WriteLine("两个节点已经有连线了!"); _DrawingLinkNodes.Clear(); _Panel.Invalidate(); return; } line.Name = line.P0Node.Name + "--" + line.P1Node.Name; line.IsSelected = false; line.ObjPtyChanged += (sx, ex) => { _Panel.Invalidate(); }; _LinkList.Add(line); _Panel.Invalidate(); Console.WriteLine("加入列线条:" + line.Name); // checkBox1.Checked = false; _DrawingLinkNodes.Clear(); } #endregion } catch (Exception ex) { ErrMsg(ex.Message); _Panel.Invalidate(); } return; } //Console.WriteLine(node.Name + "be Clicked"); }; node.MouseDown += (s, e) => { if (e.Button == System.Windows.Forms.MouseButtons.Left && GetPanelCursor() == Cursors.Default) { Console.WriteLine(node.Name + "MouseDown"); node.MoveFlag = true; node.MoveClickPoint = e.Location; } }; node.MouseMove += (s, e) => { Console.WriteLine("Node.MouseMove" + e.Location); if (e.Button == System.Windows.Forms.MouseButtons.Left && GetPanelCursor() == Cursors.Default) { if (node.MoveFlag) { var curNode = s as WFNode; var point4Parent = _Panel.PointToClient(Cursor.Position); point4Parent.X = point4Parent.X - node.MoveClickPoint.X; point4Parent.Y = point4Parent.Y - node.MoveClickPoint.Y; if (point4Parent.X < -5) point4Parent.X = -5; if (point4Parent.Y < -5) point4Parent.Y = -5; if (point4Parent.X > (_Panel.Width-node.Width)) point4Parent.X = _Panel.Width - node.Width; if (point4Parent.Y > (_Panel.Height-node.Height)) point4Parent.Y = _Panel.Height - node.Height; curNode.Location =TransPoint( point4Parent); _Panel.Invalidate(); } } }; node.MouseUp += (s, e) => { var sNode = s as WFNode; if (e.Button == System.Windows.Forms.MouseButtons.Right && sNode.ObjectType == WFObjectType.Task) { _LastSelectedNode = sNode; ShowNodeContextMenu(); return; } if (e.Button == System.Windows.Forms.MouseButtons.Left && GetPanelCursor() == Cursors.Default) { node.MoveFlag = false; } }; node.Location = new Point(-100, -100); node.ObjectType = oType; if (oType == WFObjectType.Task) node.Text = "任务节点" + _NodeList.Count; if (oType == WFObjectType.Start) node.Text = "开始"; if (oType == WFObjectType.End) node.Text = "结束"; _NodeList.Add(node); return node; } #region Helper public void ErrMsg(string errStr) { dynamic dy = MainForm; if (dy!=null) { dy.ErrMsg(errStr); } } public void ShowNodeContextMenu() { dynamic dy = MainForm; if (dy!=null) { dy.ShowNodeContextMenu(); } } public void ShowLinkContextMenu() { dynamic dy = MainForm; if (dy!=null) { dy.ShowLinkContextMenu(); } } #endregion } }
另外自己继承个Panel开启一下双缓冲设置
Form应用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public partial class Form3 : Form { public WFDocDesign _Doc=null; public Form3() { InitializeComponent(); _Doc = new WFDocDesign(panel1); _Doc.MainForm = this; #region 按钮标志 { btnStartNode.Paint += (s, e) => { WFObject.DrawNodeSymbol(e.Graphics, WFObjectType.Start,new PointF()); }; btnEndNode.Paint += (s, e) => { WFObject.DrawNodeSymbol(e.Graphics, WFObjectType.End, new PointF()); }; btnTaskNode.Paint += (s, e) => { WFObject.DrawNodeSymbol(e.Graphics, WFObjectType.Task, new PointF()); }; btnLine.Paint += (s, e) => { WFObject.DrawLineSymbol(e.Graphics); }; btnPointer.Paint += (s, e) => { WFObject.DrawPointer(e.Graphics); }; } #endregion #region 对象选中事件处理 _Doc.LinkSelected += (line, e) => { propertyGrid1.SelectedObject = line; }; _Doc.NodeSelected += (node, e) => { propertyGrid1.SelectedObject = node; }; #endregion #region 弹出菜单 barButtonItem1.ItemClick += (s, e) => { try { var selectedNode = _Doc.SelectedNode; if (selectedNode == null) return; if (MessageBox.Show("确认删除选中节点吗?", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) != System.Windows.Forms.DialogResult.OK) return; _Doc.RemoveNode(selectedNode); } catch (Exception ex) { ErrMsg(ex.Message); } }; barButtonItem2.ItemClick += (s, e) => { try { var link = _Doc.SelectedLink; if (link == null) return; if (MessageBox.Show("确认删除选中连线吗?", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) != System.Windows.Forms.DialogResult.OK) return; _Doc.RemoveLink(link); } catch (Exception ex) { ErrMsg(ex.Message); } }; #endregion } private void Form1_Load(object sender, EventArgs e) { } private void btnStartNode_Click(object sender, EventArgs e) { try { _Doc.BeginDrawStartNode(); } catch (Exception ex) { ErrMsg(ex.Message); } } private void btnEndNode_Click(object sender, EventArgs e) { try { _Doc.BeginDrawEndNode(); } catch (Exception ex) { ErrMsg(ex.Message); } } private void btnTaskNode_Click(object sender, EventArgs e) { try { _Doc.BeginDrawTaskNode(); } catch (Exception ex) { ErrMsg(ex.Message); } } private void btnLine_Click(object sender, EventArgs e) { try { _Doc.BeginDrawLink(); } catch (Exception ex) { ErrMsg(ex.Message); } } private void btnPointer_Click(object sender, EventArgs e) { _Doc.CancelCurrentOpt(); } public void ErrMsg(string errStr) { MessageBox.Show(errStr, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } public void ShowNodeContextMenu() { popupMenu1.ShowPopup(System.Windows.Forms.Control.MousePosition); } public void ShowLinkContextMenu() { popupMenu2.ShowPopup(System.Windows.Forms.Control.MousePosition); } }