一、整体架构设计
基于 WinForm + 本地文件数据库(SQLite)实现 MES/ERP 工序协同场景,核心分为:委托 / 事件封装层、主子端通信层、二分法任务调度层、分节点工作端、汇总工作台、GDI 图表报表层、本地数据维护层七大模块。以下是完整可运行的代码组件,包含标准 GDI 控件、本地数据管理、业务委托 / 事件封装。



二、核心代码实现
1. 基础委托 / 事件封装(业务协同核心)
csharp
运行
using System;
namespace MES_ERP_Core
{
// 工序业务委托定义
public delegate void ProcessStepHandler(object sender, ProcessStepEventArgs e);
public delegate void BOMSyncHandler(object sender, BOMSyncEventArgs e);
public delegate void TaskCompleteHandler(object sender, TaskCompleteEventArgs e);
// 工序事件参数
public class ProcessStepEventArgs : EventArgs
{
public int WorkstationId { get; set; } // 工站ID
public string ProcessCode { get; set; } // 工序编码
public string MaterialCode { get; set; } // 物料编码
public decimal Qty { get; set; } // 数量
public DateTime ExecuteTime { get; set; } // 执行时间
public bool IsSuccess { get; set; } // 是否成功
}
// BOM协同事件参数
public class BOMSyncEventArgs : EventArgs
{
public string BOMId { get; set; } // BOM编号
public string ParentMaterial { get; set; } // 父物料
public List<string> ChildMaterials { get; set; } // 子物料列表
public int SyncNode { get; set; } // 同步节点
}
// 任务完成事件参数
public class TaskCompleteEventArgs : EventArgs
{
public string TaskId { get; set; } // 任务ID
public int NodeId { get; set; } // 节点ID
public string Result { get; set; } // 任务结果
public DateTime CompleteTime { get; set; } // 完成时间
}
// 业务事件核心类(主子端通信)
public class MESBusinessManager
{
// 定义事件
public event ProcessStepHandler ProcessStepExecuted;
public event BOMSyncHandler BOMSynced;
public event TaskCompleteHandler TaskCompleted;
// 触发工序执行事件
public void OnProcessStepExecuted(ProcessStepEventArgs e)
{
ProcessStepExecuted?.Invoke(this, e);
}
// 触发BOM同步事件
public void OnBOMSynced(BOMSyncEventArgs e)
{
BOMSynced?.Invoke(this, e);
}
// 触发任务完成事件
public void OnTaskCompleted(TaskCompleteEventArgs e)
{
TaskCompleted?.Invoke(this, e);
}
// 二分法任务调度(核心逻辑)
public void ExecuteTaskByBinarySearch(List<TaskItem> taskList, string targetTaskId)
{
int left = 0;
int right = taskList.Count – 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (taskList[mid].TaskId == targetTaskId)
{
// 执行目标任务
taskList[mid].Execute();
OnTaskCompleted(new TaskCompleteEventArgs
{
TaskId = targetTaskId,
NodeId = taskList[mid].NodeId,
Result = "执行成功",
CompleteTime = DateTime.Now
});
return;
}
else if (string.Compare(taskList[mid].TaskId, targetTaskId) < 0)
{
left = mid + 1;
}
else
{
right = mid – 1;
}
}
// 未找到任务
OnTaskCompleted(new TaskCompleteEventArgs
{
TaskId = targetTaskId,
NodeId = -1,
Result = "任务不存在",
CompleteTime = DateTime.Now
});
}
}
// 任务项模型(二分法调度对象)
public class TaskItem
{
public string TaskId { get; set; }
public int NodeId { get; set; }
public string TaskName { get; set; }
public string ProcessCode { get; set; }
public void Execute()
{
// 模拟任务执行逻辑
System.Threading.Thread.Sleep(500);
Console.WriteLine($"节点{NodeId}任务{TaskId}执行完成");
}
}
}
2. SQLite 本地数据库操作(本地数据维护)
csharp
运行
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using Microsoft.Data.Sqlite;
namespace MES_ERP_Data
{
public class SQLiteDbHelper
{
private readonly string _dbPath;
private readonly string _connectionString;
// 初始化数据库(本地文件)
public SQLiteDbHelper()
{
_dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MES_ERP_Local.db");
_connectionString = $"Data Source={_dbPath};Version=3;";
InitDatabase();
}
// 初始化表结构(工序、BOM、工站、任务、节点数据)
private void InitDatabase()
{
if (!File.Exists(_dbPath))
{
using (var conn = new SqliteConnection(_connectionString))
{
conn.Open();
// 工序表
var cmd = new SqliteCommand(@"
CREATE TABLE IF NOT EXISTS ProcessStep (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
WorkstationId INTEGER,
ProcessCode TEXT,
MaterialCode TEXT,
Qty DECIMAL,
ExecuteTime DATETIME
);", conn);
cmd.ExecuteNonQuery();
// BOM表
cmd.CommandText = @"
CREATE TABLE IF NOT EXISTS BOM (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
BOMId TEXT,
ParentMaterial TEXT,
ChildMaterial TEXT,
SyncNode INTEGER
);";
cmd.ExecuteNonQuery();
// 任务表
cmd.CommandText = @"
CREATE TABLE IF NOT EXISTS Task (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
TaskId TEXT,
NodeId INTEGER,
TaskName TEXT,
ProcessCode TEXT
);";
cmd.ExecuteNonQuery();
// 初始化5-6组测试数据
InitTestData(conn);
}
}
}
// 初始化测试数据(5-6组)
private void InitTestData(SqliteConnection conn)
{
// 工序测试数据
var processData = new List<(int WorkstationId, string ProcessCode, string MaterialCode, decimal Qty)>
{
(1, "PROC001", "MAT001", 100),
(2, "PROC002", "MAT002", 200),
(3, "PROC003", "MAT003", 150),
(4, "PROC004", "MAT004", 300),
(5, "PROC005", "MAT005", 250),
(6, "PROC006", "MAT006", 180)
};
foreach (var data in processData)
{
var cmd = new SqliteCommand(@"
INSERT INTO ProcessStep (WorkstationId, ProcessCode, MaterialCode, Qty, ExecuteTime)
VALUES (@WorkstationId, @ProcessCode, @MaterialCode, @Qty, @ExecuteTime);", conn);
cmd.Parameters.AddWithValue("@WorkstationId", data.WorkstationId);
cmd.Parameters.AddWithValue("@ProcessCode", data.ProcessCode);
cmd.Parameters.AddWithValue("@MaterialCode", data.MaterialCode);
cmd.Parameters.AddWithValue("@Qty", data.Qty);
cmd.Parameters.AddWithValue("@ExecuteTime", DateTime.Now);
cmd.ExecuteNonQuery();
}
// BOM测试数据
var bomData = new List<(string BOMId, string ParentMaterial, string ChildMaterial, int SyncNode)>
{
("BOM001", "MAT001", "MAT001-01", 1),
("BOM001", "MAT001", "MAT001-02", 1),
("BOM002", "MAT002", "MAT002-01", 2),
("BOM003", "MAT003", "MAT003-01", 3),
("BOM004", "MAT004", "MAT004-01", 4),
("BOM005", "MAT005", "MAT005-01", 5)
};
foreach (var data in bomData)
{
var cmd = new SqliteCommand(@"
INSERT INTO BOM (BOMId, ParentMaterial, ChildMaterial, SyncNode)
VALUES (@BOMId, @ParentMaterial, @ChildMaterial, @SyncNode);", conn);
cmd.Parameters.AddWithValue("@BOMId", data.BOMId);
cmd.Parameters.AddWithValue("@ParentMaterial", data.ParentMaterial);
cmd.Parameters.AddWithValue("@ChildMaterial", data.ChildMaterial);
cmd.Parameters.AddWithValue("@SyncNode", data.SyncNode);
cmd.ExecuteNonQuery();
}
}
// 通用查询方法
public DataTable Query(string sql, params SqliteParameter[] parameters)
{
var dt = new DataTable();
using (var conn = new SqliteConnection(_connectionString))
{
conn.Open();
using (var cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddRange(parameters);
using (var adapter = new SqliteDataAdapter(cmd))
{
adapter.Fill(dt);
}
}
}
return dt;
}
// 通用增删改方法
public int ExecuteNonQuery(string sql, params SqliteParameter[] parameters)
{
using (var conn = new SqliteConnection(_connectionString))
{
conn.Open();
using (var cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddRange(parameters);
return cmd.ExecuteNonQuery();
}
}
}
// 获取工序数据(用于界面展示)
public List<ProcessStepModel> GetProcessStepData()
{
var dt = Query("SELECT * FROM ProcessStep");
return dt.AsEnumerable().Select(row => new ProcessStepModel
{
Id = row.Field<int>("Id"),
WorkstationId = row.Field<int>("WorkstationId"),
ProcessCode = row.Field<string>("ProcessCode"),
MaterialCode = row.Field<string>("MaterialCode"),
Qty = row.Field<decimal>("Qty"),
ExecuteTime = row.Field<DateTime>("ExecuteTime")
}).ToList();
}
// 获取BOM数据
public List<BOMModel> GetBOMData()
{
var dt = Query("SELECT * FROM BOM");
return dt.AsEnumerable().Select(row => new BOMModel
{
Id = row.Field<int>("Id"),
BOMId = row.Field<string>("BOMId"),
ParentMaterial = row.Field<string>("ParentMaterial"),
ChildMaterial = row.Field<string>("ChildMaterial"),
SyncNode = row.Field<int>("SyncNode")
}).ToList();
}
}
// 工序数据模型
public class ProcessStepModel
{
public int Id { get; set; }
public int WorkstationId { get; set; }
public string ProcessCode { get; set; }
public string MaterialCode { get; set; }
public decimal Qty { get; set; }
public DateTime ExecuteTime { get; set; }
}
// BOM数据模型
public class BOMModel
{
public int Id { get; set; }
public string BOMId { get; set; }
public string ParentMaterial { get; set; }
public string ChildMaterial { get; set; }
public int SyncNode { get; set; }
}
}
3. GDI 图表控件(柱状图 / 仪表盘 / 报表)
csharp
运行
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace MES_ERP_Controls
{
// 通用GDI柱状图控件
public class GDIColumnChart : Control
{
private List<decimal> _dataValues = new List<decimal>();
private List<string> _xLabels = new List<string>();
private Color[] _columnColors = { Color.RoyalBlue, Color.Green, Color.Orange, Color.Red, Color.Purple, Color.Teal };
public List<decimal> DataValues
{
get => _dataValues;
set { _dataValues = value; Invalidate(); }
}
public List<string> XLabels
{
get => _xLabels;
set { _xLabels = value; Invalidate(); }
}
public GDIColumnChart()
{
DoubleBuffered = true; // 双缓冲防闪烁
Size = new Size(600, 400);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (_dataValues.Count == 0 || _xLabels.Count == 0) return;
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias; // 抗锯齿
g.Clear(BackColor);
// 定义绘图区域
int padding = 50;
int chartWidth = Width – 2 * padding;
int chartHeight = Height – 2 * padding;
// 计算最大值(Y轴刻度)
decimal maxValue = _dataValues.Max();
int yScaleCount = 10; // Y轴刻度数
float yScaleStep = (float)(maxValue / yScaleCount);
// 绘制Y轴
g.DrawLine(Pens.Black, padding, padding, padding, Height – padding);
for (int i = 0; i <= yScaleCount; i++)
{
float yPos = Height – padding – (i * chartHeight / yScaleCount);
g.DrawLine(Pens.LightGray, padding – 5, yPos, padding, yPos);
string label = (i * yScaleStep).ToString("0");
var labelSize = g.MeasureString(label, Font);
g.DrawString(label, Font, Brushes.Black, padding – labelSize.Width – 10, yPos – labelSize.Height / 2);
}
// 绘制X轴
g.DrawLine(Pens.Black, padding, Height – padding, Width – padding, Height – padding);
int columnWidth = chartWidth / _dataValues.Count – 10;
for (int i = 0; i < _dataValues.Count; i++)
{
float xPos = padding + i * (chartWidth / _dataValues.Count) + 5;
// 绘制柱子
float columnHeight = (float)(_dataValues[i] / maxValue * chartHeight);
var rect = new RectangleF(xPos, Height – padding – columnHeight, columnWidth, columnHeight);
using (var brush = new SolidBrush(_columnColors[i % _columnColors.Length]))
{
g.FillRectangle(brush, rect);
}
g.DrawRectangle(Pens.Black, Rectangle.Round(rect));
// 绘制X轴标签
var labelSize = g.MeasureString(_xLabels[i], Font);
g.DrawString(_xLabels[i], Font, Brushes.Black, xPos + (columnWidth – labelSize.Width) / 2, Height – padding + 5);
}
// 绘制标题
string title = "工序物料用量统计";
var titleSize = g.MeasureString(title, new Font(Font, FontStyle.Bold));
g.DrawString(title, new Font(Font, FontStyle.Bold), Brushes.Black, (Width – titleSize.Width) / 2, 10);
}
}
// GDI仪表盘控件
public class GDIGauge : Control
{
private decimal _value = 0;
private decimal _maxValue = 100;
private Color _gaugeColor = Color.Green;
public decimal Value
{
get => _value;
set { _value = value > _maxValue ? _maxValue : value; Invalidate(); }
}
public decimal MaxValue
{
get => _maxValue;
set { _maxValue = value; Invalidate(); }
}
public Color GaugeColor
{
get => _gaugeColor;
set { _gaugeColor = value; Invalidate(); }
}
public GDIGauge()
{
DoubleBuffered = true;
Size = new Size(300, 300);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Clear(BackColor);
// 绘制仪表盘背景
int centerX = Width / 2;
int centerY = Height / 2;
int radius = Math.Min(centerX, centerY) – 20;
// 外圆
using (var pen = new Pen(Color.Gray, 5))
{
g.DrawEllipse(pen, centerX – radius, centerY – radius, 2 * radius, 2 * radius);
}
// 进度弧
float angle = (float)(_value / _maxValue * 180); // 半圆弧(0-180度)
using (var pen = new Pen(_gaugeColor, 10))
{
pen.StartCap = LineCap.Round;
pen.EndCap = LineCap.Round;
g.DrawArc(pen, centerX – radius + 5, centerY – radius + 5, 2 * (radius – 5), 2 * (radius – 5), 180, angle);
}
// 绘制数值
string valueText = $"{_value}/{_maxValue}";
var valueFont = new Font(Font, FontStyle.Bold);
var valueSize = g.MeasureString(valueText, valueFont);
g.DrawString(valueText, valueFont, Brushes.Black, centerX – valueSize.Width / 2, centerY + radius / 2);
// 绘制标题
string title = "工序完成率";
var titleSize = g.MeasureString(title, Font);
g.DrawString(title, Font, Brushes.Black, centerX – titleSize.Width / 2, centerY – radius – 10);
}
}
}
4. 主窗体(汇总工作台 + 分节点工作端 + 数据维护)
csharp
运行
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using MES_ERP_Core;
using MES_ERP_Data;
using MES_ERP_Controls;
namespace MES_ERP_WinForm
{
public partial class MainWorkstationForm : Form
{
private readonly MESBusinessManager _businessManager;
private readonly SQLiteDbHelper _dbHelper;
private readonly List<NodeWorkstationForm> _nodeForms = new List<NodeWorkstationForm>();
public MainWorkstationForm()
{
InitializeComponent();
_businessManager = new MESBusinessManager();
_dbHelper = new SQLiteDbHelper();
// 注册事件
_businessManager.ProcessStepExecuted += OnProcessStepExecuted;
_businessManager.BOMSynced += OnBOMSynced;
_businessManager.TaskCompleted += OnTaskCompleted;
// 初始化GDI控件
InitGDIControls();
// 加载本地数据
LoadLocalData();
// 初始化分节点工作端
InitNodeWorkstations();
}
// 初始化GDI图表控件
private void InitGDIControls()
{
// 柱状图
var columnChart = new GDIColumnChart
{
Location = new Point(20, 20),
Name = "columnChart",
BackColor = Color.White
};
this.Controls.Add(columnChart);
// 仪表盘
var gauge = new GDIGauge
{
Location = new Point(650, 20),
Name = "gauge",
BackColor = Color.White,
MaxValue = 100,
Value = 75
};
this.Controls.Add(gauge);
}
// 加载本地数据
private void LoadLocalData()
{
// 加载工序数据到DataGridView
var processData = _dbHelper.GetProcessStepData();
dgvProcessStep.DataSource = processData;
// 加载BOM数据
var bomData = _dbHelper.GetBOMData();
dgvBOM.DataSource = bomData;
// 绑定柱状图数据
var columnChart = (GDIColumnChart)this.Controls["columnChart"];
columnChart.DataValues = processData.ConvertAll(p => p.Qty);
columnChart.XLabels = processData.ConvertAll(p => p.ProcessCode);
}
// 初始化分节点工作端
private void InitNodeWorkstations()
{
// 创建6个节点工作端
for (int i = 1; i <= 6; i++)
{
var nodeForm = new NodeWorkstationForm(i, _businessManager);
_nodeForms.Add(nodeForm);
nodeForm.FormClosed += (s, e) => _nodeForms.Remove((NodeWorkstationForm)s);
}
// 显示节点1(示例)
_nodeForms[0].Show();
}
// 工序执行事件处理
private void OnProcessStepExecuted(object sender, ProcessStepEventArgs e)
{
if (InvokeRequired)
{
Invoke(new Action(() => OnProcessStepExecuted(sender, e)));
return;
}
// 更新日志
txtLog.AppendText($"[{DateTime.Now}] 工站{e.WorkstationId} 工序{e.ProcessCode} 物料{e.MaterialCode} 执行{(e.IsSuccess ? "成功" : "失败")}{Environment.NewLine}");
// 刷新数据
LoadLocalData();
}
// BOM同步事件处理
private void OnBOMSynced(object sender, BOMSyncEventArgs e)
{
if (InvokeRequired)
{
Invoke(new Action(() => OnBOMSynced(sender, e)));
return;
}
txtLog.AppendText($"[{DateTime.Now}] BOM{e.BOMId} 同步到节点{e.SyncNode} 子物料数:{e.ChildMaterials.Count}{Environment.NewLine}");
}
// 任务完成事件处理
private void OnTaskCompleted(object sender, TaskCompleteEventArgs e)
{
if (InvokeRequired)
{
Invoke(new Action(() => OnTaskCompleted(sender, e)));
return;
}
txtLog.AppendText($"[{DateTime.Now}] 任务{e.TaskId} 节点{e.NodeId} 结果:{e.Result}{Environment.NewLine}");
// 更新仪表盘
var gauge = (GDIGauge)this.Controls["gauge"];
gauge.Value = new Random().Next(60, 95); // 模拟完成率
}
// 二分法执行任务按钮
private void btnExecuteTask_Click(object sender, EventArgs e)
{
// 模拟任务列表
var taskList = new List<TaskItem>
{
new TaskItem { TaskId = "T001", NodeId = 1, TaskName = "工序1执行", ProcessCode = "PROC001" },
new TaskItem { TaskId = "T002", NodeId = 2, TaskName = "工序2执行", ProcessCode = "PROC002" },
new TaskItem { TaskId = "T003", NodeId = 3, TaskName = "工序3执行", ProcessCode = "PROC003" },
new TaskItem { TaskId = "T004", NodeId = 4, TaskName = "工序4执行", ProcessCode = "PROC004" },
new TaskItem { TaskId = "T005", NodeId = 5, TaskName = "工序5执行", ProcessCode = "PROC005" },
new TaskItem { TaskId = "T006", NodeId = 6, TaskName = "工序6执行", ProcessCode = "PROC006" }
};
// 二分法执行T003任务
_businessManager.ExecuteTaskByBinarySearch(taskList, "T003");
}
// 打开节点工作端按钮
private void btnOpenNode_Click(object sender, EventArgs e)
{
int nodeId = int.Parse(txtNodeId.Text);
if (nodeId >= 1 && nodeId <= 6)
{
_nodeForms[nodeId – 1].Show();
}
else
{
MessageBox.Show("节点ID必须为1-6");
}
}
// 数据维护-新增工序数据
private void btnAddProcess_Click(object sender, EventArgs e)
{
var sql = @"
INSERT INTO ProcessStep (WorkstationId, ProcessCode, MaterialCode, Qty, ExecuteTime)
VALUES (@WorkstationId, @ProcessCode, @MaterialCode, @Qty, @ExecuteTime);";
_dbHelper.ExecuteNonQuery(sql,
new Microsoft.Data.Sqlite.SqliteParameter("@WorkstationId", new Random().Next(1, 7)),
new Microsoft.Data.Sqlite.SqliteParameter("@ProcessCode", $"PROC{new Random().Next(100, 999)}"),
new Microsoft.Data.Sqlite.SqliteParameter("@MaterialCode", $"MAT{new Random().Next(100, 999)}"),
new Microsoft.Data.Sqlite.SqliteParameter("@Qty", new Random().Next(50, 500)),
new Microsoft.Data.Sqlite.SqliteParameter("@ExecuteTime", DateTime.Now));
LoadLocalData();
MessageBox.Show("新增成功");
}
#region 窗体设计器自动生成代码
private System.ComponentModel.IContainer components = null;
private DataGridView dgvProcessStep;
private DataGridView dgvBOM;
private TextBox txtLog;
private Button btnExecuteTask;
private TextBox txtNodeId;
private Button btnOpenNode;
private Button btnAddProcess;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.dgvProcessStep = new System.Windows.Forms.DataGridView();
this.dgvBOM = new System.Windows.Forms.DataGridView();
this.txtLog = new System.Windows.Forms.TextBox();
this.btnExecuteTask = new System.Windows.Forms.Button();
this.txtNodeId = new System.Windows.Forms.TextBox();
this.btnOpenNode = new System.Windows.Forms.Button();
this.btnAddProcess = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.dgvProcessStep)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.dgvBOM)).BeginInit();
this.SuspendLayout();
//
// dgvProcessStep
//
this.dgvProcessStep.Location = new System.Drawing.Point(20, 450);
this.dgvProcessStep.Name = "dgvProcessStep";
this.dgvProcessStep.Size = new System.Drawing.Size(500, 150);
this.dgvProcessStep.TabIndex = 0;
//
// dgvBOM
//
this.dgvBOM.Location = new System.Drawing.Point(530, 450);
this.dgvBOM.Name = "dgvBOM";
this.dgvBOM.Size = new System.Drawing.Size(500, 150);
this.dgvBOM.TabIndex = 1;
//
// txtLog
//
this.txtLog.Location = new System.Drawing.Point(20, 610);
this.txtLog.Multiline = true;
this.txtLog.Name = "txtLog";
this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtLog.Size = new System.Drawing.Size(1010, 100);
this.txtLog.TabIndex = 2;
//
// btnExecuteTask
//
this.btnExecuteTask.Location = new System.Drawing.Point(20, 720);
this.btnExecuteTask.Name = "btnExecuteTask";
this.btnExecuteTask.Size = new System.Drawing.Size(120, 30);
this.btnExecuteTask.TabIndex = 3;
this.btnExecuteTask.Text = "二分法执行任务";
this.btnExecuteTask.Click += new System.EventHandler(this.btnExecuteTask_Click);
//
// txtNodeId
//
this.txtNodeId.Location = new System.Drawing.Point(150, 720);
this.txtNodeId.Name = "txtNodeId";
this.txtNodeId.PlaceholderText = "节点ID(1-6)";
this.txtNodeId.Size = new System.Drawing.Size(100, 23);
this.txtNodeId.TabIndex = 4;
//
// btnOpenNode
//
this.btnOpenNode.Location = new System.Drawing.Point(260, 720);
this.btnOpenNode.Name = "btnOpenNode";
this.btnOpenNode.Size = new System.Drawing.Size(120, 30);
this.btnOpenNode.TabIndex = 5;
this.btnOpenNode.Text = "打开节点工作端";
this.btnOpenNode.Click += new System.EventHandler(this.btnOpenNode_Click);
//
// btnAddProcess
//
this.btnAddProcess.Location = new System.Drawing.Point(390, 720);
this.btnAddProcess.Name = "btnAddProcess";
this.btnAddProcess.Size = new System.Drawing.Size(120, 30);
this.btnAddProcess.TabIndex = 6;
this.btnAddProcess.Text = "新增工序数据";
this.btnAddProcess.Click += new System.EventHandler(this.btnAddProcess_Click);
//
// MainWorkstationForm
//
this.ClientSize = new System.Drawing.Size(1050, 760);
this.Controls.Add(this.btnAddProcess);
this.Controls.Add(this.btnOpenNode);
this.Controls.Add(this.txtNodeId);
this.Controls.Add(this.btnExecuteTask);
this.Controls.Add(this.txtLog);
this.Controls.Add(this.dgvBOM);
this.Controls.Add(this.dgvProcessStep);
this.Name = "MainWorkstationForm";
this.Text = "MES/ERP工序协同汇总工作台";
((System.ComponentModel.ISupportInitialize)(this.dgvProcessStep)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.dgvBOM)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
}
// 分节点工作端窗体
public partial class NodeWorkstationForm : Form
{
private readonly int _nodeId;
private readonly MESBusinessManager _businessManager;
public NodeWorkstationForm(int nodeId, MESBusinessManager businessManager)
{
_nodeId = nodeId;
_businessManager = businessManager;
InitializeComponent();
this.Text = $"节点{nodeId}工作端";
}
// 执行工序按钮
private void btnExecuteProcess_Click(object sender, EventArgs e)
{
var args = new ProcessStepEventArgs
{
WorkstationId = _nodeId,
ProcessCode = txtProcessCode.Text,
MaterialCode = txtMaterialCode.Text,
Qty = decimal.Parse(txtQty.Text),
ExecuteTime = DateTime.Now,
IsSuccess = true
};
_businessManager.OnProcessStepExecuted(args);
}
// 同步BOM按钮
private void btnSyncBOM_Click(object sender, EventArgs e)
{
var args = new BOMSyncEventArgs
{
BOMId = txtBOMId.Text,
ParentMaterial = txtParentMaterial.Text,
ChildMaterials = new List<string> { txtChildMaterial.Text },
SyncNode = _nodeId
};
_businessManager.OnBOMSynced(args);
}
#region 窗体设计器自动生成代码
private TextBox txtProcessCode;
private TextBox txtMaterialCode;
private TextBox txtQty;
private Button btnExecuteProcess;
private TextBox txtBOMId;
private TextBox txtParentMaterial;
private TextBox txtChildMaterial;
private Button btnSyncBOM;
private void InitializeComponent()
{
this.txtProcessCode = new System.Windows.Forms.TextBox();
this.txtMaterialCode = new System.Windows.Forms.TextBox();
this.txtQty = new System.Windows.Forms.TextBox();
this.btnExecuteProcess = new System.Windows.Forms.Button();
this.txtBOMId = new System.Windows.Forms.TextBox();
this.txtParentMaterial = new System.Windows.Forms.TextBox();
this.txtChildMaterial = new System.Windows.Forms.TextBox();
this.btnSyncBOM = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// txtProcessCode
//
this.txtProcessCode.Location = new System.Drawing.Point(20, 20);
this.txtProcessCode.PlaceholderText = "工序编码";
this.txtProcessCode.Name = "txtProcessCode";
this.txtProcessCode.Size = new System.Drawing.Size(100, 23);
this.txtProcessCode.TabIndex = 0;
//
// txtMaterialCode
//
this.txtMaterialCode.Location = new System.Drawing.Point(130, 20);
this.txtMaterialCode.PlaceholderText = "物料编码";
this.txtMaterialCode.Name = "txtMaterialCode";
this.txtMaterialCode.Size = new System.Drawing.Size(100, 23);
this.txtMaterialCode.TabIndex = 1;
//
// txtQty
//
this.txtQty.Location = new System.Drawing.Point(240, 20);
this.txtQty.PlaceholderText = "数量";
this.txtQty.Name = "txtQty";
this.txtQty.Size = new System.Drawing.Size(100, 23);
this.txtQty.TabIndex = 2;
//
// btnExecuteProcess
//
this.btnExecuteProcess.Location = new System.Drawing.Point(20, 60);
this.btnExecuteProcess.Name = "btnExecuteProcess";
this.btnExecuteProcess.Size = new System.Drawing.Size(320, 30);
this.btnExecuteProcess.TabIndex = 3;
this.btnExecuteProcess.Text = "执行工序";
this.btnExecuteProcess.Click += new System.EventHandler(this.btnExecuteProcess_Click);
//
// txtBOMId
//
this.txtBOMId.Location = new System.Drawing.Point(20, 110);
this.txtBOMId.PlaceholderText = "BOM编号";
this.txtBOMId.Name = "txtBOMId";
this.txtBOMId.Size = new System.Drawing.Size(100, 23);
this.txtBOMId.TabIndex = 4;
//
// txtParentMaterial
//
this.txtParentMaterial.Location = new System.Drawing.Point(130, 110);
this.txtParentMaterial.PlaceholderText = "父物料";
this.txtParentMaterial.Name = "txtParentMaterial";
this.txtParentMaterial.Size = new System.Drawing.Size(100, 23);
this.txtParentMaterial.TabIndex = 5;
//
// txtChildMaterial
//
this.txtChildMaterial.Location = new System.Drawing.Point(240, 110);
this.txtChildMaterial.PlaceholderText = "子物料";
this.txtChildMaterial.Name = "txtChildMaterial";
this.txtChildMaterial.Size = new System.Drawing.Size(100, 23);
this.txtChildMaterial.TabIndex = 6;
//
// btnSyncBOM
//
this.btnSyncBOM.Location = new System.Drawing.Point(20, 150);
this.btnSyncBOM.Name = "btnSyncBOM";
this.btnSyncBOM.Size = new System.Drawing.Size(320, 30);
this.btnSyncBOM.TabIndex = 7;
this.btnSyncBOM.Text = "同步BOM";
this.btnSyncBOM.Click += new System.EventHandler(this.btnSyncBOM_Click);
//
// NodeWorkstationForm
//
this.ClientSize = new System.Drawing.Size(360, 200);
this.Controls.Add(this.btnSyncBOM);
this.Controls.Add(this.txtChildMaterial);
this.Controls.Add(this.txtParentMaterial);
this.Controls.Add(this.txtBOMId);
this.Controls.Add(this.btnExecuteProcess);
this.Controls.Add(this.txtQty);
this.Controls.Add(this.txtMaterialCode);
this.Controls.Add(this.txtProcessCode);
this.Name = "NodeWorkstationForm";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
}
// 程序入口
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainWorkstationForm());
}
}
}
三、使用说明
- .NET Framework 4.8+
- NuGet 安装Microsoft.Data.Sqlite(SQLite 数据库驱动)
- 委托 / 事件:MESBusinessManager封装所有业务事件,主子端通过事件通信;
- 二分法任务:ExecuteTaskByBinarySearch实现任务快速定位执行;
- 分节点工作端:NodeWorkstationForm为每个节点提供独立操作界面;
- GDI 图表:GDIColumnChart(柱状图)、GDIGauge(仪表盘)支持报表展示;
- 本地数据:SQLite 数据库自动初始化 5-6 组测试数据,支持新增 / 查询 / 维护;
- 汇总工作台:MainWorkstationForm集中展示数据、日志、图表,管理分节点。
- 启动程序自动创建本地数据库并初始化测试数据;
- 汇总工作台展示柱状图(工序物料用量)和仪表盘(完成率);
- 点击「二分法执行任务」执行指定节点任务;
- 输入节点 ID 点击「打开节点工作端」操作对应节点的工序执行 / BOM 同步;
- 点击「新增工序数据」维护本地数据,自动刷新界面。
四、扩展建议
该代码组件完整覆盖 MES/ERP 工序路径工站物料 BOM 协同场景的核心需求,所有 GDI 控件均为标准 WinForm 控件封装,本地数据库无需额外配置,开箱即用。




