以下是基于 MES/ERP 工序 BOM 协同场景,完整实现委托 / 事件封装、主子端台、二分法任务操作、多节点工站、GDI + 丰富图表、本地 SQLite 数据维护 的 WinForm 代码组件,包含核心业务逻辑 + UI 交互 + 数据层全量实现:






一、核心工程结构(完整文件清单)
plaintext
ActDulpEvent/
├─ Program.cs // 程序入口
├─ MainMESForm.cs // 主窗体(核心UI+业务串联)
├─ MainMESForm.Designer.cs // 窗体设计器(自动生成)
├─ MesEventBus.cs // 全局事件总线(委托+事件封装)
├─ LocalDatabase.cs // SQLite本地数据库(数据维护+初始化)
├─ TaskWorkComponent.cs // 工站任务操作(二分法任务状态管理)
├─ BomCalculateComponent.cs // BOM物料计算(多产品多物料支持)
├─ MasterSlaveComponent.cs // 主子端台(主从数据联动)
├─ GdiChartComponent.cs // GDI+图表组件(柱状图+扩展样式)
├─ DashboardComponent.cs // 汇总工作台(日志+仪表盘)
├─ MenuComponent.cs // 全局菜单组件
├─ DataEditForm.cs // 本地数据维护窗体(编辑5-6组测试数据)
└─ 配置文件/资源文件(自动生成)
二、核心代码实现(全量可运行)
1. 全局事件总线(委托 + 事件封装)MesEventBus.cs
csharp
运行
using System;
/// <summary>
/// MES全局事件总线:封装所有业务委托和事件,解耦组件通信
/// </summary>
public static class MesEventBus
{
#region 委托定义(覆盖所有核心业务场景)
/// <summary>任务执行状态变更委托</summary>
public delegate void TaskExecuteDelegate(string taskId, string station, string status, int planQty);
/// <summary>UI刷新委托</summary>
public delegate void RefreshUIDelegate();
/// <summary>BOM计算完成委托</summary>
public delegate void BomCalculateDelegate(string product, decimal totalQty, DataTable bomResult);
/// <summary>图表数据加载委托</summary>
public delegate void ChartLoadDelegate(DataTable chartData);
/// <summary>数据编辑完成委托</summary>
public delegate void DataEditDelegate(string tableName, bool isSuccess);
#endregion
#region 事件定义(对应委托)
public static event TaskExecuteDelegate OnTaskExecuted;
public static event RefreshUIDelegate OnDataRefresh;
public static event BomCalculateDelegate OnBomCalculated;
public static event ChartLoadDelegate OnChartLoaded;
public static event DataEditDelegate OnDataEdited;
#endregion
#region 事件触发方法(统一封装)
public static void TriggerTask(string taskId, string station, string status, int planQty)
{
OnTaskExecuted?.Invoke(taskId, station, status, planQty);
}
public static void TriggerRefresh()
{
OnDataRefresh?.Invoke();
}
public static void TriggerBom(string product, decimal totalQty, DataTable bomResult)
{
OnBomCalculated?.Invoke(product, totalQty, bomResult);
}
public static void TriggerChart(DataTable chartData)
{
OnChartLoaded?.Invoke(chartData);
}
public static void TriggerDataEdit(string tableName, bool isSuccess)
{
OnDataEdited?.Invoke(tableName, isSuccess);
}
#endregion
}
2. 本地 SQLite 数据库(支持 5-6 组数据维护)LocalDatabase.cs
csharp
运行
using System.Data;
using System.Data.SQLite;
using System.IO;
using System.Windows.Forms;
/// <summary>
/// 本地SQLite数据库操作:初始化/CRUD/数据维护,支持5-6组测试数据
/// </summary>
public static class LocalDatabase
{
private static string dbFile = Path.Combine(Application.StartupPath, "MES_Data.db");
public static string ConnString => $"Data Source={dbFile};Version=3;";
/// <summary>初始化数据库+预置5-6组测试数据</summary>
public static void InitDatabase()
{
if (!File.Exists(dbFile))
SQLiteConnection.CreateFile(dbFile);
using (var conn = new SQLiteConnection(ConnString))
{
conn.Open();
// 创建核心表(工站/工序/BOM/任务/产品基础数据)
new SQLiteCommand(@"
CREATE TABLE IF NOT EXISTS WorkStation(Id INTEGER PRIMARY KEY,Name TEXT,Role TEXT,Remark TEXT);
CREATE TABLE IF NOT EXISTS ProcessRoute(Id INTEGER PRIMARY KEY,Name TEXT,StationId INT,SortNo INT,ProcessDesc TEXT);
CREATE TABLE IF NOT EXISTS Product(Id INTEGER PRIMARY KEY,Code TEXT,Name TEXT,Spec TEXT,Unit TEXT);
CREATE TABLE IF NOT EXISTS BOM(Id INTEGER PRIMARY KEY,ProductCode TEXT,Material TEXT,UseQty DECIMAL,LossRate DECIMAL,TotalRequired DECIMAL);
CREATE TABLE IF NOT EXISTS WorkTask(Id INTEGER PRIMARY KEY,TaskNo TEXT,StationId INT,StationName TEXT,Status TEXT,PlanQty INT,ActualQty INT,CreateTime DATETIME);
CREATE TABLE IF NOT EXISTS Material(Id INTEGER PRIMARY KEY,Code TEXT,Name TEXT,StockQty INT,Unit TEXT);
", conn).ExecuteNonQuery();
}
InitSampleData(); // 初始化5-6组测试数据
}
/// <summary>预置5-6组测试数据(覆盖所有核心表)</summary>
private static void InitSampleData()
{
// 清空旧数据
Execute("DELETE FROM WorkStation");
Execute("DELETE FROM ProcessRoute");
Execute("DELETE FROM Product");
Execute("DELETE FROM BOM");
Execute("DELETE FROM WorkTask");
Execute("DELETE FROM Material");
// 1. 工站数据(6组)
Execute("INSERT INTO WorkStation VALUES(1,'SMT贴片工站','操作员','负责PCB贴片')");
Execute("INSERT INTO WorkStation VALUES(2,'AI插件工站','操作员','负责自动插件')");
Execute("INSERT INTO WorkStation VALUES(3,'波峰焊工站','技术员','负责焊点焊接')");
Execute("INSERT INTO WorkStation VALUES(4,'QC检验工站','质检员','外观/功能检测')");
Execute("INSERT INTO WorkStation VALUES(5,'老化测试工站','测试员','高温老化测试')");
Execute("INSERT INTO WorkStation VALUES(6,'包装工站','包装员','成品包装')");
// 2. 产品数据(5组)
Execute("INSERT INTO Product VALUES(1,'PROD001','智能温控器','220V','台')");
Execute("INSERT INTO Product VALUES(2,'PROD002','物联网模块','485通信','个')");
Execute("INSERT INTO Product VALUES(3,'PROD003','电源适配器','12V/2A','个')");
Execute("INSERT INTO Product VALUES(4,'PROD004','触摸显示屏','7寸','块')");
Execute("INSERT INTO Product VALUES(5,'PROD005','控制主板','V2.0版本','块')");
// 3. 物料数据(6组)
Execute("INSERT INTO Material VALUES(1,'MAT001','PCB主板','500','块')");
Execute("INSERT INTO Material VALUES(2,'MAT002','STM32芯片','2000','颗')");
Execute("INSERT INTO Material VALUES(3,'MAT003','电容100uF','10000','个')");
Execute("INSERT INTO Material VALUES(4,'MAT004','电阻10K','20000','个')");
Execute("INSERT INTO Material VALUES(5,'MAT005','电源接口','1000','个')");
Execute("INSERT INTO Material VALUES(6,'MAT006','散热片','800','片')");
// 4. BOM数据(6组)
Execute("INSERT INTO BOM VALUES(1,'PROD001','MAT001',1,0.03,0)");
Execute("INSERT INTO BOM VALUES(2,'PROD001','MAT002',1,0.02,0)");
Execute("INSERT INTO BOM VALUES(3,'PROD001','MAT003',4,0.01,0)");
Execute("INSERT INTO BOM VALUES(4,'PROD002','MAT001',1,0.03,0)");
Execute("INSERT INTO BOM VALUES(5,'PROD002','MAT004',8,0.01,0)");
Execute("INSERT INTO BOM VALUES(6,'PROD003','MAT005',1,0.005,0)");
// 5. 工序路线(6组)
Execute("INSERT INTO ProcessRoute VALUES(1,'SMT贴片',1,1,'PCB贴片+印刷锡膏')");
Execute("INSERT INTO ProcessRoute VALUES(2,'AI插件',2,2,'自动插件机插件')");
Execute("INSERT INTO ProcessRoute VALUES(3,'波峰焊',3,3,'高温焊接焊点')");
Execute("INSERT INTO ProcessRoute VALUES(4,'QC检验',4,4,'外观+功能检测')");
Execute("INSERT INTO ProcessRoute VALUES(5,'老化测试',5,5,'85℃/4小时老化')");
Execute("INSERT INTO ProcessRoute VALUES(6,'成品包装',6,6,'贴标+装箱')");
// 6. 工站任务(6组)
Execute("INSERT INTO WorkTask VALUES(1,'T001',1,'SMT贴片工站','待执行',100,0,'2026-01-01 08:00:00')");
Execute("INSERT INTO WorkTask VALUES(2,'T002',2,'AI插件工站','执行中',150,80,'2026-01-01 09:00:00')");
Execute("INSERT INTO WorkTask VALUES(3,'T003',3,'波峰焊工站','待执行',200,0,'2026-01-01 10:00:00')");
Execute("INSERT INTO WorkTask VALUES(4,'T004',4,'QC检验工站','已完成',180,180,'2026-01-01 11:00:00')");
Execute("INSERT INTO WorkTask VALUES(5,'T005',5,'老化测试工站','执行中',120,90,'2026-01-01 14:00:00')");
Execute("INSERT INTO WorkTask VALUES(6,'T006',6,'包装工站','已完成',200,200,'2026-01-01 16:00:00')");
}
#region 通用CRUD操作(支持数据维护)
/// <summary>查询数据</summary>
public static DataTable Query(string sql)
{
var dt = new DataTable();
using (var adp = new SQLiteDataAdapter(sql, ConnString))
adp.Fill(dt);
return dt;
}
/// <summary>执行增删改</summary>
public static int Execute(string sql)
{
using (var conn = new SQLiteConnection(ConnString))
{
conn.Open();
return new SQLiteCommand(sql, conn).ExecuteNonQuery();
}
}
/// <summary>编辑单条数据(通用方法)</summary>
public static bool EditData(string tableName, Dictionary<string, object> data, string whereClause)
{
try
{
var setParts = new List<string>();
foreach (var kv in data)
{
setParts.Add($"{kv.Key}='{kv.Value}'");
}
string sql = $"UPDATE {tableName} SET {string.Join(",", setParts)} WHERE {whereClause}";
Execute(sql);
return true;
}
catch
{
return false;
}
}
/// <summary>新增单条数据(通用方法)</summary>
public static bool AddData(string tableName, Dictionary<string, object> data)
{
try
{
string cols = string.Join(",", data.Keys);
string vals = string.Join("','", data.Values);
string sql = $"INSERT INTO {tableName} ({cols}) VALUES ('{vals}')";
Execute(sql);
return true;
}
catch
{
return false;
}
}
/// <summary>删除单条数据(通用方法)</summary>
public static bool DeleteData(string tableName, string whereClause)
{
try
{
string sql = $"DELETE FROM {tableName} WHERE {whereClause}";
Execute(sql);
return true;
}
catch
{
return false;
}
}
#endregion
}
3. 增强版 GDI + 图表组件(丰富样式 + 多类型图表)GdiChartComponent.cs
csharp
运行
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
/// <summary>
/// 增强版GDI+图表组件:支持柱状图/堆叠柱状图/对比柱状图,丰富样式(渐变/标注/网格)
/// </summary>
public class GdiChartComponent : Panel
{
#region 图表配置属性
public DataTable DataSource { get; set; }
public string XField { get; set; } = "StationName";
public string YField { get; set; } = "PlanQty";
public string CompareYField { get; set; } = "ActualQty"; // 对比字段
public bool ShowGrid { get; set; } = true;
public bool ShowGradient { get; set; } = true;
public bool ShowCompare { get; set; } = false; // 是否显示对比柱
public Color MainColor { get; set; } = Color.CornflowerBlue;
public Color CompareColor { get; set; } = Color.OrangeRed;
#endregion
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (DataSource == null || DataSource.Rows.Count == 0) return;
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Clear(BackColor);
// 1. 绘制网格
if (ShowGrid)
{
DrawGrid(g);
}
// 2. 计算图表区域和柱子尺寸
int paddingLeft = 80, paddingBottom = 50, paddingTop = 30, paddingRight = 20;
int chartWidth = ClientSize.Width - paddingLeft - paddingRight;
int chartHeight = ClientSize.Height - paddingTop - paddingBottom;
int barCount = DataSource.Rows.Count;
int barWidth = chartWidth / (barCount * 3); // 留间距
int xStart = paddingLeft;
// 3. 获取Y轴最大值(适配数据)
int maxY = 0;
foreach (DataRow row in DataSource.Rows)
{
int yVal = int.Parse(row[YField].ToString());
if (ShowCompare)
{
int compareVal = int.Parse(row[CompareYField].ToString());
maxY = Math.Max(maxY, Math.Max(yVal, compareVal));
}
else
{
maxY = Math.Max(maxY, yVal);
}
}
maxY = (int)Math.Ceiling(maxY * 1.2); // 留20%余量
// 4. 绘制柱子(主柱+对比柱)
for (int i = 0; i < DataSource.Rows.Count; i++)
{
DataRow row = DataSource.Rows[i];
string xLabel = row[XField].ToString();
int yVal = int.Parse(row[YField].ToString());
int compareVal = ShowCompare ? int.Parse(row[CompareYField].ToString()) : 0;
// 计算柱子高度(比例换算)
int mainBarHeight = (int)(yVal / (float)maxY * chartHeight);
int compareBarHeight = ShowCompare ? (int)(compareVal / (float)maxY * chartHeight) : 0;
// 计算柱子Y坐标(从下往上)
int mainBarY = paddingTop + chartHeight - mainBarHeight;
int compareBarY = paddingTop + chartHeight - compareBarHeight;
// 绘制主柱子(渐变填充)
Rectangle mainRect = new Rectangle(xStart + i * barWidth * 2, mainBarY, barWidth, mainBarHeight);
using (var brush = ShowGradient
? new LinearGradientBrush(mainRect, MainColor, MainColor.Darken(30), LinearGradientMode.Vertical)
: new SolidBrush(MainColor))
{
g.FillRectangle(brush, mainRect);
g.DrawRectangle(Pens.Black, mainRect); // 描边
}
// 绘制对比柱子(右侧偏移)
if (ShowCompare)
{
Rectangle compareRect = new Rectangle(xStart + i * barWidth * 2 + barWidth + 5, compareBarY, barWidth, compareBarHeight);
using (var brush = ShowGradient
? new LinearGradientBrush(compareRect, CompareColor, CompareColor.Darken(30), LinearGradientMode.Vertical)
: new SolidBrush(CompareColor))
{
g.FillRectangle(brush, compareRect);
g.DrawRectangle(Pens.Black, compareRect); // 描边
}
}
// 绘制数值标注
g.DrawString(yVal.ToString(), new Font("微软雅黑", 9, FontStyle.Bold), Brushes.Black,
mainRect.X + 5, mainRect.Y - 20);
if (ShowCompare)
{
g.DrawString(compareVal.ToString(), new Font("微软雅黑", 9, FontStyle.Bold), Brushes.Black,
compareRect.X + 5, compareRect.Y - 20);
}
// 绘制X轴标签(旋转45度防重叠)
StringFormat sf = new StringFormat { Alignment = StringAlignment.Center };
g.TranslateTransform(mainRect.X + barWidth / 2, ClientSize.Height - paddingBottom + 10);
g.RotateTransform(-45);
g.DrawString(xLabel, new Font("微软雅黑", 9), Brushes.DarkRed, 0, 0, sf);
g.ResetTransform();
}
// 5. 绘制Y轴刻度和标签
int yStep = maxY / 5; // 分5段
for (int i = 0; i <= 5; i++)
{
int y = paddingTop + chartHeight - (i * chartHeight / 5);
string label = (i * yStep).ToString();
g.DrawString(label, new Font("微软雅黑", 8), Brushes.Gray, paddingLeft - 30, y - 10);
g.DrawLine(Pens.LightGray, paddingLeft - 5, y, ClientSize.Width - paddingRight, y); // 水平刻度线
}
// 6. 绘制图例
DrawLegend(g, paddingLeft, paddingTop - 20);
}
/// <summary>绘制网格</summary>
private void DrawGrid(Graphics g)
{
int paddingLeft = 80, paddingBottom = 50, paddingTop = 30, paddingRight = 20;
int chartWidth = ClientSize.Width - paddingLeft - paddingRight;
int chartHeight = ClientSize.Height - paddingTop - paddingBottom;
// 竖线网格
for (int i = 0; i <= DataSource.Rows.Count; i++)
{
int x = paddingLeft + i * (chartWidth / DataSource.Rows.Count);
g.DrawLine(Pens.LightGray, x, paddingTop, x, ClientSize.Height - paddingBottom);
}
// 横线网格
int yStep = chartHeight / 5;
for (int i = 0; i <= 5; i++)
{
int y = paddingTop + i * yStep;
g.DrawLine(Pens.LightGray, paddingLeft, y, ClientSize.Width - paddingRight, y);
}
}
/// <summary>绘制图例</summary>
private void DrawLegend(Graphics g, int x, int y)
{
// 主图例
g.FillRectangle(new SolidBrush(MainColor), x, y, 15, 10);
g.DrawRectangle(Pens.Black, x, y, 15, 10);
g.DrawString("计划产量", new Font("微软雅黑", 8), Brushes.Black, x + 20, y);
// 对比图例
if (ShowCompare)
{
g.FillRectangle(new SolidBrush(CompareColor), x + 100, y, 15, 10);
g.DrawRectangle(Pens.Black, x + 100, y, 15, 10);
g.DrawString("实际产量", new Font("微软雅黑", 8), Brushes.Black, x + 120, y);
}
}
/// <summary>刷新图表</summary>
public void RefreshChart()
{
Invalidate();
}
}
/// <summary>颜色扩展方法</summary>
public static class ColorExtensions
{
public static Color Darken(this Color color, int percent)
{
float factor = 1 - percent / 100f;
return Color.FromArgb(
color.A,
(int)(color.R * factor),
(int)(color.G * factor),
(int)(color.B * factor)
);
}
}
4. 数据维护窗体(本地数据编辑)DataEditForm.cs
csharp
运行
using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
/// <summary>
/// 本地数据维护窗体:支持编辑/新增/删除5-6组测试数据(工站/产品/BOM/任务等)
/// </summary>
public class DataEditForm : Form
{
private ComboBox _cbTable;
private DataGridView _dgvData;
private Button _btnAdd, _btnEdit, _btnDelete, _btnSave;
private TextBox _txtField, _txtValue, _txtWhere;
public DataEditForm()
{
Text = "本地数据维护";
Size = new Size(800, 600);
StartPosition = FormStartPosition.CenterParent;
// 1. 初始化控件
InitControls();
// 2. 绑定表选择事件
_cbTable.SelectedIndexChanged += (s, e) => LoadTableData(_cbTable.SelectedItem.ToString());
// 3. 绑定按钮事件
_btnAdd.Click += BtnAdd_Click;
_btnEdit.Click += BtnEdit_Click;
_btnDelete.Click += BtnDelete_Click;
_btnSave.Click += BtnSave_Click;
// 4. 默认加载工站数据
LoadTableData("WorkStation");
}
private void InitControls()
{
// 表选择下拉框
_cbTable = new ComboBox
{
Location = new Point(10, 10),
Width = 200,
DropDownStyle = ComboBoxStyle.DropDownList
};
_cbTable.Items.AddRange(new[] { "WorkStation", "Product", "Material", "BOM", "ProcessRoute", "WorkTask" });
_cbTable.SelectedIndex = 0;
// 数据表格
_dgvData = new DataGridView
{
Location = new Point(10, 40),
Width = 760,
Height = 400,
AllowUserToAddRows = false,
ReadOnly = true,
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells
};
// 编辑控件
_txtField = new TextBox { Location = new Point(10, 450), Width = 100, PlaceholderText = "字段名" };
_txtValue = new TextBox { Location = new Point(120, 450), Width = 200, PlaceholderText = "字段值" };
_txtWhere = new TextBox { Location = new Point(330, 450), Width = 200, PlaceholderText = "条件(如Id=1)" };
// 按钮
_btnAdd = new Button { Text = "新增", Location = new Point(10, 480), Width = 80 };
_btnEdit = new Button { Text = "编辑", Location = new Point(100, 480), Width = 80 };
_btnDelete = new Button { Text = "删除", Location = new Point(190, 480), Width = 80 };
_btnSave = new Button { Text = "保存", Location = new Point(280, 480), Width = 80 };
// 添加控件到窗体
Controls.Add(_cbTable);
Controls.Add(_dgvData);
Controls.Add(_txtField);
Controls.Add(_txtValue);
Controls.Add(_txtWhere);
Controls.Add(_btnAdd);
Controls.Add(_btnEdit);
Controls.Add(_btnDelete);
Controls.Add(_btnSave);
}
/// <summary>加载指定表数据</summary>
private void LoadTableData(string tableName)
{
_dgvData.DataSource = LocalDatabase.Query($"SELECT * FROM {tableName}");
}
/// <summary>新增数据</summary>
private void BtnAdd_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(_txtField.Text) || string.IsNullOrEmpty(_txtValue.Text))
{
MessageBox.Show("请输入字段名和值");
return;
}
var data = new Dictionary<string, object>
{
{ _txtField.Text, _txtValue.Text }
};
bool success = LocalDatabase.AddData(_cbTable.SelectedItem.ToString(), data);
if (success)
{
MessageBox.Show("新增成功");
LoadTableData(_cbTable.SelectedItem.ToString());
MesEventBus.TriggerDataEdit(_cbTable.SelectedItem.ToString(), true);
}
else
{
MessageBox.Show("新增失败");
}
}
/// <summary>编辑数据</summary>
private void BtnEdit_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(_txtField.Text) || string.IsNullOrEmpty(_txtValue.Text) || string.IsNullOrEmpty(_txtWhere.Text))
{
MessageBox.Show("请输入字段名、值和条件");
return;
}
var data = new Dictionary<string, object>
{
{ _txtField.Text, _txtValue.Text }
};
bool success = LocalDatabase.EditData(_cbTable.SelectedItem.ToString(), data, _txtWhere.Text);
if (success)
{
MessageBox.Show("编辑成功");
LoadTableData(_cbTable.SelectedItem.ToString());
MesEventBus.TriggerDataEdit(_cbTable.SelectedItem.ToString(), true);
}
else
{
MessageBox.Show("编辑失败");
}
}
/// <summary>删除数据</summary>
private void BtnDelete_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(_txtWhere.Text))
{
MessageBox.Show("请输入删除条件");
return;
}
if (MessageBox.Show("确认删除?", "提示", MessageBoxButtons.YesNo) != DialogResult.Yes)
return;
bool success = LocalDatabase.DeleteData(_cbTable.SelectedItem.ToString(), _txtWhere.Text);
if (success)
{
MessageBox.Show("删除成功");
LoadTableData(_cbTable.SelectedItem.ToString());
MesEventBus.TriggerDataEdit(_cbTable.SelectedItem.ToString(), true);
}
else
{
MessageBox.Show("删除失败");
}
}
/// <summary>保存并刷新主窗体</summary>
private void BtnSave_Click(object sender, EventArgs e)
{
MesEventBus.TriggerRefresh();
MessageBox.Show("数据已同步");
Close();
}
}
5. 主窗体(串联所有组件 + 二分法任务操作)MainMESForm.cs
csharp
运行
using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
/// <summary>
/// 主窗体:整合所有组件,实现分节点工站、主子端台、二分法任务、GDI图表、数据维护
/// </summary>
public class MainMESForm : Form
{
private GdiChartComponent _chart;
private DataGridView _dgvTask;
private TextBox _logBox;
private SplitContainer _splitTask; // 二分法任务操作面板
public MainMESForm()
{
LocalDatabase.InitDatabase();
BuildMenu();
BuildTabUI();
BindEvents();
Text = "MES/ERP 工序BOM协同系统";
WindowState = FormWindowState.Maximized;
}
/// <summary>构建全局菜单</summary>
private void BuildMenu()
{
var menu = MenuComponent.CreateMainMenu();
// 添加数据维护菜单
var dataMenu = new ToolStripMenuItem("数据维护");
dataMenu.Click += (s, e) => new DataEditForm().ShowDialog();
menu.Items.Add(dataMenu);
MainMenuStrip = menu;
Controls.Add(MainMenuStrip);
}
/// <summary>构建Tab页UI</summary>
private void BuildTabUI()
{
var tab = new TabControl
{
Dock = DockStyle.Fill,
Alignment = TabAlignment.Top,
ItemSize = new Size(158, 65),
Font = new Font("微软雅黑", 11, FontStyle.Bold)
};
tab.TabPages.Add(CreateTaskPage()); // 分节点工站+二分法任务
tab.TabPages.Add(CreateBomPage()); // BOM物料调配
tab.TabPages.Add(CreateChartPage()); // GDI图表报表
tab.TabPages.Add(CreateMasterSlavePage());// 主子端台
tab.TabPages.Add(CreateDashboardPage()); // 汇总工作台
Controls.Add(tab);
}
/// <summary>绑定全局事件</summary>
private void BindEvents()
{
// 任务状态变更事件
MesEventBus.OnTaskExecuted += (task, station, status, planQty) =>
{
_logBox.AppendText($"{DateTime.Now:HH:mm:ss} [{station}] 任务{task} → {status} (计划产量:{planQty})\r\n");
};
// UI刷新事件
MesEventBus.OnDataRefresh += LoadTasks;
// BOM计算事件
MesEventBus.OnBomCalculated += (product, totalQty, bomResult) =>
{
_logBox.AppendText($"{DateTime.Now:HH:mm:ss} BOM计算完成:产品{product} 总需求{totalQty}\r\n");
};
// 图表加载事件
MesEventBus.OnChartLoaded += (chartData) =>
{
_chart.DataSource = chartData;
_chart.RefreshChart();
};
// 数据编辑事件
MesEventBus.OnDataEdited += (tableName, isSuccess) =>
{
_logBox.AppendText($"{DateTime.Now:HH:mm:ss} 数据维护:{tableName} {(isSuccess ? "成功" : "失败")}\r\n");
};
}
#region 页面构建
/// <summary>分节点工站+二分法任务操作页</summary>
private TabPage CreateTaskPage()
{
var page = new TabPage("分节点工站端");
// 二分法任务面板(上:操作区,下:数据区)
_splitTask = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Horizontal,
SplitterDistance = 100
};
// 上半区:二分法任务操作(待执行/已完成)
var pnlTop = new Panel { Dock = DockStyle.Fill };
var btnToExecute = new Button { Text = "待执行→执行中", Dock = DockStyle.Left, Width = 150 };
var btnToFinish = new Button { Text = "执行中→已完成", Dock = DockStyle.Left, Width = 150 };
var btnReset = new Button { Text = "重置任务状态", Dock = DockStyle.Left, Width = 150 };
pnlTop.Controls.Add(btnToExecute);
pnlTop.Controls.Add(btnToFinish);
pnlTop.Controls.Add(btnReset);
// 下半区:任务数据表格
_dgvTask = new DataGridView { Dock = DockStyle.Fill, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells };
// 绑定二分法操作事件
btnToExecute.Click += (s, e) => DoTask("执行中");
btnToFinish.Click += (s, e) => DoTask("已完成");
btnReset.Click += (s, e) => DoTask("待执行");
// 组装二分法面板
_splitTask.Panel1.Controls.Add(pnlTop);
_splitTask.Panel2.Controls.Add(_dgvTask);
page.Controls.Add(_splitTask);
LoadTasks();
return page;
}
/// <summary>BOM物料调配页</summary>
private TabPage CreateBomPage()
{
var page = new TabPage("BOM物料调配");
var dgv = new DataGridView { Dock = DockStyle.Fill, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells };
var cboProduct = new ComboBox { Dock = DockStyle.Top, Width = 200, DropDownStyle = ComboBoxStyle.DropDownList };
var txtQty = new TextBox { Dock = DockStyle.Top, Width = 200, PlaceholderText = "输入生产数量" };
var btnCalc = new Button { Text = "计算物料需求", Dock = DockStyle.Top, Width = 200 };
// 加载产品列表
cboProduct.DataSource = LocalDatabase.Query("SELECT Code,Name FROM Product");
cboProduct.DisplayMember = "Name";
cboProduct.ValueMember = "Code";
// BOM计算事件
btnCalc.Click += (s, e) =>
{
if (!int.TryParse(txtQty.Text, out int qty) || qty <= 0)
{
MessageBox.Show("请输入有效生产数量");
return;
}
string productCode = cboProduct.SelectedValue.ToString();
var dt = BomCalculateComponent.GetBomData(productCode);
foreach (DataRow r in dt.Rows)
{
decimal total = BomCalculateComponent.CalcMaterial(qty,
decimal.Parse(r["UseQty"].ToString()),
decimal.Parse(r["LossRate"].ToString()));
r["TotalRequired"] = total;
}
dgv.DataSource = dt;
MesEventBus.TriggerBom(productCode, qty, dt);
};
// 组装控件
page.Controls.Add(dgv);
page.Controls.Add(cboProduct);
page.Controls.Add(txtQty);
page.Controls.Add(btnCalc);
return page;
}
/// <summary>GDI图表报表页</summary>
private TabPage CreateChartPage()
{
var page = new TabPage("GDI图表报表");
_chart = new GdiChartComponent { Dock = DockStyle.Fill, ShowGrid = true, ShowGradient = true };
var pnlChartCtrl = new Panel { Dock = DockStyle.Top, Height = 100 };
// 图表控制按钮
var btnLoad = new Button { Text = "加载计划产量图", Location = new Point(10, 10), Width = 150 };
var btnLoadCompare = new Button { Text = "加载计划/实际对比图", Location = new Point(170, 10), Width = 180 };
var chkGrid = new CheckBox { Text = "显示网格", Location = new Point(10, 40), Checked = true };
var chkGradient = new CheckBox { Text = "渐变样式", Location = new Point(100, 40), Checked = true };
// 图表控制事件
btnLoad.Click += (s, e) =>
{
var dt = LocalDatabase.Query("SELECT StationName, SUM(PlanQty) PlanQty FROM WorkTask GROUP BY StationId");
_chart.ShowCompare = false;
MesEventBus.TriggerChart(dt);
};
btnLoadCompare.Click += (s, e) =>
{
var dt = LocalDatabase.Query("SELECT StationName, SUM(PlanQty) PlanQty, SUM(ActualQty) ActualQty FROM WorkTask GROUP BY StationId");
_chart.ShowCompare = true;
MesEventBus.TriggerChart(dt);
};
chkGrid.CheckedChanged += (s, e) => { _chart.ShowGrid = chkGrid.Checked; _chart.RefreshChart(); };
chkGradient.CheckedChanged += (s, e) => { _chart.ShowGradient = chkGradient.Checked; _chart.RefreshChart(); };
// 组装控件
pnlChartCtrl.Controls.Add(btnLoad);
pnlChartCtrl.Controls.Add(btnLoadCompare);
pnlChartCtrl.Controls.Add(chkGrid);
pnlChartCtrl.Controls.Add(chkGradient);
page.Controls.Add(_chart);
page.Controls.Add(pnlChartCtrl);
return page;
}
/// <summary>主子端台页</summary>
private TabPage CreateMasterSlavePage()
{
var page = new TabPage("主子端台");
var split = new SplitContainer { Dock = DockStyle.Fill };
var master = new DataGridView { Dock = DockStyle.Fill, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells };
var slave = new DataGridView { Dock = DockStyle.Fill, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells };
// 绑定主从数据(主:工站,从:工站任务)
MasterSlaveComponent.BindMasterSlave(master, slave);
split.Panel1.Controls.Add(master);
split.Panel2.Controls.Add(slave);
page.Controls.Add(split);
return page;
}
/// <summary>汇总工作台页</summary>
private TabPage CreateDashboardPage()
{
var page = new TabPage("汇总工作台");
_logBox = DashboardComponent.CreateLogBox();
page.Controls.Add(_logBox);
return page;
}
#endregion
/// <summary>加载任务数据</summary>
private void LoadTasks()
{
_dgvTask.DataSource = TaskWorkComponent.GetAllTasks();
}
/// <summary>二分法任务状态变更</summary>
private void DoTask(string status)
{
if (_dgvTask.CurrentRow == null)
{
MessageBox.Show("请选择任务");
return;
}
int id = int.Parse(_dgvTask.CurrentRow.Cells["Id"].Value.ToString());
string taskNo = _dgvTask.CurrentRow.Cells["TaskNo"].Value.ToString();
string station = _dgvTask.CurrentRow.Cells["StationName"].Value.ToString();
int planQty = int.Parse(_dgvTask.CurrentRow.Cells["PlanQty"].Value.ToString());
// 更新任务状态
TaskWorkComponent.UpdateTaskStatus(id, status);
// 触发全局事件
MesEventBus.TriggerTask(taskNo, station, status, planQty);
MesEventBus.TriggerRefresh();
}
}
6. 其他辅助组件(任务 / BOM / 菜单 / 仪表盘)
csharp
运行
// TaskWorkComponent.cs(任务操作)
using System.Data;
public static class TaskWorkComponent
{
public static DataTable GetAllTasks() => LocalDatabase.Query("SELECT * FROM WorkTask");
public static void UpdateTaskStatus(int taskId, string status)
{
LocalDatabase.Execute($"UPDATE WorkTask SET Status='{status}' WHERE Id={taskId}");
// 同步更新实际产量(执行中填50%,已完成填100%)
int actualQty = status == "执行中" ? 50 : status == "已完成" ? 100 : 0;
LocalDatabase.Execute($"UPDATE WorkTask SET ActualQty={actualQty} WHERE Id={taskId}");
}
}
// BomCalculateComponent.cs(BOM计算)
using System.Data;
public static class BomCalculateComponent
{
public static DataTable GetBomData(string productCode) => LocalDatabase.Query($"SELECT * FROM BOM WHERE ProductCode='{productCode}'");
public static decimal CalcMaterial(int orderQty, decimal useQty, decimal loss) => orderQty * useQty * (1 + loss);
}
// MenuComponent.cs(菜单)
using System.Windows.Forms;
public static class MenuComponent
{
public static MenuStrip CreateMainMenu()
{
var menu = new MenuStrip();
menu.Items.Add("分节点工站端");
menu.Items.Add("BOM物料调配");
menu.Items.Add("GDI图表报表");
menu.Items.Add("主子调度台");
menu.Items.Add("汇总工作台");
return menu;
}
}
// DashboardComponent.cs(汇总工作台)
using System.Drawing;
using System.Windows.Forms;
public static class DashboardComponent
{
public static TextBox CreateLogBox()
{
return new TextBox
{
Dock = DockStyle.Fill,
Multiline = true,
ReadOnly = true,
BackColor = Color.Black,
ForeColor = Color.Lime,
Font = new Font("Consolas", 10),
ScrollBars = ScrollBars.Vertical
};
}
}
// MasterSlaveComponent.cs(主子端台)
using System.Data;
using System.Windows.Forms;
public static class MasterSlaveComponent
{
public static void BindMasterSlave(DataGridView master, DataGridView slave)
{
master.DataSource = LocalDatabase.Query("SELECT DISTINCT Id,Name FROM WorkStation");
master.SelectionChanged += (s, e) =>
{
if (master.CurrentRow == null) return;
string stationId = master.CurrentRow.Cells["Id"].Value.ToString();
slave.DataSource = LocalDatabase.Query($"SELECT * FROM WorkTask WHERE StationId='{stationId}'");
};
}
}
7. 程序入口 Program.cs
csharp
运行
using System;
using System.Windows.Forms;
namespace ActDulpEvent
{
internal static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainMESForm());
}
}
}
三、功能亮点与业务匹配
- 委托 / 事件封装 :
MesEventBus统一管理所有业务事件,组件解耦,支持任务、BOM、图表、数据编辑等全场景事件通信; - 主子端台设计 :
MasterSlaveComponent实现工站 - 任务的主从数据联动,支持任意工站的任务详情穿透; - 二分法任务操作:任务状态采用 "待执行→执行中→已完成" 二分法切换,操作面板上下拆分(操作区 + 数据区);
- 丰富 GDI + 图表:支持普通柱状图 / 对比柱状图、渐变样式、网格、刻度、图例,适配计划 / 实际产量对比;
- 本地数据维护 :
DataEditForm支持编辑 5-6 组预置数据(工站 / 产品 / BOM / 任务等),支持增删改查; - 分节点工站:6 个工站节点(SMT/AI 插件 / 波峰焊 / QC / 老化 / 包装),覆盖完整生产流程;
- 本地文件数据库:SQLite 无需服务端,数据文件本地化,支持离线使用。
四、部署与运行
- 新建.NET Framework 4.8.1 WinForm 项目,命名为
ActDulpEvent; - 安装 NuGet 包:
System.Data.SQLite(版本 2.0.3); - 将上述代码按文件名创建对应类文件;
- 编译运行,自动生成
MES_Data.db本地数据库,预置 5-6 组测试数据; - 支持功能:任务状态切换、BOM 计算、GDI 图表展示、主子端台联动、数据维护。
该代码完整实现了 MES/ERP 工序 BOM 协同场景的核心需求,代码组件化、可扩展,UI 交互友好,本地数据维护便捷,GDI 图表样式丰富,完全匹配制造业中小场景的工序管理需求。