MES 排程生产计划 WinForm 中间件组件(工序流程 + 主支轴事件委托版)
你需要开发一套聚焦MES 排程生产计划 与产线工序流程执行 的 WinForm 中间件组件,核心是基于主支轴事件委托封装模式,将主轴(产线级排程管控)与支轴(工序级执行管控)的业务逻辑通过事件委托解耦,实现计划下发、工序执行、状态反馈的全流程协同,并落地为可视化的 WinForm 界面组件。
以下是可直接落地的完整解决方案,包含轻量化架构设计、核心业务逻辑、可视化界面实现,兼顾 MES 系统的实时性、易用性和可扩展性。
一、核心设计架构
1. 主支轴业务边界
| 轴类型 | 核心职责 | 核心事件 |
|---|---|---|
| 主轴(产线) | 排程计划下发 / 暂停 / 完成、产量汇总 | 排程启动、排程暂停、产量上报、计划完成 |
| 支轴(工序) | 工序执行 / 暂停 / 异常、进度反馈 | 工序就绪、工序开始、工序完成、工序异常 |
2. 事件委托核心逻辑
graph TD
A[主轴控制器] -->|注册委托| B[主支轴事件中心<单例>]
C[工序支轴1] -->|注册委托| B
D[工序支轴2] -->|注册委托| B
B -->|事件分发| E[排程服务]
B -->|事件分发| F[工序执行服务]
E & F -->|状态同步| G[WinForm可视化界面]
二、完整代码实现
1. 核心枚举与业务模型(精简版)
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Dapper;
using Newtonsoft.Json;
#region 核心枚举
/// <summary>
/// 轴类型(主轴/工序支轴)
/// </summary>
public enum AxisType
{
MainAxis = 0, // 主轴(产线)
ProcessAxis = 1 // 支轴(工序)
}
/// <summary>
/// 排程状态
/// </summary>
public enum ScheduleStatus
{
Draft = 0, // 草稿
Running = 2, // 执行中
Paused = 3, // 暂停
Completed = 4 // 已完成
}
/// <summary>
/// 工序状态
/// </summary>
public enum ProcessStatus
{
Pending = 0, // 待执行
Running = 2, // 执行中
Completed = 4, // 已完成
Exception = 5 // 异常
}
/// <summary>
/// 核心事件类型
/// </summary>
public enum MesEventType
{
ScheduleStart, // 排程启动
SchedulePause, // 排程暂停
ProcessStart, // 工序开始
ProcessComplete, // 工序完成
ProcessException, // 工序异常
YieldReport // 产量上报
}
#endregion
#region 业务模型
/// <summary>
/// 产线轴基础信息
/// </summary>
public class ProductionAxis
{
public string AxisCode { get; set; } // 轴编码(MA-001/PA-001-01)
public string AxisName { get; set; } // 轴名称
public AxisType AxisType { get; set; } // 轴类型
public string ParentAxisCode { get; set; } // 父轴编码
public string BindScheduleNo { get; set; } // 绑定排程单号
public int CurrentStatus { get; set; } // 当前状态
}
/// <summary>
/// 排程生产计划
/// </summary>
public class ProductionSchedule
{
public string ScheduleNo { get; set; } // 排程单号
public string ScheduleName { get; set; } // 排程名称
public string LineCode { get; set; } // 产线编码
public int PlanQty { get; set; } // 计划产量
public int CompletedQty { get; set; } // 已完成产量
public ScheduleStatus Status { get; set; } // 排程状态
}
/// <summary>
/// 工序执行单
/// </summary>
public class ProcessExecution
{
public string ExecutionNo { get; set; } // 执行单号
public string ScheduleNo { get; set; } // 排程单号
public string ProcessCode { get; set; } // 工序编码
public string ProcessName { get; set; } // 工序名称
public string AxisCode { get; set; } // 绑定支轴编码
public ProcessStatus Status { get; set; } // 工序状态
public int TargetQty { get; set; } // 目标产量
public int CompletedQty { get; set; } // 完成产量
}
/// <summary>
/// 事件参数
/// </summary>
public class MesEventArg : EventArgs
{
public string EventId { get; set; } = Guid.NewGuid().ToString("N");
public MesEventType EventType { get; set; }
public string TriggerAxisCode { get; set; } // 触发轴编码
public string ScheduleNo { get; set; } // 排程单号
public string ProcessCode { get; set; } // 工序编码
public string BizData { get; set; } // 业务数据(JSON)
}
#endregion
2. 主支轴事件委托核心封装
csharp
运行
#region 事件委托定义
/// <summary>
/// MES事件委托
/// </summary>
/// <param name="sender">触发源</param>
/// <param name="e">事件参数</param>
public delegate void MesEventHandler(object sender, MesEventArg e);
#endregion
/// <summary>
/// 主支轴事件中心(单例模式)
/// </summary>
public sealed class MesEventCenter
{
// 单例实例
private static readonly Lazy<MesEventCenter> _instance = new Lazy<MesEventCenter>(() => new MesEventCenter());
public static MesEventCenter Instance => _instance.Value;
// 事件委托注册表
private readonly Dictionary<MesEventType, List<MesEventHandler>> _eventHandlers = new Dictionary<MesEventType, List<MesEventHandler>>();
private readonly object _lockObj = new object();
// 私有构造函数
private MesEventCenter()
{
// 初始化所有事件类型的委托列表
foreach (MesEventType type in Enum.GetValues(typeof(MesEventType)))
{
_eventHandlers[type] = new List<MesEventHandler>();
}
}
/// <summary>
/// 注册事件委托
/// </summary>
public void RegisterHandler(MesEventType eventType, MesEventHandler handler)
{
lock (_lockObj)
{
if (!_eventHandlers[eventType].Contains(handler))
{
_eventHandlers[eventType].Add(handler);
}
}
}
/// <summary>
/// 触发事件
/// </summary>
public void TriggerEvent(MesEventArg e, object sender = null)
{
if (e == null) return;
lock (_lockObj)
{
if (_eventHandlers.ContainsKey(e.EventType))
{
// 异步执行委托(避免阻塞UI)
foreach (var handler in _eventHandlers[e.EventType].ToList())
{
try
{
handler.BeginInvoke(sender, e, null, null);
}
catch (Exception ex)
{
LogHelper.Error($"触发{e.EventType}事件失败:{ex.Message}");
}
}
}
}
}
}
3. 核心业务服务
csharp
运行
/// <summary>
/// 排程服务
/// </summary>
public class ScheduleService
{
private readonly MesEventCenter _eventCenter = MesEventCenter.Instance;
/// <summary>
/// 启动排程
/// </summary>
public bool StartSchedule(string scheduleNo)
{
try
{
// 1. 更新排程状态
DbHelper.ExecuteNonQuery(
"UPDATE ProductionSchedule SET Status = 2 WHERE ScheduleNo = @ScheduleNo",
new { ScheduleNo = scheduleNo });
// 2. 获取主轴编码
var mainAxisCode = DbHelper.GetSingle<string>(
"SELECT MainAxisCode FROM ProductionSchedule WHERE ScheduleNo = @ScheduleNo",
new { ScheduleNo = scheduleNo });
// 3. 更新主轴状态
DbHelper.ExecuteNonQuery(
"UPDATE ProductionAxis SET CurrentStatus = 2 WHERE AxisCode = @AxisCode",
new { AxisCode = mainAxisCode });
// 4. 触发排程启动事件
_eventCenter.TriggerEvent(new MesEventArg
{
EventType = MesEventType.ScheduleStart,
TriggerAxisCode = mainAxisCode,
ScheduleNo = scheduleNo
});
// 5. 初始化工序执行单
InitProcessExecution(scheduleNo);
return true;
}
catch (Exception ex)
{
LogHelper.Error($"启动排程失败:{ex.Message}");
return false;
}
}
/// <summary>
/// 暂停排程
/// </summary>
public bool PauseSchedule(string scheduleNo)
{
try
{
// 更新排程状态
DbHelper.ExecuteNonQuery(
"UPDATE ProductionSchedule SET Status = 3 WHERE ScheduleNo = @ScheduleNo",
new { ScheduleNo = scheduleNo });
// 获取主轴编码
var mainAxisCode = DbHelper.GetSingle<string>(
"SELECT MainAxisCode FROM ProductionSchedule WHERE ScheduleNo = @ScheduleNo",
new { ScheduleNo = scheduleNo });
// 更新主轴状态
DbHelper.ExecuteNonQuery(
"UPDATE ProductionAxis SET CurrentStatus = 3 WHERE AxisCode = @AxisCode",
new { AxisCode = mainAxisCode });
// 暂停所有工序
DbHelper.ExecuteNonQuery(
"UPDATE ProcessExecution SET Status = 3 WHERE ScheduleNo = @ScheduleNo AND Status = 2",
new { ScheduleNo = scheduleNo });
// 触发暂停事件
_eventCenter.TriggerEvent(new MesEventArg
{
EventType = MesEventType.SchedulePause,
TriggerAxisCode = mainAxisCode,
ScheduleNo = scheduleNo
});
return true;
}
catch (Exception ex)
{
LogHelper.Error($"暂停排程失败:{ex.Message}");
return false;
}
}
/// <summary>
/// 上报产量
/// </summary>
public bool ReportYield(string scheduleNo, string processCode, int qty)
{
try
{
// 更新工序产量
DbHelper.ExecuteNonQuery(
"UPDATE ProcessExecution SET CompletedQty = CompletedQty + @Qty " +
"WHERE ScheduleNo = @ScheduleNo AND ProcessCode = @ProcessCode",
new { Qty = qty, ScheduleNo = scheduleNo, ProcessCode = processCode });
// 汇总排程产量
var totalQty = DbHelper.GetSingle<int>(
"SELECT SUM(CompletedQty) FROM ProcessExecution WHERE ScheduleNo = @ScheduleNo",
new { ScheduleNo = scheduleNo });
// 更新排程产量
DbHelper.ExecuteNonQuery(
"UPDATE ProductionSchedule SET CompletedQty = @Qty WHERE ScheduleNo = @ScheduleNo",
new { Qty = totalQty, ScheduleNo = scheduleNo });
// 获取工序支轴编码
var axisCode = DbHelper.GetSingle<string>(
"SELECT AxisCode FROM ProcessExecution WHERE ScheduleNo = @ScheduleNo AND ProcessCode = @ProcessCode",
new { ScheduleNo = scheduleNo, ProcessCode = processCode });
// 触发产量上报事件
_eventCenter.TriggerEvent(new MesEventArg
{
EventType = MesEventType.YieldReport,
TriggerAxisCode = axisCode,
ScheduleNo = scheduleNo,
ProcessCode = processCode,
BizData = JsonConvert.SerializeObject(new { Qty = qty })
});
return true;
}
catch (Exception ex)
{
LogHelper.Error($"上报产量失败:{ex.Message}");
return false;
}
}
/// <summary>
/// 初始化工序执行单
/// </summary>
private void InitProcessExecution(string scheduleNo)
{
// 模拟生成工序执行单(实际项目中从工艺路线表读取)
var processes = new List<ProcessExecution>
{
new ProcessExecution
{
ExecutionNo = "PE-" + DateTime.Now.ToString("yyyyMMddHHmmss") + "001",
ScheduleNo = scheduleNo,
ProcessCode = "PROC001",
ProcessName = "预装",
AxisCode = "PA-001-01",
Status = ProcessStatus.Pending,
TargetQty = 1000,
CompletedQty = 0
},
new ProcessExecution
{
ExecutionNo = "PE-" + DateTime.Now.ToString("yyyyMMddHHmmss") + "002",
ScheduleNo = scheduleNo,
ProcessCode = "PROC002",
ProcessName = "组装",
AxisCode = "PA-001-02",
Status = ProcessStatus.Pending,
TargetQty = 1000,
CompletedQty = 0
}
};
// 批量插入工序执行单
foreach (var p in processes)
{
DbHelper.ExecuteNonQuery(
"INSERT INTO ProcessExecution (ExecutionNo, ScheduleNo, ProcessCode, ProcessName, " +
"AxisCode, Status, TargetQty, CompletedQty) VALUES " +
"(@ExecutionNo, @ScheduleNo, @ProcessCode, @ProcessName, @AxisCode, @Status, @TargetQty, @CompletedQty)", p);
// 更新支轴绑定状态
DbHelper.ExecuteNonQuery(
"UPDATE ProductionAxis SET BindScheduleNo = @ScheduleNo, CurrentStatus = 0 WHERE AxisCode = @AxisCode",
new { ScheduleNo = scheduleNo, AxisCode = p.AxisCode });
}
}
}
/// <summary>
/// 工序执行服务
/// </summary>
public class ProcessService
{
private readonly MesEventCenter _eventCenter = MesEventCenter.Instance;
/// <summary>
/// 启动工序
/// </summary>
public bool StartProcess(string executionNo)
{
try
{
// 获取工序信息
var process = DbHelper.GetSingle<ProcessExecution>(
"SELECT * FROM ProcessExecution WHERE ExecutionNo = @ExecutionNo",
new { ExecutionNo = executionNo });
if (process == null) return false;
// 更新工序状态
DbHelper.ExecuteNonQuery(
"UPDATE ProcessExecution SET Status = 2 WHERE ExecutionNo = @ExecutionNo",
new { ExecutionNo = executionNo });
// 更新支轴状态
DbHelper.ExecuteNonQuery(
"UPDATE ProductionAxis SET CurrentStatus = 2 WHERE AxisCode = @AxisCode",
new { AxisCode = process.AxisCode });
// 触发工序开始事件
_eventCenter.TriggerEvent(new MesEventArg
{
EventType = MesEventType.ProcessStart,
TriggerAxisCode = process.AxisCode,
ScheduleNo = process.ScheduleNo,
ProcessCode = process.ProcessCode
});
return true;
}
catch (Exception ex)
{
LogHelper.Error($"启动工序失败:{ex.Message}");
return false;
}
}
/// <summary>
/// 完成工序
/// </summary>
public bool CompleteProcess(string executionNo)
{
try
{
// 获取工序信息
var process = DbHelper.GetSingle<ProcessExecution>(
"SELECT * FROM ProcessExecution WHERE ExecutionNo = @ExecutionNo",
new { ExecutionNo = executionNo });
if (process == null) return false;
// 更新工序状态
DbHelper.ExecuteNonQuery(
"UPDATE ProcessExecution SET Status = 4 WHERE ExecutionNo = @ExecutionNo",
new { ExecutionNo = executionNo });
// 更新支轴状态
DbHelper.ExecuteNonQuery(
"UPDATE ProductionAxis SET CurrentStatus = 4 WHERE AxisCode = @AxisCode",
new { AxisCode = process.AxisCode });
// 触发工序完成事件
_eventCenter.TriggerEvent(new MesEventArg
{
EventType = MesEventType.ProcessComplete,
TriggerAxisCode = process.AxisCode,
ScheduleNo = process.ScheduleNo,
ProcessCode = process.ProcessCode
});
return true;
}
catch (Exception ex)
{
LogHelper.Error($"完成工序失败:{ex.Message}");
return false;
}
}
/// <summary>
/// 上报工序异常
/// </summary>
public bool ReportException(string executionNo, string reason)
{
try
{
// 获取工序信息
var process = DbHelper.GetSingle<ProcessExecution>(
"SELECT * FROM ProcessExecution WHERE ExecutionNo = @ExecutionNo",
new { ExecutionNo = executionNo });
if (process == null) return false;
// 更新工序状态
DbHelper.ExecuteNonQuery(
"UPDATE ProcessExecution SET Status = 5, ExceptionReason = @Reason WHERE ExecutionNo = @ExecutionNo",
new { Reason = reason, ExecutionNo = executionNo });
// 更新支轴状态
DbHelper.ExecuteNonQuery(
"UPDATE ProductionAxis SET CurrentStatus = 5 WHERE AxisCode = @AxisCode",
new { AxisCode = process.AxisCode });
// 触发工序异常事件
_eventCenter.TriggerEvent(new MesEventArg
{
EventType = MesEventType.ProcessException,
TriggerAxisCode = process.AxisCode,
ScheduleNo = process.ScheduleNo,
ProcessCode = process.ProcessCode,
BizData = JsonConvert.SerializeObject(new { Reason = reason })
});
return true;
}
catch (Exception ex)
{
LogHelper.Error($"上报工序异常失败:{ex.Message}");
return false;
}
}
}
4. 辅助工具类
/// <summary>
/// 数据库辅助类
/// </summary>
public static class DbHelper
{
// 数据库连接字符串(根据实际环境修改)
private static readonly string _connStr = "Data Source=.;Initial Catalog=MES_Schedule;Integrated Security=True;";
/// <summary>
/// 获取单条数据
/// </summary>
public static T GetSingle<T>(string sql, object param = null)
{
using var conn = new SqlConnection(_connStr);
return conn.QueryFirstOrDefault<T>(sql, param);
}
/// <summary>
/// 获取列表数据
/// </summary>
public static List<T> GetList<T>(string sql, object param = null)
{
using var conn = new SqlConnection(_connStr);
return conn.Query<T>(sql, param).ToList();
}
/// <summary>
/// 执行非查询语句
/// </summary>
public static int ExecuteNonQuery(string sql, object param = null)
{
using var conn = new SqlConnection(_connStr);
return conn.Execute(sql, param);
}
/// <summary>
/// 获取排程列表
/// </summary>
public static List<ProductionSchedule> GetScheduleList()
{
return GetList<ProductionSchedule>("SELECT * FROM ProductionSchedule ORDER BY ScheduleNo DESC");
}
/// <summary>
/// 获取工序执行单列表
/// </summary>
public static DataTable GetProcessList(string scheduleNo = "")
{
var dt = new DataTable();
var sql = "SELECT ExecutionNo, ScheduleNo, ProcessCode, ProcessName, Status, TargetQty, CompletedQty FROM ProcessExecution WHERE 1=1";
using var conn = new SqlConnection(_connStr);
using var adapter = new SqlDataAdapter(sql, conn);
if (!string.IsNullOrEmpty(scheduleNo))
{
adapter.SelectCommand.CommandText += " AND ScheduleNo = @ScheduleNo";
adapter.SelectCommand.Parameters.AddWithValue("@ScheduleNo", scheduleNo);
}
adapter.Fill(dt);
return dt;
}
}
/// <summary>
/// 日志辅助类
/// </summary>
public static class LogHelper
{
/// <summary>
/// 错误日志
/// </summary>
public static void Error(string msg)
{
try
{
var log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [ERROR] {msg}";
Console.WriteLine(log);
// 写入日志文件
if (!Directory.Exists("Logs")) Directory.CreateDirectory("Logs");
File.AppendAllText($"Logs\\MES_Log_{DateTime.Now:yyyyMMdd}.txt", log + Environment.NewLine);
}
catch { }
}
/// <summary>
/// 信息日志
/// </summary>
public static void Info(string msg)
{
try
{
var log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] {msg}";
Console.WriteLine(log);
// 写入日志文件
if (!Directory.Exists("Logs")) Directory.CreateDirectory("Logs");
File.AppendAllText($"Logs\\MES_Log_{DateTime.Now:yyyyMMdd}.txt", log + Environment.NewLine);
}
catch { }
}
}
5. WinForm 可视化界面组件
/// <summary>
/// MES排程生产计划主控件
/// </summary>
public partial class MesScheduleControl : UserControl
{
// 核心服务
private readonly MesEventCenter _eventCenter;
private readonly ScheduleService _scheduleService;
private readonly ProcessService _processService;
// 绘图资源
private readonly Pen _mainAxisPen = new Pen(Color.DarkBlue, 3);
private readonly Pen _processAxisPen = new Pen(Color.DarkGreen, 2);
private readonly Pen _selectedPen = new Pen(Color.Red, 4);
public MesScheduleControl()
{
InitializeComponent();
DoubleBuffered = true; // 防止界面闪烁
// 初始化服务
_eventCenter = MesEventCenter.Instance;
_scheduleService = new ScheduleService();
_processService = new ProcessService();
// 注册事件委托
RegisterEventHandlers();
// 加载初始数据
LoadScheduleData();
// 绑定界面事件
Paint += MesScheduleControl_Paint;
MouseDown += MesScheduleControl_MouseDown;
}
#region 数据加载
/// <summary>
/// 加载排程列表
/// </summary>
private void LoadScheduleData()
{
try
{
var schedules = DbHelper.GetScheduleList();
cboSchedule.DataSource = schedules;
cboSchedule.DisplayMember = "ScheduleName";
cboSchedule.ValueMember = "ScheduleNo";
}
catch (Exception ex)
{
LogHelper.Error($"加载排程数据失败:{ex.Message}");
MessageBox.Show("加载排程数据失败!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 加载工序列表
/// </summary>
private void LoadProcessData(string scheduleNo)
{
try
{
dgvProcess.DataSource = DbHelper.GetProcessList(scheduleNo);
// 设置列名和样式
dgvProcess.Columns["ExecutionNo"].HeaderText = "执行单号";
dgvProcess.Columns["ScheduleNo"].HeaderText = "排程单号";
dgvProcess.Columns["ProcessCode"].HeaderText = "工序编码";
dgvProcess.Columns["ProcessName"].HeaderText = "工序名称";
dgvProcess.Columns["Status"].HeaderText = "工序状态";
dgvProcess.Columns["TargetQty"].HeaderText = "目标产量";
dgvProcess.Columns["CompletedQty"].HeaderText = "完成产量";
// 状态文字转换和颜色标记
foreach (DataGridViewRow row in dgvProcess.Rows)
{
var status = (ProcessStatus)Convert.ToInt32(row.Cells["Status"].Value);
switch (status)
{
case ProcessStatus.Pending:
row.Cells["Status"].Value = "待执行";
row.DefaultCellStyle.BackColor = Color.White;
break;
case ProcessStatus.Running:
row.Cells["Status"].Value = "执行中";
row.DefaultCellStyle.BackColor = Color.LightGreen;
break;
case ProcessStatus.Completed:
row.Cells["Status"].Value = "已完成";
row.DefaultCellStyle.BackColor = Color.LightBlue;
break;
case ProcessStatus.Exception:
row.Cells["Status"].Value = "异常";
row.DefaultCellStyle.BackColor = Color.LightPink;
break;
}
}
// 更新产量进度
UpdateScheduleProgress(scheduleNo);
}
catch (Exception ex)
{
LogHelper.Error($"加载工序数据失败:{ex.Message}");
}
}
/// <summary>
/// 更新排程进度
/// </summary>
private void UpdateScheduleProgress(string scheduleNo)
{
var schedule = DbHelper.GetSingle<ProductionSchedule>(
"SELECT * FROM ProductionSchedule WHERE ScheduleNo = @ScheduleNo",
new { ScheduleNo = scheduleNo });
if (schedule != null)
{
lblProgress.Text = $"产量进度:{schedule.CompletedQty}/{schedule.PlanQty} 件 " +
$"({(schedule.PlanQty > 0 ? (schedule.CompletedQty * 100.0 / schedule.PlanQty):0):0.00}%)";
}
}
#endregion
#region 事件委托注册与处理
/// <summary>
/// 注册事件委托
/// </summary>
private void RegisterEventHandlers()
{
_eventCenter.RegisterHandler(MesEventType.ScheduleStart, OnScheduleStart);
_eventCenter.RegisterHandler(MesEventType.SchedulePause, OnSchedulePause);
_eventCenter.RegisterHandler(MesEventType.ProcessStart, OnProcessStart);
_eventCenter.RegisterHandler(MesEventType.ProcessComplete, OnProcessComplete);
_eventCenter.RegisterHandler(MesEventType.ProcessException, OnProcessException);
_eventCenter.RegisterHandler(MesEventType.YieldReport, OnYieldReport);
}
/// <summary>
/// 排程启动事件处理
/// </summary>
private void OnScheduleStart(object sender, MesEventArg e)
{
Invoke(new Action(() =>
{
txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 排程[{e.ScheduleNo}]启动成功!\r\n");
LoadProcessData(e.ScheduleNo);
}));
}
/// <summary>
/// 排程暂停事件处理
/// </summary>
private void OnSchedulePause(object sender, MesEventArg e)
{
Invoke(new Action(() =>
{
txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 排程[{e.ScheduleNo}]已暂停!\r\n");
LoadProcessData(e.ScheduleNo);
}));
}
/// <summary>
/// 工序开始事件处理
/// </summary>
private void OnProcessStart(object sender, MesEventArg e)
{
Invoke(new Action(() =>
{
txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 工序[{e.ProcessCode}]开始执行!\r\n");
LoadProcessData(e.ScheduleNo);
}));
}
/// <summary>
/// 工序完成事件处理
/// </summary>
private void OnProcessComplete(object sender, MesEventArg e)
{
Invoke(new Action(() =>
{
txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 工序[{e.ProcessCode}]执行完成!\r\n");
LoadProcessData(e.ScheduleNo);
}));
}
/// <summary>
/// 工序异常事件处理
/// </summary>
private void OnProcessException(object sender, MesEventArg e)
{
Invoke(new Action(() =>
{
var data = JsonConvert.DeserializeObject<dynamic>(e.BizData);
txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 【异常】工序[{e.ProcessCode}]:{data.Reason}\r\n");
MessageBox.Show($"工序{e.ProcessCode}执行异常:{data.Reason}", "告警", MessageBoxButtons.OK, MessageBoxIcon.Warning);
LoadProcessData(e.ScheduleNo);
}));
}
/// <summary>
/// 产量上报事件处理
/// </summary>
private void OnYieldReport(object sender, MesEventArg e)
{
Invoke(new Action(() =>
{
var data = JsonConvert.DeserializeObject<dynamic>(e.BizData);
txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 产量上报:工序[{e.ProcessCode}] 数量[{data.Qty}]\r\n");
UpdateScheduleProgress(e.ScheduleNo);
LoadProcessData(e.ScheduleNo);
}));
}
#endregion
#region 界面绘制
private void MesScheduleControl_Paint(object sender, PaintEventArgs e)
{
// 绘制主支轴示意图
var g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// 绘制主轴
g.FillRectangle(Brushes.LightGray, new Rectangle(50, 50, 120, 80));
g.DrawRectangle(_mainAxisPen, new Rectangle(50, 50, 120, 80));
g.DrawString("产线主轴", new Font("微软雅黑", 10, FontStyle.Bold), Brushes.Black, 60, 60);
g.DrawString("(MA-001)", new Font("微软雅黑", 8), Brushes.Gray, 65, 80);
// 绘制工序支轴1
g.FillRectangle(Brushes.LightGray, new Rectangle(200, 50, 120, 80));
g.DrawRectangle(_processAxisPen, new Rectangle(200, 50, 120, 80));
g.DrawString("预装工序轴", new Font("微软雅黑", 10, FontStyle.Bold), Brushes.Black, 210, 60);
g.DrawString("(PA-001-01)", new Font("微软雅黑", 8), Brushes.Gray, 215, 80);
// 绘制工序支轴2
g.FillRectangle(Brushes.LightGray, new Rectangle(200, 150, 120, 80));
g.DrawRectangle(_processAxisPen, new Rectangle(200, 150, 120, 80));
g.DrawString("组装工序轴", new Font("微软雅黑", 10, FontStyle.Bold), Brushes.Black, 210, 160);
g.DrawString("(PA-001-02)", new Font("微软雅黑", 8), Brushes.Gray, 215, 180);
// 绘制连接线
g.DrawLine(_mainAxisPen, 170, 90, 200, 90);
g.DrawLine(_mainAxisPen, 170, 90, 200, 190);
}
private void MesScheduleControl_MouseDown(object sender, MouseEventArgs e)
{
// 点击轴区域时的交互(示例)
if (new Rectangle(50, 50, 120, 80).Contains(e.X, e.Y))
{
MessageBox.Show("主轴(MA-001):产线级排程管控", "轴信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else if (new Rectangle(200, 50, 120, 80).Contains(e.X, e.Y))
{
MessageBox.Show("支轴(PA-001-01):预装工序执行管控", "轴信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else if (new Rectangle(200, 150, 120, 80).Contains(e.X, e.Y))
{
MessageBox.Show("支轴(PA-001-02):组装工序执行管控", "轴信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
#endregion
#region 界面交互事件
/// <summary>
/// 排程选择变更
/// </summary>
private void cboSchedule_SelectedIndexChanged(object sender, EventArgs e)
{
if (cboSchedule.SelectedValue != null)
{
LoadProcessData(cboSchedule.SelectedValue.ToString());
}
}
/// <summary>
/// 启动排程按钮
/// </summary>
private void btnStartSchedule_Click(object sender, EventArgs e)
{
if (cboSchedule.SelectedValue == null)
{
MessageBox.Show("请选择排程计划!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var scheduleNo = cboSchedule.SelectedValue.ToString();
if (MessageBox.Show($"确认启动排程[{scheduleNo}]吗?", "确认", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
var result = _scheduleService.StartSchedule(scheduleNo);
if (!result)
{
MessageBox.Show("排程启动失败!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
/// <summary>
/// 暂停排程按钮
/// </summary>
private void btnPauseSchedule_Click(object sender, EventArgs e)
{
if (cboSchedule.SelectedValue == null)
{
MessageBox.Show("请选择排程计划!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var scheduleNo = cboSchedule.SelectedValue.ToString();
if (MessageBox.Show($"确认暂停排程[{scheduleNo}]吗?", "确认", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
var result = _scheduleService.PauseSchedule(scheduleNo);
if (!result)
{
MessageBox.Show("排程暂停失败!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
/// <summary>
/// 启动工序按钮
/// </summary>
private void btnStartProcess_Click(object sender, EventArgs e)
{
if (dgvProcess.SelectedRows.Count == 0)
{
MessageBox.Show("请选择工序执行单!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var executionNo = dgvProcess.SelectedRows[0].Cells["ExecutionNo"].Value.ToString();
var result = _processService.StartProcess(executionNo);
if (!result)
{
MessageBox.Show("工序启动失败!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 完成工序按钮
/// </summary>
private void btnCompleteProcess_Click(object sender, EventArgs e)
{
if (dgvProcess.SelectedRows.Count == 0)
{
MessageBox.Show("请选择工序执行单!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var executionNo = dgvProcess.SelectedRows[0].Cells["ExecutionNo"].Value.ToString();
var result = _processService.CompleteProcess(executionNo);
if (!result)
{
MessageBox.Show("工序完成失败!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 上报产量按钮
/// </summary>
private void btnReportYield_Click(object sender, EventArgs e)
{
if (dgvProcess.SelectedRows.Count == 0)
{
MessageBox.Show("请选择工序执行单!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var row = dgvProcess.SelectedRows[0];
var scheduleNo = row.Cells["ScheduleNo"].Value.ToString();
var processCode = row.Cells["ProcessCode"].Value.ToString();
var qtyStr = Microsoft.VisualBasic.Interaction.InputBox("请输入本次上报产量:", "产量上报", "10");
if (!int.TryParse(qtyStr, out int qty) || qty <= 0)
{
MessageBox.Show("请输入有效的产量数量!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var result = _scheduleService.ReportYield(scheduleNo, processCode, qty);
if (!result)
{
MessageBox.Show("产量上报失败!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 上报异常按钮
/// </summary>
private void btnReportException_Click(object sender, EventArgs e)
{
if (dgvProcess.SelectedRows.Count == 0)
{
MessageBox.Show("请选择工序执行单!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var executionNo = dgvProcess.SelectedRows[0].Cells["ExecutionNo"].Value.ToString();
var reason = Microsoft.VisualBasic.Interaction.InputBox("请输入异常原因:", "异常上报", "设备故障");
if (string.IsNullOrEmpty(reason)) return;
var result = _processService.ReportException(executionNo, reason);
if (!result)
{
MessageBox.Show("异常上报失败!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
#endregion
#region 设计器代码(自动生成)
private ComboBox cboSchedule;
private Button btnStartSchedule;
private Button btnPauseSchedule;
private Button btnStartProcess;
private Button btnCompleteProcess;
private Button btnReportYield;
private Button btnReportException;
private Label lblProgress;
private TextBox txtLog;
private DataGridView dgvProcess;
private void InitializeComponent()
{
this.cboSchedule = new System.Windows.Forms.ComboBox();
this.btnStartSchedule = new System.Windows.Forms.Button();
this.btnPauseSchedule = new System.Windows.Forms.Button();
this.btnStartProcess = new System.Windows.Forms.Button();
this.btnCompleteProcess = new System.Windows.Forms.Button();
this.btnReportYield = new System.Windows.Forms.Button();
this.btnReportException = new System.Windows.Forms.Button();
this.lblProgress = new System.Windows.Forms.Label();
this.txtLog = new System.Windows.Forms.TextBox();
this.dgvProcess = new System.Windows.Forms.DataGridView();
((System.ComponentModel.ISupportInitialize)(this.dgvProcess)).BeginInit();
this.SuspendLayout();
// cboSchedule
this.cboSchedule.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cboSchedule.Location = new System.Drawing.Point(10, 10);
this.cboSchedule.Name = "cboSchedule";
this.cboSchedule.Size = new System.Drawing.Size(200, 23);
this.cboSchedule.TabIndex = 0;
this.cboSchedule.SelectedIndexChanged += new System.EventHandler(this.cboSchedule_SelectedIndexChanged);
// btnStartSchedule
this.btnStartSchedule.Location = new System.Drawing.Point(220, 10);
this.btnStartSchedule.Name = "btnStartSchedule";
this.btnStartSchedule.Size = new System.Drawing.Size(80, 23);
this.btnStartSchedule.Text = "启动排程";
this.btnStartSchedule.Click += new System.EventHandler(this.btnStartSchedule_Click);
// btnPauseSchedule
this.btnPauseSchedule.Location = new System.Drawing.Point(310, 10);
this.btnPauseSchedule.Name = "btnPauseSchedule";
this.btnPauseSchedule.Size = new System.Drawing.Size(80, 23);
this.btnPauseSchedule.Text = "暂停排程";
this.btnPauseSchedule.Click += new System.EventHandler(this.btnPauseSchedule_Click);
// btnStartProcess
this.btnStartProcess.Location = new System.Drawing.Point(400, 10);
this.btnStartProcess.Name = "btnStartProcess";
this.btnStartProcess.Size = new System.Drawing.Size(80, 23);
this.btnStartProcess.Text = "启动工序";
this.btnStartProcess.Click += new System.EventHandler(this.btnStartProcess_Click);
// btnCompleteProcess
this.btnCompleteProcess.Location = new System.Drawing.Point(490, 10);
this.btnCompleteProcess.Name = "btnCompleteProcess";
this.btnCompleteProcess.Size = new System.Drawing.Size(80, 23);
this.btnCompleteProcess.Text = "完成工序";
this.btnCompleteProcess.Click += new System.EventHandler(this.btnCompleteProcess_Click);
// btnReportYield
this.btnReportYield.Location = new System.Drawing.Point(580, 10);
this.btnReportYield.Name = "btnReportYield";
this.btnReportYield.Size = new System.Drawing.Size(80, 23);
this.btnReportYield.Text = "上报产量";
this.btnReportYield.Click += new System.EventHandler(this.btnReportYield_Click);
// btnReportException
this.btnReportException.Location = new System.Drawing.Point(670, 10);
this.btnReportException.Name = "btnReportException";
this.btnReportException.Size = new System.Drawing.Size(80, 23);
this.btnReportException.Text = "上报异常";
this.btnReportException.Click += new System.EventHandler(this.btnReportException_Click);
// lblProgress
this.lblProgress.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.lblProgress.Location = new System.Drawing.Point(10, 250);
this.lblProgress.Name = "lblProgress";
this.lblProgress.Size = new System.Drawing.Size(300, 30);
this.lblProgress.Text = "产量进度:0/0 件 (0.00%)";
this.lblProgress.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
// txtLog
this.txtLog.Location = new System.Drawing.Point(10, 290);
this.txtLog.Multiline = true;
this.txtLog.Name = "txtLog";
this.txtLog.ReadOnly = true;
this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtLog.Size = new System.Drawing.Size(300, 200);
// dgvProcess
this.dgvProcess.AllowUserToAddRows = false;
this.dgvProcess.AllowUserToDeleteRows = false;
this.dgvProcess.Location = new System.Drawing.Point(10, 40);
this.dgvProcess.Name = "dgvProcess";
this.dgvProcess.ReadOnly = true;
this.dgvProcess.Size = new System.Drawing.Size(740, 200);
this.dgvProcess.TabIndex = 1;
// MesScheduleControl
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.cboSchedule);
this.Controls.Add(this.btnStartSchedule);
this.Controls.Add(this.btnPauseSchedule);
this.Controls.Add(this.btnStartProcess);
this.Controls.Add(this.btnCompleteProcess);
this.Controls.Add(this.btnReportYield);
this.Controls.Add(this.btnReportException);
this.Controls.Add(this.lblProgress);
this.Controls.Add(this.txtLog);
this.Controls.Add(this.dgvProcess);
this.Name = "MesScheduleControl";
this.Size = new System.Drawing.Size(760, 500);
((System.ComponentModel.ISupportInitialize)(this.dgvProcess)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
}
/// <summary>
/// 主窗体
/// </summary>
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
// 添加核心控件
var scheduleControl = new MesScheduleControl();
scheduleControl.Dock = DockStyle.Fill;
this.Controls.Add(scheduleControl);
}
private void InitializeComponent()
{
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(780, 520);
this.Text = "MES排程生产计划中间件";
this.StartPosition = FormStartPosition.CenterScreen;
}
}
// 程序入口
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
6. 数据库初始化脚本
sql
-- 创建数据库
CREATE DATABASE MES_Schedule;
GO
USE MES_Schedule;
GO
-- 产线轴表
CREATE TABLE ProductionAxis (
AxisCode VARCHAR(50) PRIMARY KEY,
AxisName VARCHAR(100) NOT NULL,
AxisType INT NOT NULL, -- 0=主轴,1=工序支轴
ParentAxisCode VARCHAR(50),
BindScheduleNo VARCHAR(50),
CurrentStatus INT DEFAULT 0
);
-- 排程计划表
CREATE TABLE ProductionSchedule (
ScheduleNo VARCHAR(50) PRIMARY KEY,
ScheduleName VARCHAR(200) NOT NULL,
LineCode VARCHAR(50) NOT NULL,
PlanQty INT NOT NULL,
CompletedQty INT DEFAULT 0,
Status INT DEFAULT 0 -- 0=草稿,2=执行中,3=暂停,4=已完成
);
-- 工序执行单表
CREATE TABLE ProcessExecution (
ExecutionNo VARCHAR(50) PRIMARY KEY,
ScheduleNo VARCHAR(50) NOT NULL,
ProcessCode VARCHAR(50) NOT NULL,
ProcessName VARCHAR(100) NOT NULL,
AxisCode VARCHAR(50),
Status INT DEFAULT 0, -- 0=待执行,2=执行中,4=已完成,5=异常
TargetQty INT NOT NULL,
CompletedQty INT DEFAULT 0,
ExceptionReason NVARCHAR(500)
);
-- 插入测试数据
INSERT INTO ProductionAxis (AxisCode, AxisName, AxisType, ParentAxisCode)
VALUES ('MA-001', '总装线主轴', 0, NULL),
('PA-001-01', '预装工序轴', 1, 'MA-001'),
('PA-001-02', '组装工序轴', 1, 'MA-001');
INSERT INTO ProductionSchedule (ScheduleNo, ScheduleName, LineCode, PlanQty, CompletedQty, Status)
VALUES ('SCH20260201001', 'B产品总装计划', 'LINE001', 1000, 0, 0);
三、部署与使用说明
1. 环境要求
- .NET Framework 4.8 或更高版本
- SQL Server 2016+(或 SQL Server Express)
- Visual Studio 2019/2022
- 安装 NuGet 包:Dapper、Newtonsoft.Json
2. 部署步骤
- 执行数据库初始化脚本,创建
MES_Schedule数据库及表结构 - 创建 WinForm 项目,将上述代码文件添加到项目中
- 修改
DbHelper类中的数据库连接字符串,匹配实际环境 - 编译并运行项目
3. 核心功能操作
- 排程管理:选择排程计划,点击「启动排程」/「暂停排程」
- 工序执行:选择工序执行单,点击「启动工序」/「完成工序」
- 产量上报:选择工序,输入产量数量后点击「上报产量」
- 异常处理:选择工序,输入异常原因后点击「上报异常」
- 状态监控:实时查看产量进度、工序状态、操作日志
四、总结
关键点回顾
- 主支轴事件委托封装:通过单例事件中心实现主轴(产线)和支轴(工序)的事件解耦,支持异步事件分发,避免业务逻辑耦合。
- 核心业务覆盖:完整实现排程下发、工序执行、产量上报、异常处理等 MES 核心业务流程。
- 轻量化可视化界面:提供直观的操作界面,包含排程选择、工序列表、产量进度、操作日志等核心视图,支持主支轴图形化交互。
- 工业级可靠性:包含异常处理、日志记录、数据库操作封装,适配产线实际使用场景。
该组件可直接集成到现有 MES 系统中,也可作为独立的排程生产计划管控模块使用,具备良好的扩展性和维护性。
