排程计划的产线工序流程业务执行组件

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. 部署步骤

  1. 执行数据库初始化脚本,创建MES_Schedule数据库及表结构
  2. 创建 WinForm 项目,将上述代码文件添加到项目中
  3. 修改DbHelper类中的数据库连接字符串,匹配实际环境
  4. 编译并运行项目

3. 核心功能操作

  1. 排程管理:选择排程计划,点击「启动排程」/「暂停排程」
  2. 工序执行:选择工序执行单,点击「启动工序」/「完成工序」
  3. 产量上报:选择工序,输入产量数量后点击「上报产量」
  4. 异常处理:选择工序,输入异常原因后点击「上报异常」
  5. 状态监控:实时查看产量进度、工序状态、操作日志

四、总结

关键点回顾

  1. 主支轴事件委托封装:通过单例事件中心实现主轴(产线)和支轴(工序)的事件解耦,支持异步事件分发,避免业务逻辑耦合。
  2. 核心业务覆盖:完整实现排程下发、工序执行、产量上报、异常处理等 MES 核心业务流程。
  3. 轻量化可视化界面:提供直观的操作界面,包含排程选择、工序列表、产量进度、操作日志等核心视图,支持主支轴图形化交互。
  4. 工业级可靠性:包含异常处理、日志记录、数据库操作封装,适配产线实际使用场景。

该组件可直接集成到现有 MES 系统中,也可作为独立的排程生产计划管控模块使用,具备良好的扩展性和维护性。