支组汇总主子节点工序路径图表

开发一个主页面包含主子节点树状图表格的工序路径的图表控件的页面,组支轴事件任务线程执行层面,支组轴汇总搭配,部分支组件组装安装工序路径资源功能模块,相关菜单页面SqlSugar函数方法预备5,8组工序成组数据,项目主要使用GDI绘图绘制,界面的图表控件尽量封装实现,实现本地文件化数据库的Winform端的代码实现组件

以下是基于 WinForm + SqlSugar + GDI+ 实现工序路径树状图 / 表格一体化控件的完整代码示例,包含本地文件数据库(SQLite)、GDI 绘图封装、成组工序数据管理等核心功能:

一、整体项目结构

plaintext

复制代码
WinFormProcessPath/
├─ Models/           // 数据模型
│  ├─ ProcessGroup.cs  // 工序组模型
│  ├─ ProcessNode.cs   // 工序节点模型
├─ DataAccess/       // 数据访问层(SqlSugar)
│  ├─ DbContext.cs     // SqlSugar上下文
├─ Controls/         // 自定义控件封装
│  ├─ ProcessTreeChart.cs // GDI绘制的工序树状图控件
│  ├─ ProcessDataGrid.cs  // 工序数据表格控件
├─ Forms/            // 主窗体
│  ├─ MainForm.cs        // 主页面
└─ Program.cs        // 程序入口

二、核心代码实现

1. 数据模型定义

ProcessGroup.cs

csharp

运行

复制代码
using System;
using SqlSugar;

namespace WinFormProcessPath.Models
{
    /// <summary>
    /// 工序组模型(5/8组成组数据)
    /// </summary>
    [SugarTable("ProcessGroup")]
    public class ProcessGroup
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int GroupId { get; set; }
        
        /// <summary>
        /// 组编号(5/8组)
        /// </summary>
        public int GroupNo { get; set; }
        
        /// <summary>
        /// 组名称
        /// </summary>
        public string GroupName { get; set; }
        
        /// <summary>
        /// 轴类型(组支轴/支组轴)
        /// </summary>
        public string AxisType { get; set; }
        
        /// <summary>
        /// 创建时间
        /// </summary>
        public DateTime CreateTime { get; set; } = DateTime.Now;
    }
}

ProcessNode.cs

csharp

运行

复制代码
using System;
using SqlSugar;

namespace WinFormProcessPath.Models
{
    /// <summary>
    /// 工序节点模型(主子节点树状结构)
    /// </summary>
    [SugarTable("ProcessNode")]
    public class ProcessNode
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int NodeId { get; set; }
        
        /// <summary>
        /// 所属工序组ID
        /// </summary>
        public int GroupId { get; set; }
        
        /// <summary>
        /// 父节点ID(0为根节点)
        /// </summary>
        public int ParentId { get; set; }
        
        /// <summary>
        /// 工序名称
        /// </summary>
        public string ProcessName { get; set; }
        
        /// <summary>
        /// 工序类型(组装/安装/资源模块)
        /// </summary>
        public string ProcessType { get; set; }
        
        /// <summary>
        /// 执行线程/任务ID
        /// </summary>
        public string ThreadId { get; set; }
        
        /// <summary>
        /// 工序状态
        /// </summary>
        public string Status { get; set; }
        
        /// <summary>
        /// 排序号
        /// </summary>
        public int SortNo { get; set; }
    }
}
2. SqlSugar 数据访问层(本地 SQLite)

DbContext.cs

csharp

运行

复制代码
using SqlSugar;
using System.IO;
using WinFormProcessPath.Models;

namespace WinFormProcessPath.DataAccess
{
    public class DbContext
    {
        public SqlSugarClient Db { get; private set; }

        public DbContext()
        {
            // 本地文件数据库(SQLite)
            string dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ProcessDb.db");
            Db = new SqlSugarClient(new ConnectionConfig()
            {
                ConnectionString = $"Data Source={dbPath}",
                DbType = DbType.Sqlite,
                IsAutoCloseConnection = true,
                InitKeyType = InitKeyType.Attribute
            });

            // 初始化表结构
            Db.CodeFirst.InitTables(typeof(ProcessGroup), typeof(ProcessNode));
            
            // 调试模式
            Db.Aop.OnLogExecuting = (sql, pars) =>
            {
                Console.WriteLine(sql);
            };
            
            // 初始化测试数据(5/8组工序)
            InitTestData();
        }

        /// <summary>
        /// 初始化5/8组测试工序数据
        /// </summary>
        private void InitTestData()
        {
            if (!Db.Queryable<ProcessGroup>().Any())
            {
                // 插入5/8组基础数据
                Db.Insertable(new[]
                {
                    new ProcessGroup{GroupNo=5,GroupName="5组工序(组支轴)",AxisType="组支轴"},
                    new ProcessGroup{GroupNo=8,GroupName="8组工序(支组轴)",AxisType="支组轴"}
                }).ExecuteCommand();

                // 插入5组工序节点(树状结构)
                int group5Id = Db.Queryable<ProcessGroup>().Where(g => g.GroupNo == 5).Select(g => g.GroupId).First();
                Db.Insertable(new[]
                {
                    // 根节点
                    new ProcessNode{GroupId=group5Id,ParentId=0,ProcessName="5组总工序",ProcessType="汇总",ThreadId="THREAD_001",Status="待执行",SortNo=1},
                    // 子节点(组装工序)
                    new ProcessNode{GroupId=group5Id,ParentId=1,ProcessName="组件A组装",ProcessType="组装",ThreadId="THREAD_002",Status="执行中",SortNo=2},
                    new ProcessNode{GroupId=group5Id,ParentId=1,ProcessName="组件B组装",ProcessType="组装",ThreadId="THREAD_003",Status="待执行",SortNo=3},
                    // 孙节点(安装工序)
                    new ProcessNode{GroupId=group5Id,ParentId=2,ProcessName="组件A安装",ProcessType="安装",ThreadId="THREAD_004",Status="已完成",SortNo=4},
                    new ProcessNode{GroupId=group5Id,ParentId=3,ProcessName="组件B安装",ProcessType="安装",ThreadId="THREAD_005",Status="待执行",SortNo=5}
                }).ExecuteCommand();

                // 插入8组工序节点
                int group8Id = Db.Queryable<ProcessGroup>().Where(g => g.GroupNo == 8).Select(g => g.GroupId).First();
                Db.Insertable(new[]
                {
                    new ProcessNode{GroupId=group8Id,ParentId=0,ProcessName="8组总工序",ProcessType="汇总",ThreadId="THREAD_006",Status="待执行",SortNo=1},
                    new ProcessNode{GroupId=group8Id,ParentId=6,ProcessName="资源模块配置",ProcessType="资源模块",ThreadId="THREAD_007",Status="执行中",SortNo=2},
                    new ProcessNode{GroupId=group8Id,ParentId=7,ProcessName="资源模块挂载",ProcessType="资源模块",ThreadId="THREAD_008",Status="待执行",SortNo=3}
                }).ExecuteCommand();
            }
        }

        // 封装常用查询方法
        public ISugarQueryable<ProcessGroup> GetAllGroups() => Db.Queryable<ProcessGroup>();
        public ISugarQueryable<ProcessNode> GetNodesByGroupId(int groupId) => Db.Queryable<ProcessNode>().Where(n => n.GroupId == groupId).OrderBy(n => n.SortNo);
        public bool UpdateNodeStatus(int nodeId, string status) => Db.Updateable<ProcessNode>().Set(n => n.Status, status).Where(n => n.NodeId == nodeId).ExecuteCommand() > 0;
    }
}
3. GDI + 封装工序树状图控件

ProcessTreeChart.cs

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using WinFormProcessPath.Models;

namespace WinFormProcessPath.Controls
{
    /// <summary>
    /// GDI绘制的工序树状图控件
    /// </summary>
    public class ProcessTreeChart : Control
    {
        private List<ProcessNode> _nodes = new List<ProcessNode>();
        private readonly Font _textFont = new Font("微软雅黑", 9);
        private readonly int _nodeWidth = 120;
        private readonly int _nodeHeight = 40;
        private readonly int _horizontalGap = 80;
        private readonly int _verticalGap = 30;

        /// <summary>
        /// 绑定工序节点数据
        /// </summary>
        /// <param name="nodes"></param>
        public void BindData(List<ProcessNode> nodes)
        {
            _nodes = nodes;
            Invalidate(); // 重绘控件
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            if (_nodes == null || _nodes.Count == 0) return;

            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; // 抗锯齿
            e.Graphics.Clear(BackColor);

            // 构建树状结构
            var rootNodes = _nodes.FindAll(n => n.ParentId == 0);
            int startY = 50;
            foreach (var root in rootNodes)
            {
                DrawTreeNode(e.Graphics, root, 50, startY);
                startY += CalculateNodeHeight(root) + _verticalGap * 2;
            }
        }

        /// <summary>
        /// 递归绘制树节点
        /// </summary>
        private void DrawTreeNode(Graphics g, ProcessNode node, int x, int y)
        {
            // 绘制节点矩形
            Rectangle nodeRect = new Rectangle(x, y, _nodeWidth, _nodeHeight);
            Brush bgBrush = GetNodeBgBrush(node.Status);
            g.FillRoundedRectangle(bgBrush, nodeRect, 5);
            g.DrawRectangle(Pens.Black, nodeRect);

            // 绘制节点文本
            string text = $"{node.ProcessName}\n[{node.ThreadId}]";
            StringFormat sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
            g.DrawString(text, _textFont, Brushes.Black, nodeRect, sf);

            // 绘制子节点
            var childNodes = _nodes.FindAll(n => n.ParentId == node.NodeId);
            int childY = y - (childNodes.Count - 1) * (_nodeHeight + _verticalGap) / 2;
            foreach (var child in childNodes)
            {
                int childX = x + _horizontalGap;
                // 绘制连接线
                g.DrawLine(Pens.DarkGray, x + _nodeWidth, y + _nodeHeight / 2, childX, childY + _nodeHeight / 2);
                // 递归绘制子节点
                DrawTreeNode(g, child, childX, childY);
                childY += _nodeHeight + _verticalGap;
            }
        }

        /// <summary>
        /// 计算节点及其子节点总高度
        /// </summary>
        private int CalculateNodeHeight(ProcessNode node)
        {
            var childNodes = _nodes.FindAll(n => n.ParentId == node.NodeId);
            if (childNodes.Count == 0) return _nodeHeight;

            int totalHeight = 0;
            foreach (var child in childNodes)
            {
                totalHeight += CalculateNodeHeight(child) + _verticalGap;
            }
            return Math.Max(_nodeHeight, totalHeight - _verticalGap);
        }

        /// <summary>
        /// 根据状态获取节点背景色
        /// </summary>
        private Brush GetNodeBgBrush(string status)
        {
            return status switch
            {
                "已完成" => Brushes.LightGreen,
                "执行中" => Brushes.LightSkyBlue,
                "待执行" => Brushes.LightYellow,
                _ => Brushes.White
            };
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _textFont.Dispose();
            }
            base.Dispose(disposing);
        }
    }

    /// <summary>
    /// 扩展方法:绘制圆角矩形
    /// </summary>
    public static class GraphicsExtension
    {
        public static void FillRoundedRectangle(this Graphics g, Brush brush, Rectangle rect, int radius)
        {
            using (GraphicsPath path = new GraphicsPath())
            {
                path.AddArc(rect.X, rect.Y, radius, radius, 180, 90);
                path.AddArc(rect.Right - radius, rect.Y, radius, radius, 270, 90);
                path.AddArc(rect.Right - radius, rect.Bottom - radius, radius, radius, 0, 90);
                path.AddArc(rect.X, rect.Bottom - radius, radius, radius, 90, 90);
                path.CloseAllFigures();
                g.FillPath(brush, path);
            }
        }
    }
}
4. 工序数据表格控件(封装)

ProcessDataGrid.cs

csharp

运行

复制代码
using System.Windows.Forms;
using WinFormProcessPath.Models;

namespace WinFormProcessPath.Controls
{
    /// <summary>
    /// 工序数据表格控件封装
    /// </summary>
    public class ProcessDataGrid : DataGridView
    {
        public ProcessDataGrid()
        {
            // 基础样式配置
            AutoGenerateColumns = false;
            AllowUserToAddRows = false;
            SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            ReadOnly = true;
            ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;

            // 配置列
            InitColumns();
        }

        /// <summary>
        /// 初始化表格列
        /// </summary>
        private void InitColumns()
        {
            Columns.AddRange(new[]
            {
                new DataGridViewTextBoxColumn{Name="NodeId",HeaderText="工序ID",DataPropertyName="NodeId",Width=80},
                new DataGridViewTextBoxColumn{Name="ProcessName",HeaderText="工序名称",DataPropertyName="ProcessName",Width=150},
                new DataGridViewTextBoxColumn{Name="ProcessType",HeaderText="工序类型",DataPropertyName="ProcessType",Width=120},
                new DataGridViewTextBoxColumn{Name="ThreadId",HeaderText="执行线程",DataPropertyName="ThreadId",Width=120},
                new DataGridViewTextBoxColumn{Name="Status",HeaderText="状态",DataPropertyName="Status",Width=100},
                new DataGridViewTextBoxColumn{Name="SortNo",HeaderText="排序号",DataPropertyName="SortNo",Width=80}
            });
        }

        /// <summary>
        /// 绑定工序数据
        /// </summary>
        /// <param name="nodes"></param>
        public void BindProcessData(System.Collections.IEnumerable nodes)
        {
            DataSource = null;
            DataSource = nodes;
            // 状态列着色
            foreach (DataGridViewRow row in Rows)
            {
                string status = row.Cells["Status"].Value?.ToString();
                row.DefaultCellStyle.BackColor = status switch
                {
                    "已完成" => System.Drawing.Color.LightGreen,
                    "执行中" => System.Drawing.Color.LightSkyBlue,
                    "待执行" => System.Drawing.Color.LightYellow,
                    _ => System.Drawing.Color.White
                };
            }
        }
    }
}
5. 主窗体(整合树状图 + 表格)

MainForm.cs

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using WinFormProcessPath.Controls;
using WinFormProcessPath.DataAccess;
using WinFormProcessPath.Models;

namespace WinFormProcessPath.Forms
{
    public partial class MainForm : Form
    {
        private readonly DbContext _dbContext;
        private readonly ProcessTreeChart _processChart;
        private readonly ProcessDataGrid _processGrid;
        private ComboBox _cboGroup;

        public MainForm()
        {
            InitializeComponent();
            _dbContext = new DbContext();
            
            // 初始化控件
            InitLayout();
            
            // 绑定工序组下拉框
            BindGroupCombo();
            
            // 默认加载5组数据
            LoadProcessData(5);
        }

        /// <summary>
        /// 初始化界面布局
        /// </summary>
        private void InitLayout()
        {
            // 窗体基础设置
            Text = "工序路径管理系统";
            Size = new System.Drawing.Size(1200, 800);

            // 工序组选择框
            _cboGroup = new ComboBox
            {
                Location = new System.Drawing.Point(20, 20),
                Width = 200,
                DropDownStyle = ComboBoxStyle.DropDownList
            };
            _cboGroup.SelectedIndexChanged += CboGroup_SelectedIndexChanged;
            Controls.Add(_cboGroup);

            // 树状图控件
            _processChart = new ProcessTreeChart
            {
                Location = new System.Drawing.Point(20, 60),
                Size = new System.Drawing.Size(800, 400),
                BackColor = System.Drawing.Color.White
            };
            Controls.Add(_processChart);

            // 表格控件
            _processGrid = new ProcessDataGrid
            {
                Location = new System.Drawing.Point(20, 480),
                Size = new System.Drawing.Size(1150, 250)
            };
            Controls.Add(_processGrid);

            // 功能按钮
            Button btnUpdateStatus = new Button
            {
                Text = "标记为已完成",
                Location = new System.Drawing.Point(250, 20),
                Width = 120
            };
            btnUpdateStatus.Click += BtnUpdateStatus_Click;
            Controls.Add(btnUpdateStatus);
        }

        /// <summary>
        /// 绑定工序组下拉框
        /// </summary>
        private void BindGroupCombo()
        {
            List<ProcessGroup> groups = _dbContext.GetAllGroups().ToList();
            _cboGroup.DataSource = groups;
            _cboGroup.DisplayMember = "GroupName";
            _cboGroup.ValueMember = "GroupNo";
        }

        /// <summary>
        /// 切换工序组加载数据
        /// </summary>
        private void CboGroup_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (_cboGroup.SelectedValue is int groupNo)
            {
                LoadProcessData(groupNo);
            }
        }

        /// <summary>
        /// 加载指定组的工序数据
        /// </summary>
        private void LoadProcessData(int groupNo)
        {
            int groupId = _dbContext.GetAllGroups().Where(g => g.GroupNo == groupNo).Select(g => g.GroupId).First();
            List<ProcessNode> nodes = _dbContext.GetNodesByGroupId(groupId).ToList();
            
            // 绑定树状图和表格
            _processChart.BindData(nodes);
            _processGrid.BindProcessData(nodes);
        }

        /// <summary>
        /// 更新选中工序状态
        /// </summary>
        private void BtnUpdateStatus_Click(object sender, EventArgs e)
        {
            if (_processGrid.SelectedRows.Count == 0) return;
            
            int nodeId = (int)_processGrid.SelectedRows[0].Cells["NodeId"].Value;
            bool success = _dbContext.UpdateNodeStatus(nodeId, "已完成");
            if (success)
            {
                // 重新加载数据
                int groupNo = (int)_cboGroup.SelectedValue;
                LoadProcessData(groupNo);
                MessageBox.Show("状态更新成功!");
            }
            else
            {
                MessageBox.Show("状态更新失败!");
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _dbContext.Db.Dispose();
                _processChart.Dispose();
                _processGrid.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}
6. 程序入口

Program.cs

csharp

运行

复制代码
using System;
using System.Windows.Forms;
using WinFormProcessPath.Forms;

namespace WinFormProcessPath
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
}

三、关键功能说明

  1. 本地文件数据库:使用 SQLite 作为本地文件数据库,无需安装数据库服务,数据文件直接存储在程序目录下。
  2. SqlSugar 封装:封装数据访问层,包含初始化表结构、测试数据、常用 CRUD 方法,支持 5/8 组工序成组数据管理。
  3. GDI + 绘图封装 :自定义ProcessTreeChart控件,递归绘制树状工序路径,支持节点状态着色、圆角矩形、连接线绘制,抗锯齿优化。
  4. 表格控件封装ProcessDataGrid封装 DataGridView,固定列配置、状态着色,简化数据绑定。
  5. 界面整合:主窗体整合工序组选择、树状图展示、表格展示、状态更新等功能,支持组支轴 / 支组轴工序数据切换。

四、使用说明

  1. 需安装 NuGet 包:SqlSugarCore(SqlSugar 核心库)、System.Data.SQLite(SQLite 驱动)。
  2. 程序启动后自动创建本地数据库文件ProcessDb.db,并初始化 5/8 组测试工序数据。
  3. 下拉框切换 5/8 组工序,树状图展示工序路径层级,表格展示详细数据,选中表格行可点击按钮更新工序状态。
  4. 可扩展:支持更多工序类型、自定义 GDI 绘图样式、增加导出 / 导入功能、扩展 SqlSugar 查询方法等。

五、扩展建议

  1. 增加节点拖拽功能,支持工序路径排序调整。
  2. 封装更多 GDI 绘图样式(如节点图标、连接线样式)。
  3. 增加工序数据的增删改查完整功能。
  4. 支持导出树状图为图片(GDI 绘图保存为 Bitmap)。
  5. 增加多线程执行状态实时刷新功能。
相关推荐
万年咸鱼8 分钟前
JVM 内存区域详解
jvm
风骏时光牛马28 分钟前
从零到实战,完整AI学习路线
前端
三十而立洋1 小时前
深入理解 Monorepo:子包安装依赖
前端·前端工程化
IT_陈寒1 小时前
Java Stream处理大集合,我的内存怎么就炸了
前端·人工智能·后端
Captaincc1 小时前
AI 用量桌面端-桌面宠物自定义指南
前端·人工智能
唐青枫1 小时前
别只会看日志:C#.NET SOS 调试扩展从 Dump 到内存泄漏排查
c#·.net
OpenTiny社区2 小时前
码力全开,智启前端新生态|OpenTiny 登陆华为全联接大会2026
前端·github
妙码生花2 小时前
利用AI从零学Go并完成实战项目,完工总结:目录结构
前端·后端·go
妙码生花2 小时前
利用AI从零学Go并完成实战项目,完工总结:商业级开源产品定位和核心特性介绍
前端·后端·go
Captaincc2 小时前
掘金AI用量统计v0.1.0大更新-支持桌面宠物自定义和订阅额度卡片
前端·后端