这是一个功能完整的 C# WinForm 流程图绘制程序,核心特性如下:
- 节点管理:支持 4 种节点类型(开始 / 结束椭圆、文件矩形、分支菱形、流程平行四边形),可拖拽移动、右键添加 / 删除(禁止删除开始 / 结束节点),部分节点带红色叉号标识。
- 连线功能:Shift + 左键绘制节点间折线连线(自动计算边缘锚点),支持连线命中检测、悬停 / 选中高亮(变红变粗),右键可删除单条连线,删除节点时自动清理关联连线。
- 绘制优化:采用双缓冲 Panel 解决闪烁,抗锯齿绘制保证线条平滑,折线连线符合流程图规范。
- 交互体验:节点拖拽无偏移,连线预览清晰,右键菜单区分节点 / 连线 / 空白处,操作逻辑健壮。
整体实现了流程图的可视化绘制、交互编辑核心能力,代码结构清晰,交互细节完善。
using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Windows.Forms; namespace FlowChartWinForm { // 节点类型枚举 public enum NodeType { StartEnd, // 开始/结束(椭圆) File, // 文件节点(矩形+图标) Branch, // 分支/汇合(菱形) Process // 流程节点(平行四边形) } // 流程图节点类(封装属性) public class FlowNode { public Guid Id { get; set; } = Guid.NewGuid(); // 唯一标识 public NodeType Type { get; set; } // 节点类型 public string Text { get; set; } // 节点文本 public Rectangle Bounds { get; set; } // 节点边界(用于绘制和碰撞检测) public Point[] CustomShapePoints { get; set; } // 自定义形状的顶点 public bool HasCross { get; set; } = false; // 是否显示红色叉号 public bool IsDragging { get; set; } = false; // 是否正在拖拽 public Point DragOffset { get; set; } // 拖拽偏移量 // 获取节点中心点(用于连线锚点) public Point GetCenterPoint() { return new Point( Bounds.X + Bounds.Width / 2, Bounds.Y + Bounds.Height / 2); } // 获取节点边缘锚点(根据目标位置自动选择最近边缘) public Point GetEdgeAnchorPoint(Point targetPoint) { Point center = GetCenterPoint(); int offsetX = targetPoint.X - center.X; int offsetY = targetPoint.Y - center.Y; // 优先水平/垂直方向 if (Math.Abs(offsetX) > Math.Abs(offsetY)) { return offsetX > 0 ? new Point(Bounds.Right, center.Y) // 右边缘 : new Point(Bounds.Left, center.Y); // 左边缘 } else { return offsetY > 0 ? new Point(center.X, Bounds.Bottom) // 下边缘 : new Point(center.X, Bounds.Top); // 上边缘 } } } // 连线类(存储连线信息) public class FlowConnection { public Guid Id { get; set; } = Guid.NewGuid(); public FlowNode StartNode { get; set; } // 起点节点 public FlowNode EndNode { get; set; } // 终点节点 public Pen Pen { get; set; } = new Pen(Color.Black, 1); // 连线样式 public bool IsYellow { get; set; } = false; // 是否黄色高亮 // 存储连线的折线点(用于命中检测) public List<Point> LinePoints { get; set; } = new List<Point>(); } // 自定义双缓冲Panel(解决CS1540错误) public class DoubleBufferedPanel : Panel { public DoubleBufferedPanel() { this.DoubleBuffered = true; this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true); } } public class FlowChartForm : Form { // 控件声明 private DoubleBufferedPanel panelChart; private ContextMenuStrip nodeContextMenu; private ContextMenuStrip connectionContextMenu; // 连线专属右键菜单 // 数据存储 private List<FlowNode> _nodes = new List<FlowNode>(); private List<FlowConnection> _connections = new List<FlowConnection>(); // 自定义连线集合 // 状态管理 private FlowNode _selectedNode = null; private FlowConnection _selectedConnection = null; // 选中的连线 private FlowConnection _hoveredConnection = null; // 悬停的连线 private Point _rightClickPos; // 连线绘制状态 private bool _isDrawingConnection = false; private FlowNode _connectionStartNode = null; private Point _tempConnectionEndPoint; public FlowChartForm() { // 初始化窗体 this.Text = "流程图(支持拖拽节点+自定义连线+删除单条连线)"; this.Size = new Size(1000, 700); this.KeyPreview = true; // 捕获键盘事件 // 1. 创建自定义Panel panelChart = new DoubleBufferedPanel(); panelChart.Dock = DockStyle.Fill; this.Controls.Add(panelChart); // 2. 创建右键菜单 InitContextMenus(); // 3. 绑定事件 panelChart.Paint += PanelChart_Paint; panelChart.MouseDown += PanelChart_MouseDown; panelChart.MouseMove += PanelChart_MouseMove; panelChart.MouseUp += PanelChart_MouseUp; panelChart.MouseClick += PanelChart_MouseClick; panelChart.MouseHover += PanelChart_MouseHover; // 鼠标悬停检测 this.KeyDown += FlowChartForm_KeyDown; this.KeyUp += FlowChartForm_KeyUp; // 4. 初始化默认节点和连线 InitDefaultNodes(); InitDefaultConnections(); } #region 初始化相关 // 初始化右键菜单(节点+连线) private void InitContextMenus() { // 1. 节点右键菜单 nodeContextMenu = new ContextMenuStrip(); var deleteNodeItem = new ToolStripMenuItem("删除节点"); deleteNodeItem.Click += DeleteNode_Click; nodeContextMenu.Items.Add(deleteNodeItem); nodeContextMenu.Items.Add(new ToolStripSeparator()); var addFileItem = new ToolStripMenuItem("添加文件节点"); addFileItem.Click += AddFileNode_Click; nodeContextMenu.Items.Add(addFileItem); var addBranchItem = new ToolStripMenuItem("添加分支节点"); addBranchItem.Click += AddBranchNode_Click; nodeContextMenu.Items.Add(addBranchItem); var addProcessItem = new ToolStripMenuItem("添加流程节点"); addProcessItem.Click += AddProcessNode_Click; nodeContextMenu.Items.Add(addProcessItem); // 2. 连线右键菜单 connectionContextMenu = new ContextMenuStrip(); var deleteConnItem = new ToolStripMenuItem("删除连线"); deleteConnItem.Click += DeleteConnection_Click; connectionContextMenu.Items.Add(deleteConnItem); } // 初始化默认节点 private void InitDefaultNodes() { // 开始节点 _nodes.Add(new FlowNode { Type = NodeType.StartEnd, Text = "开始", Bounds = new Rectangle(150, 20, 100, 40) }); // 图像文件.001 _nodes.Add(new FlowNode { Type = NodeType.File, Text = "图像文件.001", Bounds = new Rectangle(120, 80, 160, 40) }); // 分支节点 var branchNode = new FlowNode { Type = NodeType.Branch, Text = "分支", Bounds = new Rectangle(140, 140, 120, 60), HasCross = true, CustomShapePoints = GetBranchShapePoints(new FlowNode { Bounds = new Rectangle(140, 140, 120, 60) }) }; _nodes.Add(branchNode); // 图像文件.011 _nodes.Add(new FlowNode { Type = NodeType.File, Text = "图像文件.011", Bounds = new Rectangle(120, 220, 160, 40) }); // 图像文件.015 _nodes.Add(new FlowNode { Type = NodeType.File, Text = "图像文件.015", Bounds = new Rectangle(120, 280, 160, 40) }); // 图像文件.016 _nodes.Add(new FlowNode { Type = NodeType.File, Text = "图像文件.016", Bounds = new Rectangle(120, 340, 160, 40) }); // 分支汇合 var mergeNode = new FlowNode { Type = NodeType.Branch, Text = "分支汇合", Bounds = new Rectangle(140, 400, 120, 60), CustomShapePoints = GetBranchShapePoints(new FlowNode { Bounds = new Rectangle(140, 400, 120, 60) }) }; _nodes.Add(mergeNode); // 结束节点 _nodes.Add(new FlowNode { Type = NodeType.StartEnd, Text = "结束", Bounds = new Rectangle(150, 480, 100, 40) }); // 分支分支分支 var processNode = new FlowNode { Type = NodeType.Process, Text = "分支分支分支", Bounds = new Rectangle(350, 230, 100, 60), HasCross = true, CustomShapePoints = GetProcessShapePoints(new FlowNode { Bounds = new Rectangle(350, 230, 100, 60) }) }; _nodes.Add(processNode); } // 初始化默认连线(迁移到自定义连线集合) private void InitDefaultConnections() { var startNode = _nodes.Find(n => n.Text == "开始"); var file001Node = _nodes.Find(n => n.Text == "图像文件.001"); var branchNode = _nodes.Find(n => n.Text == "分支"); var file011Node = _nodes.Find(n => n.Text == "图像文件.011"); var file015Node = _nodes.Find(n => n.Text == "图像文件.015"); var file016Node = _nodes.Find(n => n.Text == "图像文件.016"); var mergeNode = _nodes.Find(n => n.Text == "分支汇合"); var endNode = _nodes.Find(n => n.Text == "结束"); var processNode = _nodes.Find(n => n.Text == "分支分支分支"); // 添加默认连线 if (startNode != null && file001Node != null) _connections.Add(new FlowConnection { StartNode = startNode, EndNode = file001Node }); if (file001Node != null && branchNode != null) _connections.Add(new FlowConnection { StartNode = file001Node, EndNode = branchNode }); if (branchNode != null && file011Node != null) _connections.Add(new FlowConnection { StartNode = branchNode, EndNode = file011Node }); if (file011Node != null && file015Node != null) _connections.Add(new FlowConnection { StartNode = file011Node, EndNode = file015Node }); if (file015Node != null && file016Node != null) _connections.Add(new FlowConnection { StartNode = file015Node, EndNode = file016Node }); if (file016Node != null && mergeNode != null) _connections.Add(new FlowConnection { StartNode = file016Node, EndNode = mergeNode }); if (mergeNode != null && endNode != null) _connections.Add(new FlowConnection { StartNode = mergeNode, EndNode = endNode }); // 分支→分支分支分支 if (branchNode != null && processNode != null) _connections.Add(new FlowConnection { StartNode = branchNode, EndNode = processNode }); // 分支分支分支→分支汇合(黄色线) if (processNode != null && mergeNode != null) _connections.Add(new FlowConnection { StartNode = processNode, EndNode = mergeNode, IsYellow = true, Pen = new Pen(Color.Orange, 2) }); } #endregion #region 绘制逻辑 private void PanelChart_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; Font nodeFont = new Font("微软雅黑", 9); // 1. 绘制所有节点 foreach (var node in _nodes) { switch (node.Type) { case NodeType.StartEnd: // 椭圆 g.FillEllipse(Brushes.LightGray, node.Bounds); g.DrawEllipse(Pens.Black, node.Bounds); g.DrawString(node.Text, nodeFont, Brushes.Black, node.Bounds, new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }); break; case NodeType.File: // 文件节点 g.FillRectangle(Brushes.White, node.Bounds); g.DrawRectangle(Pens.Black, node.Bounds); g.FillRectangle(Brushes.Red, node.Bounds.X + 5, node.Bounds.Y + 10, 15, 20); g.DrawString(node.Text, nodeFont, Brushes.Black, node.Bounds.X + 30, node.Bounds.Y + 12); break; case NodeType.Branch: // 菱形 var branchPoints = node.CustomShapePoints ?? GetBranchShapePoints(node); g.FillPolygon(Brushes.White, branchPoints); g.DrawPolygon(Pens.Black, branchPoints); g.DrawString(node.Text, nodeFont, Brushes.Black, node.Bounds.X + 10, node.Bounds.Y + 15); if (node.HasCross) { g.DrawLine(Pens.Red, node.Bounds.Right - 20, node.Bounds.Top + 10, node.Bounds.Right - 10, node.Bounds.Top + 30); g.DrawLine(Pens.Red, node.Bounds.Right - 20, node.Bounds.Top + 30, node.Bounds.Right - 10, node.Bounds.Top + 10); } break; case NodeType.Process: // 平行四边形 var processPoints = node.CustomShapePoints ?? GetProcessShapePoints(node); g.FillPolygon(Brushes.White, processPoints); g.DrawPolygon(Pens.Black, processPoints); g.DrawString(node.Text, nodeFont, Brushes.Black, node.Bounds.X + 10, node.Bounds.Y + 15); if (node.HasCross) { g.DrawLine(Pens.Red, node.Bounds.Right - 20, node.Bounds.Top + 10, node.Bounds.Right - 10, node.Bounds.Top + 30); g.DrawLine(Pens.Red, node.Bounds.Right - 20, node.Bounds.Top + 30, node.Bounds.Right - 10, node.Bounds.Top + 10); } break; } } // 2. 绘制所有已保存的连线 DrawAllConnections(g); // 3. 绘制临时连线(拖拽中) if (_isDrawingConnection && _connectionStartNode != null) { Point startAnchor = _connectionStartNode.GetEdgeAnchorPoint(_tempConnectionEndPoint); g.DrawLine(Pens.Black, startAnchor, _tempConnectionEndPoint); // 绘制终点预览圆点 g.FillEllipse(Brushes.Red, _tempConnectionEndPoint.X - 3, _tempConnectionEndPoint.Y - 3, 6, 6); } } // 绘制所有已保存的连线(包含高亮逻辑) private void DrawAllConnections(Graphics g) { foreach (var conn in _connections) { if (conn.StartNode == null || conn.EndNode == null) continue; // 获取起点和终点的边缘锚点 Point startAnchor = conn.StartNode.GetEdgeAnchorPoint(conn.EndNode.GetCenterPoint()); Point endAnchor = conn.EndNode.GetEdgeAnchorPoint(conn.StartNode.GetCenterPoint()); // 记录连线的折线点(用于命中检测) conn.LinePoints.Clear(); // 计算中点(水平/垂直折线) int midX = (startAnchor.X + endAnchor.X) / 2; int midY = (startAnchor.Y + endAnchor.Y) / 2; Pen drawPen = conn.Pen; // 高亮处理:选中/悬停的连线变粗变红 if (conn == _selectedConnection || conn == _hoveredConnection) { drawPen = new Pen(Color.Red, 3); } // 如果水平距离更远,先水平后垂直 if (Math.Abs(startAnchor.X - endAnchor.X) > Math.Abs(startAnchor.Y - endAnchor.Y)) { conn.LinePoints.Add(startAnchor); conn.LinePoints.Add(new Point(midX, startAnchor.Y)); conn.LinePoints.Add(new Point(midX, endAnchor.Y)); conn.LinePoints.Add(endAnchor); g.DrawLine(drawPen, startAnchor.X, startAnchor.Y, midX, startAnchor.Y); g.DrawLine(drawPen, midX, startAnchor.Y, midX, endAnchor.Y); g.DrawLine(drawPen, midX, endAnchor.Y, endAnchor.X, endAnchor.Y); } else // 垂直距离更远,先垂直后水平 { conn.LinePoints.Add(startAnchor); conn.LinePoints.Add(new Point(startAnchor.X, midY)); conn.LinePoints.Add(new Point(endAnchor.X, midY)); conn.LinePoints.Add(endAnchor); g.DrawLine(drawPen, startAnchor.X, startAnchor.Y, startAnchor.X, midY); g.DrawLine(drawPen, startAnchor.X, midY, endAnchor.X, midY); g.DrawLine(drawPen, endAnchor.X, midY, endAnchor.X, endAnchor.Y); } } } // 获取菱形形状顶点 private Point[] GetBranchShapePoints(FlowNode node) { int centerX = node.Bounds.X + node.Bounds.Width / 2; int centerY = node.Bounds.Y + node.Bounds.Height / 2; return new Point[] { new Point(centerX, node.Bounds.Top), new Point(node.Bounds.Right, centerY), new Point(centerX, node.Bounds.Bottom), new Point(node.Bounds.Left, centerY) }; } // 获取平行四边形形状顶点 private Point[] GetProcessShapePoints(FlowNode node) { int centerX = node.Bounds.X + node.Bounds.Width / 2; int centerY = node.Bounds.Y + node.Bounds.Height / 2; return new Point[] { new Point(node.Bounds.Left, centerY - 10), new Point(node.Bounds.Right, node.Bounds.Top), new Point(node.Bounds.Right, node.Bounds.Bottom), new Point(node.Bounds.Left, centerY + 10) }; } #endregion #region 连线命中检测 // 检测鼠标是否命中某条连线 private FlowConnection HitTestConnection(Point mousePos) { foreach (var conn in _connections) { if (conn.LinePoints.Count < 2) continue; // 检测连线的每一段 for (int i = 0; i < conn.LinePoints.Count - 1; i++) { if (IsPointNearLine(mousePos, conn.LinePoints[i], conn.LinePoints[i + 1], 5)) { return conn; } } } return null; } // 判断点是否在直线附近(容错距离) private bool IsPointNearLine(Point point, Point lineStart, Point lineEnd, int tolerance) { // 计算点到直线的距离 float distance = Math.Abs((lineEnd.X - lineStart.X) * (lineStart.Y - point.Y) - (lineStart.X - point.X) * (lineEnd.Y - lineStart.Y)) / (float)Math.Sqrt(Math.Pow(lineEnd.X - lineStart.X, 2) + Math.Pow(lineEnd.Y - lineStart.Y, 2)); // 同时检测点是否在直线的包围盒内 Rectangle lineRect = new Rectangle( Math.Min(lineStart.X, lineEnd.X) - tolerance, Math.Min(lineStart.Y, lineEnd.Y) - tolerance, Math.Abs(lineStart.X - lineEnd.X) + 2 * tolerance, Math.Abs(lineStart.Y - lineEnd.Y) + 2 * tolerance); return distance <= tolerance && lineRect.Contains(point); } #endregion #region 事件处理 // 键盘按下:Shift键激活连线绘制模式 private void FlowChartForm_KeyDown(object sender, KeyEventArgs e) { if (e.Shift) { // 激活连线模式时,鼠标指针改为十字 panelChart.Cursor = Cursors.Cross; } } // 键盘松开:退出连线绘制模式 private void FlowChartForm_KeyUp(object sender, KeyEventArgs e) { if (!e.Shift) { panelChart.Cursor = Cursors.Default; _isDrawingConnection = false; _connectionStartNode = null; } } // 鼠标按下:处理节点选中/连线起点 private void PanelChart_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { _selectedNode = null; _selectedConnection = null; // 清空选中的连线 var clickedNode = _nodes.Find(n => n.Bounds.Contains(e.Location)); var clickedConn = HitTestConnection(e.Location); // Shift+左键:选择连线起点 if (ModifierKeys == Keys.Shift && clickedNode != null) { _isDrawingConnection = true; _connectionStartNode = clickedNode; _tempConnectionEndPoint = e.Location; } // 普通左键:选中节点或连线 else if (clickedNode != null) { _selectedNode = clickedNode; _selectedNode.IsDragging = true; _selectedNode.DragOffset = new Point(e.X - clickedNode.Bounds.X, e.Y - clickedNode.Bounds.Y); } else if (clickedConn != null) { _selectedConnection = clickedConn; // 选中连线 panelChart.Invalidate(); } else { _isDrawingConnection = false; _connectionStartNode = null; } } } // 鼠标移动:处理节点拖拽/连线预览/连线悬停 private void PanelChart_MouseMove(object sender, MouseEventArgs e) { // 1. 检测鼠标悬停的连线 var hoveredConn = HitTestConnection(e.Location); if (hoveredConn != _hoveredConnection) { _hoveredConnection = hoveredConn; // 悬停时改变鼠标指针 panelChart.Cursor = _hoveredConnection != null ? Cursors.Hand : (ModifierKeys == Keys.Shift ? Cursors.Cross : Cursors.Default); panelChart.Invalidate(); } // 2. 拖拽节点 if (_selectedNode != null && _selectedNode.IsDragging) { _selectedNode.Bounds = new Rectangle( e.X - _selectedNode.DragOffset.X, e.Y - _selectedNode.DragOffset.Y, _selectedNode.Bounds.Width, _selectedNode.Bounds.Height); // 更新自定义形状顶点 if (_selectedNode.Type == NodeType.Branch) _selectedNode.CustomShapePoints = GetBranchShapePoints(_selectedNode); else if (_selectedNode.Type == NodeType.Process) _selectedNode.CustomShapePoints = GetProcessShapePoints(_selectedNode); panelChart.Invalidate(); } // 3. 预览连线 else if (_isDrawingConnection && _connectionStartNode != null) { _tempConnectionEndPoint = e.Location; panelChart.Invalidate(); } } // 鼠标松开:处理节点拖拽结束/连线终点确认 private void PanelChart_MouseUp(object sender, MouseEventArgs e) { // 1. 结束节点拖拽 if (_selectedNode != null) { _selectedNode.IsDragging = false; } // 2. 确认连线终点 if (_isDrawingConnection && _connectionStartNode != null) { var endNode = _nodes.Find(n => n.Bounds.Contains(e.Location)); // 终点是有效节点且不是起点本身 if (endNode != null && endNode.Id != _connectionStartNode.Id) { // 添加新连线 _connections.Add(new FlowConnection { StartNode = _connectionStartNode, EndNode = endNode, Pen = new Pen(Color.Black, 1) }); } // 结束连线绘制 _isDrawingConnection = false; _connectionStartNode = null; panelChart.Invalidate(); } } // 鼠标右键:根据点击位置显示不同菜单 private void PanelChart_MouseClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) { _rightClickPos = e.Location; var clickedNode = _nodes.Find(n => n.Bounds.Contains(e.Location)); var clickedConn = HitTestConnection(e.Location); // 右键点击连线:显示删除连线菜单 if (clickedConn != null) { _selectedConnection = clickedConn; connectionContextMenu.Show(panelChart, e.Location); } // 右键点击节点:显示节点菜单 else if (clickedNode != null) { _selectedNode = clickedNode; nodeContextMenu.Items[0].Enabled = clickedNode.Type != NodeType.StartEnd; nodeContextMenu.Show(panelChart, e.Location); } // 右键点击空白处:显示添加节点菜单 else { _selectedNode = null; nodeContextMenu.Items[0].Enabled = false; nodeContextMenu.Show(panelChart, e.Location); } } } // 鼠标悬停:刷新状态 private void PanelChart_MouseHover(object sender, EventArgs e) { panelChart.Invalidate(); } // 删除节点(同时删除关联连线) private void DeleteNode_Click(object sender, EventArgs e) { if (_selectedNode != null && _selectedNode.Type != NodeType.StartEnd) { // 删除关联的所有连线 _connections.RemoveAll(conn => conn.StartNode == _selectedNode || conn.EndNode == _selectedNode); // 删除节点 _nodes.Remove(_selectedNode); _selectedNode = null; panelChart.Invalidate(); } else if (_selectedNode?.Type == NodeType.StartEnd) { MessageBox.Show("禁止删除开始/结束节点!"); } } // 删除单个连线 private void DeleteConnection_Click(object sender, EventArgs e) { if (_selectedConnection != null) { _connections.Remove(_selectedConnection); _selectedConnection = null; _hoveredConnection = null; panelChart.Invalidate(); } } // 添加文件节点 private void AddFileNode_Click(object sender, EventArgs e) { int fileCount = _nodes.Count(n => n.Type == NodeType.File) + 1; var newNode = new FlowNode { Type = NodeType.File, Text = $"图像文件.{fileCount.ToString("D3")}", Bounds = new Rectangle(_rightClickPos.X - 80, _rightClickPos.Y - 20, 160, 40) }; _nodes.Add(newNode); panelChart.Invalidate(); } // 添加分支节点 private void AddBranchNode_Click(object sender, EventArgs e) { int branchCount = _nodes.Count(n => n.Type == NodeType.Branch) + 1; var newNode = new FlowNode { Type = NodeType.Branch, Text = $"分支{branchCount}", Bounds = new Rectangle(_rightClickPos.X - 60, _rightClickPos.Y - 30, 120, 60), HasCross = true, CustomShapePoints = GetBranchShapePoints(new FlowNode { Bounds = new Rectangle(_rightClickPos.X - 60, _rightClickPos.Y - 30, 120, 60) }) }; _nodes.Add(newNode); panelChart.Invalidate(); } // 添加流程节点 private void AddProcessNode_Click(object sender, EventArgs e) { int processCount = _nodes.Count(n => n.Type == NodeType.Process) + 1; var newNode = new FlowNode { Type = NodeType.Process, Text = $"流程节点{processCount}", Bounds = new Rectangle(_rightClickPos.X - 50, _rightClickPos.Y - 30, 100, 60), HasCross = true, CustomShapePoints = GetProcessShapePoints(new FlowNode { Bounds = new Rectangle(_rightClickPos.X - 50, _rightClickPos.Y - 30, 100, 60) }) }; _nodes.Add(newNode); panelChart.Invalidate(); } #endregion // 程序入口 [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new FlowChartForm()); } } }