Winform高级技巧-界面和代码分离的实践案例

办法总比困难多(不会或不想用WPF MVVM模式也可以搞工控自动化和上位机编程)...

正文

活到老就该学到老。但是难道我们不可以偷懒么,老技术指哪打哪,游刃有余,如果已经炉火纯青,那么解决问题又快又好它不香吗。本文拒绝讨论技术谁优秀谁该被鄙视。上图的流程是花2天时间搞好的,我不是得瑟有什么不得了的地方。我见很多人都在探讨MVVM,数据驱动业务多么的了不起,其实老技术Winform一样的能办到不信你问问DeepSeek让它给你一个示例代码。比如这样子的,点这里下载。如果只是这么简单的话,就没有写本文的必要了。

我们看看上图的场景,因为现场的实际方案没有最终确定,所以开发人员写了5个版本的代码来验证5种流程,但界面就1个,因为业务的数据是一样的。像这种情况我们这种解决方式是极好的。写代码的同事只需要初级程序员就可以担任。帮忙验证和测试是由高级程序员来担任。我贴一些源码来辅助说明一下是怎么实现的。

1.界面的基类:

复制代码
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
using MES.Core;
using EES.Common;
using EES.Controls.Win;
using EES.Common.Data;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq;
using WinControls;
using DataCool_MES.framework;
using DataCool_MES.config;
using DataCool_MES.utils;
using System.Drawing;

namespace DataCool_MES
{
    public delegate void FeedInfoHandler(object sender, Exception ex, string message, MessageTipsLevel level);
    /// <summary>
    /// 业务窗体基类
    /// </summary>
    public partial class BaseModuleForm : DockBaseWorkForm
    {
        #region DllImport
        [DllImport("kernel32")]
        public static extern long WritePrivateProfileString(string section, string key, string val, string filepath);
        [DllImport("kernel32")]
        public static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retval, int size, string filePath);

        [DllImport("imm32.dll")]
        public static extern IntPtr ImmGetContext(IntPtr hwnd);
        [DllImport("imm32.dll")]
        public static extern bool ImmGetOpenStatus(IntPtr himc);
        [DllImport("imm32.dll")]
        public static extern bool ImmSetOpenStatus(IntPtr himc, bool b);
        [DllImport("imm32.dll")]
        public static extern bool ImmGetConversionStatus(IntPtr himc, ref int lpdw, ref int lpdw2);
        [DllImport("imm32.dll")]
        #endregion
        public static extern int ImmSimulateHotKey(IntPtr hwnd, int lngHotkey);
        private const int IME_CMODE_FULLSHAPE = 0x8;
        private const int IME_CHOTKEY_SHAPE_TOGGLE = 0x11;
        private SynchronizationContext sc;
        protected string onceScanData = string.Empty;
        private BarCodeHooK hook;
        IModuleFlow coreFlowObj;
        // 获取屏幕分辨率
        private Rectangle primaryScreen;
        private float scaleX;  // 基准分辨率宽度
        private float scaleY;
        [Browsable(false)]
        public IModuleFlow CoreFlowObj
        {
            get { return coreFlowObj; }
        }
        [Browsable(false)]
        /// <summary>
        /// 日志记录处理异常信息显示处理对象
        /// 此处用于各种日志和状态信息的记录,并进行对应的信息的显示
        /// </summary>
        internal virtual AlertLog Log
        {
            get
            {
                return null;
            }
        }        
        protected event Action CursorBegin;
        protected event Action CursorEnd;       
        
        /// <summary>
        /// 实例化脚本对象
        /// </summary>
        /// <returns></returns>
        protected virtual IModuleFlow CreateModuleFlow()
        {
            return null;
        }
        public BaseModuleForm()
        {
            DoubleBuffered = true;
            ImeMode = ImeMode.Off;
            AutoScaleMode = AutoScaleMode.None;
            InitializeComponent();
            #region Cursor
            CursorBegin = () =>
            {
                Clear();
                Cursor = Cursors.WaitCursor;
            };
            CursorEnd = () =>
            {
                Cursor = Cursors.Default;
            };
            #endregion
            Load += BaseModuleForm_Load;
            hook = new BarCodeHooK();
            hook.BarCodeEvent += Hook_BarCodeEvent;
            hook.Start();
            FormClosed += BaseModuleForm_FormClosed;
        }
        private void BaseModuleForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            try
            {
                hook.Stop();
                hook.BarCodeEvent -= Hook_BarCodeEvent;
                hook = null;
            }
            catch (Exception ex)
            {
                FrameAppContext.GetInstance().AppLogger.Error(ex);
            }
        }

        private void Hook_BarCodeEvent(BarCodeHooK.BarCodes barCode)
        { 
            if (CoreFlowObj != null && FlowContext.Instance.WorkStatus == WorkStatus.Running && !string.IsNullOrEmpty(barCode.BarCode))
            {
                onceScanData = TrimSpecialChar(barCode.BarCode);
                CoreFlowObj.OnExecScanReceiving(onceScanData);
            }
        }
        private void BaseModuleForm_Load(object sender, EventArgs e)
        {
            sc = SynchronizationContext.Current;
            if (!DesignMode)
            {
                Input = FlowContext.Instance;
                FlowContext.Instance.FlowCustomEvent += OnRaiseFlowCustomEvent;
            }
        }
        private void OnRaiseFlowCustomEvent(object sender, TEventArgs<EventClass> e)
        {
            sc.Post(new SendOrPostCallback(delegate (object obj)
            {
                OnFlowCustomEvent((EventClass)obj);
            }), e.Data);
        }

        protected virtual void OnFlowCustomEvent(EventClass ec)
        {
            if (ec.EventType == FlowViewCommand.ClearCurText)
            {
                Clear();
            }
        }
        /// <summary>
        /// 获取当前窗体绑定的上下文对象
        /// </summary>
        /// <returns></returns>
        public virtual BindingType GetBindingType()
        {
            Type type;
            if (bindingSource.DataSource is Type)
            {
                type = (Type)bindingSource.DataSource;
            }
            else
            {
                if (bindingSource.DataSource != null)
                {
                    type = bindingSource.DataSource.GetType();
                }
                else
                {
                    type = null;
                }
            }
            if (type == null)
            {
                throw new Exception("界面输入的数据源为空");
            }
            BindingType bindingType = new BindingType();
            Type type2;
            if (type.GUID == typeof(DataCollection<>).GUID)
            {
                type2 = type.GetGenericArguments()[0];
                bindingType.IsArray = true;
            }
            else
            {
                if (type.GUID == typeof(List<>).GUID)
                {
                    type2 = type.GetGenericArguments()[0];
                    bindingType.IsArray = true;
                }
                else
                {
                    type2 = type;
                    bindingType.IsArray = false;
                }
            }
            bindingType.Type = Factory.getRawType(type2);
            return bindingType;
        }

        public virtual void Start()
        {
            if (coreFlowObj != null && FlowContext.Instance.WorkStatus == WorkStatus.Running)
                return;
            IModuleFlow core = null;
            try
            {
                CursorBegin.Invoke();
                if (CoreFlowObj != null)
                    throw new Exception("正在作业,不能启动!");
                core = CreateModuleFlow();
                if (core != null)
                {
                    core.FeedInfo += OnFeedInfo;
                    core.ExecBeginWork();
                    coreFlowObj = core;
                    FlowContext.Instance.WorkStatus = WorkStatus.Running;
                }
                ImeMode = ImeMode.NoControl;
                Log.SetLogFocus();
            }
            catch (Exception ex)
            {
                FlowContext.Instance.WorkStatus = WorkStatus.NoAction;
                if (coreFlowObj != null)
                {
                    coreFlowObj.FeedInfo -= OnFeedInfo;
                    coreFlowObj = null;
                }
                throw ex;
            }
            finally
            {
                if (FlowContext.Instance.WorkStatus == WorkStatus.Running)
                {
                    onceScanData = string.Empty;
                }
                CursorEnd.Invoke();
            }
        }

        public virtual void Stop()
        {
            if (CoreFlowObj != null)
            {
                try
                {
                    var configObj = SerializerHelper.LoadFromXML<AppContentConfig>(FrameAppContext.GetInstance().RunTimeConfigPath + "\\AppContentConfig.Config");
                    if (configObj != null && !string.IsNullOrEmpty(configObj.EntryModuleName) && configObj.EntryModuleName.Equals("成品装箱作业"))
                    {
                        using (var dlgFrm = new FlowStopCheckForm())
                        {
                            var dlg = dlgFrm.ShowDialog();
                            if (dlg == DialogResult.OK && !FlowContext.Instance.IsEndPacking)
                            {
                                CoreFlowObj.ExecEndWork();
                                coreFlowObj = null;
                                FlowContext.Instance.WorkStatus = WorkStatus.Stopped;
                            }
                            if (dlg == DialogResult.OK && FlowContext.Instance.IsEndPacking)
                            {
                                CoreFlowObj.StopWorkCheck();
                            }
                        }
                    }
                    else
                    {
                        CoreFlowObj.ExecEndWork();
                        coreFlowObj = null;
                        FlowContext.Instance.WorkStatus = WorkStatus.Stopped;
                    }
                }
                catch (Exception ex)
                {
                    Exception error = new Exception("流程执行异常停止," + ex.InnerException == null ? ex.Message : ex.InnerException.Message);
                    FrameAppContext.GetInstance().AppLogger.Error(error);
                }
                finally
                {
                    Clear();
                    if (FlowContext.Instance.WorkStatus != WorkStatus.Running)
                    {
                        Log.Write("作业已经停止!", "", MessageTipsLevel.Warning);
                        FrameAppContext.GetInstance().AppLogger.Info("作业已经停止!");
                        FlowContext.Instance.ResetContext();
                    }
                    Utility.GCFullCollect();
                }
            }
        }

        public virtual void Pause()
        {
            coreFlowObj.ExecPauseWork();
            coreFlowObj = null;
            FlowContext.Instance.WorkStatus = WorkStatus.Pauseing;
            Clear();
            Log.Write("作业已经暂停!", "", MessageTipsLevel.Warning);
        }

        /// <summary>
        /// 界面UI控件的绑定数据清理
        /// 此处为虚函数,用于UI派生界面的具体绑定调用处理
        /// </summary>
        protected virtual void Clear()
        {
        }

        /// <summary>
        /// 异常信息消息处理队列的回调函数
        /// 主要用于各种消息或异常信息的分类处理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ex">异常消息结构体</param>
        /// <param name="message">异常消息</param>
        /// <param name="level">异常消息级别</param>
        protected void OnFeedInfo(object sender, Exception ex, string message, MessageTipsLevel level)
        {
            if (Log == null)
            {
                throw new Exception("AlertLog对象没有绑定或实例化!");
            }
            if (ex != null)
            {
                Log.Write(string.Format("{0}:->", DateTime.Now.ToString("MM/dd HH:mm:ss")), ex.Message, MessageTipsLevel.Error, true);
            }
            else
            {
                Log.Write(string.Format("{0}:->", DateTime.Now.ToString("MM/dd HH:mm:ss")), message, level, true);
            }
        }
        /// <summary>
        /// 响应扫描枪输入
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="keyData"></param>
        /// <returns></returns>
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            #region 快捷键
            if (msg.Msg == 0x0100 && ContextMenuStrip != null)
            {
                foreach (ToolStripMenuItem item in ContextMenuStrip.Items)
                {
                    if (keyData == item.ShortcutKeys)
                    {
                        item.PerformClick();
                    }
                }
            }
            #endregion
            if (FlowContext.Instance.WorkStatus != WorkStatus.Running)
                return base.ProcessCmdKey(ref msg, keyData);
            else
                return true;
        }
        /// <summary>
        /// 截取特殊字符
        /// </summary>
        /// <param name="inputString"></param>
        /// <returns></returns>
        protected virtual string TrimSpecialChar(string inputString)
        {
            return inputString.Replace("\u0010", "");
        }

        /// <summary>
        /// 动态绑定DataGridView的列
        /// </summary>
        /// <param name="t"></param>
        /// <param name="dgv"></param>
        protected virtual void BingGridColumn(Type t, DataGridView dgv)
        {
            dgv.Columns.Clear();
            var props = t.GetProperties().Where(prop => prop.GetCustomAttributes(typeof(DisplayNameAttribute), false).Any());
            int col_width = dgv.Width / props.Count() - 8;
            foreach (var prop in props)
            {
                DisplayNameAttribute attrib = (DisplayNameAttribute)prop.GetCustomAttributes(typeof(DisplayNameAttribute), false).FirstOrDefault();
                if (prop.GetCustomAttributes(typeof(ProgressBarCellAttribute), false).Any())
                {
                    DataGridViewProgressBarColumn dc = new DataGridViewProgressBarColumn();
                    dc.Name = "col" + prop.Name;
                    dc.HeaderText = attrib.DisplayName;
                    dc.DataPropertyName = prop.Name;
                    dc.Width = col_width;
                    dgv.Columns.Add(dc);
                }
                else
                {
                    DataGridViewTextBoxColumn dc = new DataGridViewTextBoxColumn();
                    dc.Name = "col" + prop.Name;
                    dc.HeaderText = attrib.DisplayName;
                    dc.DataPropertyName = prop.Name;
                    dc.Width = col_width;
                    dgv.Columns.Add(dc);
                }
            }
        }
        /// <summary>
        /// 窗体激活时设置输入法是半角
        /// </summary>
        /// <param name="e"></param>
        protected override void OnActivated(EventArgs e)
        {
            base.OnActivated(e);
            IntPtr HIme = ImmGetContext(this.Handle);
            if (ImmGetOpenStatus(HIme)) //如果输入法处于打开状态
            {
                int iMode = 0;
                int iSentence = 0;
                bool bSuccess = ImmGetConversionStatus(HIme, ref iMode, ref iSentence); //检索输入法资讯
                if (bSuccess)
                {
                    if ((iMode & IME_CMODE_FULLSHAPE) > 0) //如果是全形
                        ImmSimulateHotKey(this.Handle, IME_CHOTKEY_SHAPE_TOGGLE); //转换成半形
                }
            }
        }

        /// <summary>
        /// 缩放控件
        /// </summary>
        /// <param name="control"></param>
        protected void ScaleControl(Control control)
        {
            control.Left = (int)(control.Left * scaleX);
            control.Top = (int)(control.Top * scaleY);
            control.Width = (int)(control.Width * scaleX);
            control.Height = (int)(control.Height * scaleY);
            foreach (Control child in control.Controls)
            {
                ScaleControl(child);
            }
        }
    }
}

2、代码的基类

复制代码
using EES.Common;
using System;
using MES.Core;

namespace DataCool_MES
{
    /// <summary>
    /// 脚本的基类
    /// </summary>
    public abstract class ModuleBaseFlow : IModuleFlow
    {
        /// <summary>
        /// 传递消息的事件
        /// </summary>
        public event FeedInfoHandler FeedInfo;

        protected FlowContext flowContext = FlowContext.Instance;
        //是否是调试模式
        protected bool debugMode = false;
        //是否显示过程明细信息
        protected bool showDetail = false;
        /// <summary>
        /// 采集到数据后发生
        /// </summary>
        /// <param name="data"></param>
        public virtual void OnExecScanReceiving(string data)
        {
            FrameAppContext.GetInstance().AppLogger.Info(data);
        }

        /// <summary>
        /// 触发消息传递
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ex"></param>
        /// <param name="message"></param>
        /// <param name="level"></param>
        protected virtual void OnFeedInfo(object sender, Exception ex, string message, MessageTipsLevel level)
        {
            FeedInfo?.Invoke(sender, ex, message, level);
            if (ex != null)
                OnException(ex);
        }
        /// <summary>
        /// 手动处理脚本触发的异常
        /// </summary>
        /// <param name="ex"></param>
        protected virtual void OnException(Exception ex)
        {
        }
        /// <summary>
        /// 触发抛出异常
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="message"></param>
        /// <param name="args"></param>
        protected virtual void RaiseException(object sender, string message, params object[] args)
        {
            OnFeedInfo(sender, new Exception(string.Format(message, args)), message, MessageTipsLevel.Error);
            if (!message.Contains("由于目标计算机积极拒绝"))
                throw new Exception(message);
        }
        /// <summary>
        /// 触发消息提示
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="message"></param>
        protected void RaiseInfo(object sender, string message)
        {
            OnFeedInfo(sender, null, message, MessageTipsLevel.Information);
        }
        /// <summary>
        /// 触发警告信息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="message"></param>
        protected void RaiseWarning(object sender, string message)
        {
            OnFeedInfo(sender, null, message, MessageTipsLevel.Warning);
        }

        /// <summary>
        /// 脚本触发界面自定义事件响应
        /// </summary>
        /// <param name="eventType"></param>
        /// <param name="portKey"></param>
        /// <param name="portName"></param>
        /// <param name="data"></param>
        public virtual void RaiseCustomEvent(string eventType, string portKey, string portName, object data)
        {
            try
            {
                FlowContext.Instance.RaiseCustomEvent(this, eventType, portKey, portName, data);
            }
            catch (Exception ex)
            {
                RaiseException(this, ex.Message);
            }
        }
        /// <summary>
        /// 执行开始作业
        /// </summary>
        public virtual void ExecBeginWork()
        {
            try
            {
                BeforeWorkInit();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        /// <summary>
        /// 执行结束作业
        /// </summary>
        public virtual void ExecEndWork()
        {
        }
        /// <summary>
        /// 执行暂停作业
        /// </summary>
        public virtual void ExecPauseWork()
        {
            try
            {
                PauseWorkCache();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                FlowContext.Instance.ResetContext();
            }
        }
        /// <summary>
        /// 开始作业前初始化
        /// </summary>
        protected virtual void BeforeWorkInit()
        {
            RaiseInfo(this,"作业条件检查中...");
        }
        /// <summary>
        /// 结束作业时执行检查
        /// </summary>
        public virtual void StopWorkCheck()
        {
        }
        /// <summary>
        /// 暂停作业时缓存业务临时数据
        /// </summary>
        protected virtual void PauseWorkCache()
        {
        }
    }
}

3.业务窗体

复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using MES.Core;
using MES.Services;
using DataCool_MES.modules.dto;
using MES.Services.ModuleFlowServices;
using System.Net.Sockets;
using System.IO;
using WinControls;

namespace DataCool_MES.modules
{
    [FullScreenStyle(true)]
    [View("批量扫描tray盘混料检测", EntryType.入口, "一次产品检验", "包装作业")]
    public partial class BatchPackForm : BaseModuleForm
    {
        [Import]
        protected IModuleFlowFormService ModuleFlowFormService { get; set; }
        //已扫记录
        private List<BatchScanLogDto> LogData = new List<BatchScanLogDto>();
        private BatchScanLogDto currentBarcodeData = null;
        private WorkStationConfig config;
        private TcpClient cameraClient;
        private TcpClient transmitDeviceClient;
        private byte[] buffer = new byte[2048];
        public BatchPackForm()
        {
            DoubleBuffered = true;
            CheckForIllegalCrossThreadCalls = false;
            InitializeComponent();
            config = SerializerHelper.LoadFromXML<WorkStationConfig>(AppDomain.CurrentDomain.BaseDirectory + "Config\\" + "WorkStationConfig.xml");
            if (config != null)
            {
                FlowContext.Instance.TraySpecifications = config.TraySpecifications;
            }
            Load += BatchPackForm_Load;
            FormClosed += BatchPackForm_FormClosed; 
        }
        private void BatchPackForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (transmitDeviceClient != null)
            {
                try
                {
                    transmitDeviceClient.Client?.Disconnect(false);
                    transmitDeviceClient.Client?.Close();
                    transmitDeviceClient?.Close();
                    transmitDeviceClient = null;
                }
                catch
                {
                }
            }
            Program.Quit();
        }

        AlertLog log;

        internal override AlertLog Log
        {
            get
            {
                if (log == null)
                {
                    Interlocked.CompareExchange(ref log, new AlertLog(richTextBox1), null);
                }
                return log;
            }
        }

        private void BatchPackForm_Load(object sender, EventArgs e)
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            container.ComposeParts(this, new FlowWorkService());
            if (!System.Diagnostics.Debugger.IsAttached)
            {
                btnStart.DataBindings.Add(new Binding("Enabled", FlowContext.Instance, "IsStoped", false, DataSourceUpdateMode.OnPropertyChanged));
                btnStop.DataBindings.Add(new Binding("Enabled", FlowContext.Instance, "IsRunning", false, DataSourceUpdateMode.OnPropertyChanged));
            }

            if (config != null && !string.IsNullOrEmpty(config.TransmissionDeviceIP))
            {
                try
                {
                    transmitDeviceClient = new TcpClient();
                    transmitDeviceClient.BeginConnect(config.TransmissionDeviceIP, config.TransmissionDevicePort, new AsyncCallback(ConnectCallback2), transmitDeviceClient);

                }
                catch (Exception ex)
                {
                    FrameAppContext.GetInstance().AppLogger.Error(ex);
                }
            }
        }

        protected override IModuleFlow CreateModuleFlow()
        {
            return new BatchScanWorkFlow(ModuleFlowFormService);
        }
        protected override void Clear()
        {
            richTextBox1.Text = "";
        }
        private void btnStart_Click(object sender, EventArgs e)
        {
            try
            {
                Start();
                if (FlowContext.Instance.WorkStatus == WorkStatus.Running)
                {
                    cameraClient = new TcpClient();
                    cameraClient.BeginConnect(config.CameraIP, config.CameraPort, new AsyncCallback(ConnectCallback), cameraClient);
                }
                if (FlowContext.Instance.WorkStatus == WorkStatus.Running)
                {
                    try
                    {
                        if (transmitDeviceClient != null)
                        {
                            var sm = transmitDeviceClient.GetStream();
                            SendCommandToDevice(sm, "OK");
                        }
                        else
                        {
                            Log.Write("", "没有和传动设备建立连接!!!", MessageTipsLevel.Error);
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.Write("", "和传动设备通信失败!!!" + Environment.NewLine + ex.Message, MessageTipsLevel.Error);
                    }
                }
            }
            catch (Exception ex)
            {
                FrameAppContext.GetInstance().AppLogger.Error($"{ex.Message}");
            }
        }
        void ConnectCallback(IAsyncResult ar)
        {
            TcpClient client = (TcpClient)ar.AsyncState;
            try
            {
                client.EndConnect(ar);
                NetworkStream stream = client.GetStream();
                if (stream != null)
                {
                    buffer = new byte[2048];
                    stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ReadCallback), stream);
                }
            }
            catch (Exception ex)
            {
                log.Write("", ex.Message, MessageTipsLevel.Error);
            }
        }
        void ReadCallback(IAsyncResult ar)
        {
            NetworkStream stream = (NetworkStream)ar.AsyncState;
            if (FlowContext.Instance.WorkStatus != WorkStatus.Running)
                return;
            int bytesRead = stream.EndRead(ar);
            string responseData = Encoding.ASCII.GetString(buffer, 0, bytesRead);
            log.Write("", $"Received: {Environment.NewLine}{responseData}", MessageTipsLevel.Information);
            FrameAppContext.GetInstance().AppLogger.Info(Environment.NewLine + responseData);
            if (!string.IsNullOrEmpty(responseData) && responseData.IndexOf("+") != -1)
            {
                ScanLog(responseData);
            }
            buffer = new byte[2048];
            stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ReadCallback), stream);
        }
        private void BtnStop_Click(object sender, EventArgs e)
        {
            try
            {
                Stop();
                var config = SerializerHelper.LoadFromXML<WorkStationConfig>(AppDomain.CurrentDomain.BaseDirectory + "Config\\" + "WorkStationConfig.xml");
                FlowContext.Instance.TraySpecifications = config.TraySpecifications;
                if (config != null && !string.IsNullOrEmpty(config.TraySpecifications) &&
                    config.TraySpecifications.IndexOf(",") == -1)
                {
                    string[] cellData = config.TraySpecifications.Split('*');
                    if (cellData.Length > 0)
                    {
                        FlowContext.Instance.TrayRowCount = Convert.ToInt32(cellData[1]);
                        FlowContext.Instance.TrayCellCount = Convert.ToInt32(cellData[0]);
                    }
                }
                if (cameraClient != null && FlowContext.Instance.WorkStatus != WorkStatus.Running)
                {
                    cameraClient.Client?.Disconnect(false);
                    cameraClient.Client?.Close();
                    cameraClient?.Close();
                    cameraClient = null;
                }
                SendCommandToDevice(transmitDeviceClient.GetStream(), "NG");
            }
            catch (Exception ex)
            {
                FrameAppContext.GetInstance().AppLogger.Error($"{ex.Message}");
            }
        }
        protected override void OnFlowCustomEvent(EventClass ec)
        {
            if (ec.EventType.Equals("LoadData"))
            {
                DataAction(LoadData);
            }
            if (ec.EventType.Equals("ExportResultData"))
            {
                DataAction(ExportLogToExcelFile);
            }
        }
        /// <summary>
        /// 从临时xml文件读取扫描历史记录
        /// </summary>
        private void SaveData()
        {
            if (!System.IO.Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + "LocalData"))
            {
                System.IO.Directory.CreateDirectory(AppDomain.CurrentDomain.BaseDirectory + "LocalData");
            }
            if (!System.IO.File.Exists(AppDomain.CurrentDomain.BaseDirectory + "LocalData\\" + DateTime.Now.ToString("yyyyMMdd") + ".xml"))
            {
                SerializerHelper.SerializerToXML(AppDomain.CurrentDomain.BaseDirectory + "LocalData\\" + DateTime.Now.ToString("yyyyMMdd") + ".xml", LogData);
            }
            else
            {
                System.IO.File.Delete(AppDomain.CurrentDomain.BaseDirectory + "LocalData\\" + DateTime.Now.ToString("yyyyMMdd") + ".xml");
                SerializerHelper.SerializerToXML(AppDomain.CurrentDomain.BaseDirectory + "LocalData\\" + DateTime.Now.ToString("yyyyMMdd") + ".xml", LogData);
            }
            FlowContext.Instance.L1Count = LogData.Count;
            if (LogData.Count > 0)
                bindingSourceGrid.DataSource = LogData.OrderByDescending(t => t.ScanDatetime).ToList();
        }
        /// <summary>
        /// 加载已有扫描记录
        /// </summary>
        private void LoadData()
        {
            if (!System.IO.Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + "LocalData"))
            {
                System.IO.Directory.CreateDirectory(AppDomain.CurrentDomain.BaseDirectory + "LocalData");
            }
            if (System.IO.File.Exists(AppDomain.CurrentDomain.BaseDirectory + "LocalData\\" + DateTime.Now.ToString("yyyyMMdd") + ".xml"))
            {
                LogData = SerializerHelper.LoadFromXML<List<BatchScanLogDto>>(AppDomain.CurrentDomain.BaseDirectory + "LocalData\\" + DateTime.Now.ToString("yyyyMMdd") + ".xml");
                FlowContext.Instance.L1Count = LogData.Count;
                if (LogData.Count > 0)
                    bindingSourceGrid.DataSource = LogData.OrderByDescending(t => t.ScanDatetime).ToList();

            }
        }
        /// <summary>
        /// 扫描到一组条码
        /// </summary>
        /// <param name="barcodeData"></param>
        private void ScanLog(string barcodeData)
        {
            if (LogData.Any(t => t.BarcodeData == barcodeData))
            {
                log.Write(DateTime.Now + "->:", "重复扫描!", MessageTipsLevel.Error);
            }
            else
            {
                #region 传递收到的数据
                var entity = new BatchScanLogDto();
                entity.BarcodeData = barcodeData;
                entity.MaterialCode = FlowContext.Instance.MaterielCode;
                entity.ScanDatetime = DateTime.Now.ToString();
                entity.TraySpecifications = FlowContext.Instance.TraySpecifications;
                entity.ResourceText = barcodeData;
                currentBarcodeData = entity;
                #endregion
                //校验数据
                ThreeColorled1_Click(threeColorled1, new EventArgs());
            }
        }
        private void ThreeColorled1_Click(object sender, EventArgs e)
        {
            //必须是符合业务场景的扫描数据
            if (FlowContext.Instance.WorkStatus != WorkStatus.Running ||
                currentBarcodeData == null ||
                currentBarcodeData.BarcodeData.IndexOf("+") == -1)
                return;
            #region 展示解析结果
            BarcodeResultViewForm barcodeResultViewForm = new BarcodeResultViewForm(currentBarcodeData);
            var diagResult = barcodeResultViewForm.ShowDialog();
            if (diagResult == DialogResult.OK &&
                !LogData.Any(r => r.ResourceText == currentBarcodeData.ResourceText))
            {
                threeColorled1.StatusCode = "1";
                log.Write("", "校验通过!", MessageTipsLevel.Information);
                LogData.Add(barcodeResultViewForm.BarcodeData);
                DataAction(SaveData);
                FlowContext.Instance.L1Count = LogData.Count;
                SendCommandToDevice(transmitDeviceClient.GetStream(), "OK");
            }
            else if (diagResult != DialogResult.OK &&
                !LogData.Any(r => r.ResourceText == currentBarcodeData.ResourceText))
            {
                threeColorled1.StatusCode = "2";
                var sm = transmitDeviceClient.GetStream();
                SendCommandToDevice(sm, "NG");
                log.Write("", $"{Environment.NewLine}校验结果是有混料,不予通行!!!", MessageTipsLevel.Warning);
            }
            #endregion
        }
        /// <summary>
        /// 和传动设备建立Socket连接回调
        /// </summary>
        /// <param name="ar"></param>
        void ConnectCallback2(IAsyncResult ar)
        {
            TcpClient client = (TcpClient)ar.AsyncState;
            try
            {
                client.EndConnect(ar);
                Log.Write("", "传动设备已准备就绪...", MessageTipsLevel.Information);
            }
            catch (Exception ex)
            {
                Log.Write("", "传动设备连接失败!"+ex.Message, MessageTipsLevel.Error);
                FrameAppContext.GetInstance().AppLogger.Error(ex);
            }
        }
        /// <summary>
        /// 给NetworkStream发送指令
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="cmd"></param>
        private void SendCommandToDevice(NetworkStream stream, string cmd)
        {
            string command = cmd;
            byte[] commandBuffer = ASCIIEncoding.ASCII.GetBytes(command);
            stream.WriteAsync(commandBuffer, 0, commandBuffer.Length);
        }
        /// <summary>
        /// 导出Excel文件(扫码原始记录)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnExport_Click(object sender, EventArgs e)
        {
            if (LogData.Count == 0)
            {
                MessageTip.ShowError("还没有通过检验的记录不能导出!");
            }
            else
            {
                ExportExcel exportExcel = new ExportExcel();
                string fileName = $"{AppDomain.CurrentDomain.BaseDirectory + "LocalData"}\\{DateTime.Now.Date:yyyyMMdd}{FlowContext.Instance.MaterielCode}.xls";
                exportExcel.GridToExcel(fileName, this.dataGridView1);
                if (File.Exists(fileName))
                {
                    MessageTip.ShowOk($"Excel文件导出成功!");
                }
            }
        }
        /// <summary>
        /// 导出Excel文件(MES要求的格式)
        /// </summary>
        private void ExportLogToExcelFile()
        {
            if (LogData.Count == 0)
                return;
            #region 临时表
            var dt = new DataTable();
            dt.Columns.Add(new DataColumn("条码", typeof(string)));
            foreach (var logItem in LogData)
            {
                string[] barcodeList = logItem.BarcodeData.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var barcode in barcodeList)
                {
                    DataRow dr = dt.NewRow();
                    dr[0] = barcode;
                    dt.Rows.Add(dr);
                }
            }
            #endregion
            try
            {
                ExportExcel exportExcel = new ExportExcel();
                string fileName = $"{AppDomain.CurrentDomain.BaseDirectory + "LocalData"}\\{DateTime.Now.Date:yyyyMMdd}{FlowContext.Instance.MaterielCode}(仅条码).xls";
                if (File.Exists(fileName))
                    File.Delete(fileName);
                exportExcel.DataTableToExcel(fileName, dt);
                if (File.Exists(fileName))
                {
                    MessageTip.ShowOk("生产过程的全部条码已保存!");
                    string logMessage = $"本次生产共计扫描条码{dt.Rows.Count}个,通过tray盘{LogData.Count}盘.";
                    log.Write("", logMessage, MessageTipsLevel.Information);
                    FrameAppContext.GetInstance().AppLogger.Info(logMessage);
                }
            }
            catch (Exception ex)
            {
                log.Write("", ex.Message, MessageTipsLevel.Error);
            }
            finally
            {
                dt.Dispose();
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Program.Quit();
        }

        private void richTextBox1_DoubleClick(object sender, EventArgs e)
        {
            Clear();
        }
    }
}

4.流程代码

复制代码
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
using MES.Core;
using MES.Services;

namespace DataCool_MES.modules
{
    /// <summary>
    /// 批量扫描作业流程
    /// </summary>
    public class BatchScanWorkFlow : ModuleBaseFlow
    {
        private TcpClient cameraClient;
        protected IModuleFlowFormService ModuleFlowFormService { get; set; }
        /// <summary>
        /// 析构函数
        /// </summary>
        /// <param name="svr"></param>
        public BatchScanWorkFlow(IModuleFlowFormService svr)
        {
            ModuleFlowFormService = svr;
        }
        /// <summary>
        /// 和阅读器建立Socket连接回调
        /// </summary>
        /// <param name="ar"></param>
        void ConnectCallback(IAsyncResult ar)
        {
            try
            {

                TcpClient client = (TcpClient)ar.AsyncState;
                client?.EndConnect(ar);
                NetworkStream stream = client.GetStream();
                if (stream != null)
                {
                    RaiseInfo(this, "读码器已准备就绪...");
                    if (client != null)
                    {
                        stream.Close();
                        cameraClient?.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                RaiseException(this, "读码器连接失败!!!错误码:"+ex.Message);
            }
            
        }
        /// <summary>
        /// 给NetworkStream发送指令
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="cmd"></param>
        private void SendCommandToDevice(NetworkStream stream, string cmd)
        {
            string command = cmd;
            byte[] commandBuffer = ASCIIEncoding.ASCII.GetBytes(command);
            stream.WriteAsync(commandBuffer, 0, commandBuffer.Length);
        }
        /// <summary>
        /// 和传动设备建立Socket连接回调
        /// </summary>
        /// <param name="ar"></param>
        void ConnectCallback2(IAsyncResult ar)
        {
            try
            {
                TcpClient client = (TcpClient)ar.AsyncState;
                client?.EndConnect(ar);
                NetworkStream stream = client.GetStream();
                if (stream != null)
                {
                    //发消息让皮带开始转动
                    SendCommandToDevice(stream, "OK");
                }
            }
            catch (Exception ex)
            {
                RaiseException(this, "传动设备连接失败!!!错误码:" + ex.Message);
            }
        }
        /// <summary>
        /// 开始作业前准备工作
        /// </summary>
        protected override void BeforeWorkInit()
        {
            base.BeforeWorkInit();
            if (string.IsNullOrEmpty(FlowContext.Instance.TraySpecifications))
            {
                RaiseException($"{DateTime.Now}->:", $"必须设定容器的规格!");
            }
            if (string.IsNullOrEmpty(FlowContext.Instance.MaterielCodeHeader+FlowContext.Instance.MaterielCodeEnder))
            {
                RaiseException($"{DateTime.Now}->:", $"必须输入本次校验的物料代码!");
            }
            flowContext.L1Count = 0;
            RaiseCustomEvent("LoadData", "", "", "");
            var config = SerializerHelper.LoadFromXML<WorkStationConfig>(AppDomain.CurrentDomain.BaseDirectory + "Config\\" + "WorkStationConfig.xml");
            if (config != null && !string.IsNullOrEmpty(config.CameraIP))
            {
                try
                {
                    cameraClient = new TcpClient();
                    cameraClient.BeginConnect(config.CameraIP, config.CameraPort, new AsyncCallback(ConnectCallback), cameraClient);
 }
                catch (Exception ex)
                {
                    RaiseException(this,"",ex.Message);
                }
            }
            else
            {
                RaiseException($"{DateTime.Now}->:", $"必须设定阅读器的IP地址!");
            }
            if (config != null && !string.IsNullOrEmpty(config.TraySpecifications) && config.TraySpecifications.IndexOf(",") == -1)
            {
                string[] cellData = config.TraySpecifications.Split('*');
                if (cellData.Length > 0)
                {
                    flowContext.TrayRowCount = Convert.ToInt32(cellData[1]);
                    flowContext.TrayCellCount = Convert.ToInt32(cellData[0]);
                }
            }
            FlowContext.Instance.MaterielCode= FlowContext.Instance.MaterielCodeHeader+FlowContext.Instance.MaterielCodeEnder;
            RaiseInfo(this, "系统准备就绪...");
        }
        /// <summary>
        /// 扫描到一个条码
        /// </summary>
        /// <param name="data"></param>
        public override void OnExecScanReceiving(string data)
        {
            RaiseCustomEvent("ScanLog", "", "", data);
        }
        /// <summary>
        /// 结束本次作业
        /// </summary>
        public override void ExecEndWork()
        {
            base.ExecEndWork();
            //导出扫描记录
            RaiseCustomEvent("ExportResultData", "", "", "");
        }
    }
}

5.界面绑定代码

复制代码
 protected override IModuleFlow CreateModuleFlow()
 {
     return new BatchScanWorkFlow(ModuleFlowFormService);
 }

界面和代码中间还有个上下文对象,也就是一个单例,貌似这样子:

复制代码
using EES.Common;
using MES.Core;
using System;
using System.ComponentModel;  

namespace DataCool_MES
{
    /// <summary>
    /// 工作流程上下文对象
    /// </summary>
    [Serializable]
    public class FlowContext : SingletonProvider<FlowContext>, INotifyPropertyChanged
    {
        public System.Configuration.Configuration AppConfig { get; set; }
        public event EventHandler<TEventArgs<EventClass>> FlowCustomEvent;
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        } 

        private WorkStatus workStatus;
        /// <summary>
        /// 工作流程运行状态
        /// </summary>
        public WorkStatus WorkStatus
        {
            get { return workStatus; }
            set
            {
                workStatus = value;
                OnPropertyChanged("WorkStatus");
                switch (value)
                {
                    case WorkStatus.Running:
                        IsRunning = true;
                        IsStoped = false;
                        WorkStatusName = "运行中...";
                        break;
                    case WorkStatus.Pauseing:
                        IsRunning = false;
                        IsStoped = true;
                        WorkStatusName = "暂停中...";
                        break;
                    case WorkStatus.Stopped:
                        IsRunning = false;
                        IsStoped = true;
                        WorkStatusName = "已经停止...";
                        break;
                    default:
                        IsRunning = false;
                        IsStoped = true;
                        WorkStatusName = "未启动";
                        break;
                }
            }
        }

        private string workStation;
        /// <summary>
        /// 工位
        /// </summary>
        public string WorkStation
        {
            get { return workStation; }
            set
            {
                workStation = value;
                OnPropertyChanged("WorkStation");
            }
        }

        private string _operator;
        /// <summary>
        /// 工位操作员
        /// </summary>
        public string Operator
        {
            get { return _operator; }
            set { _operator = value; OnPropertyChanged("Operator"); }
        }

        private string lineNo;
        /// <summary>
        /// 线号
        /// </summary>
        public string LineNo
        {
            get { return lineNo; }
            set { lineNo = value; OnPropertyChanged("LineNo"); }
        }

        private string _L1Code;
        /// <summary>
        /// PCBA条码
        /// </summary>
        public string L1Code
        {
            get { return _L1Code; }
            set { _L1Code = value; OnPropertyChanged("L1Code"); }
        }

        private string _L2Code;
        /// <summary>
        /// 过程条码
        /// </summary>
        public string L2Code
        {
            get { return _L2Code; }
            set { _L2Code = value; OnPropertyChanged("L2Code"); }
        }

        private string workStatusName;
        /// <summary>
        /// 状态状态描述信息
        /// </summary>
        public string WorkStatusName
        {
            get { return workStatusName; }
            set { workStatusName = value; OnPropertyChanged("WorkStatusName"); }
        }

        private int _L1Count;
        /// <summary>
        /// 第一道工序计数
        /// </summary>
        public int L1Count
        {
            get { return _L1Count; }
            set
            {
                _L1Count = value;
                OnPropertyChanged("L1Count");
            }
        }

        private int _L1CountEx = 0;
        /// <summary>
        /// 制令计数,可清零
        /// </summary>
        public int L1CountEx
        {
            get { return _L1CountEx; }
            set
            {
                _L1CountEx = value; 
                OnPropertyChanged("L1CountEx");
            }
        }

        private int _L2Count;
        /// <summary>
        /// 第二道工序计数
        /// </summary>
        public int L2Count
        {
            get { return _L2Count; }
            set { _L2Count = value; OnPropertyChanged("L2Count"); }
        }

        private string _TraySpecifications;
        /// <summary>
        ///Tray Specifications
        /// </summary>
        public string TraySpecifications
        {
            get { return _TraySpecifications; }
            set
            {
                _TraySpecifications = value;
                OnPropertyChanged("TraySpecifications");
            }
        }

        public int TrayCellCount
        {
            get;set;
        }

        public int TrayRowCount
        {
            get; set;
        }

        private string orderNo;
        /// <summary>
        /// 状态状态描述信息
        /// </summary>
        public string OrderNo
        {
            get { return orderNo; }
            set
            {
                orderNo = value;
                OnPropertyChanged("OrderNo");
            }
        }
        private string _MaterielCode;
        /// <summary>
        /// 物料编码
        /// </summary>
        public string MaterielCode
        {
            get { return _MaterielCode; }
            set { _MaterielCode = value; OnPropertyChanged("MaterielCode"); }
        }

        private string _MaterielCodeEnder;
        /// <summary>
        /// 物料编码
        /// </summary>
        public string MaterielCodeEnder
        {
            get { return _MaterielCodeEnder; }
            set { _MaterielCodeEnder = value; OnPropertyChanged("_MaterielCodeEnder"); }
        }
        private string _MaterielCodeHeader;
        /// <summary>
        /// 物料编码
        /// </summary>
        public string MaterielCodeHeader
        {
            get { return _MaterielCodeHeader; }
            set { _MaterielCodeHeader = value; OnPropertyChanged("MaterielCodeHeader"); }
        }
        private int progress;
        /// <summary>
        /// 本次扫描完成时的进度
        /// </summary>
        public int Progress
        {
            get { return progress; }
            set { progress = value; OnPropertyChanged("Progress"); }
        }

        private bool isStoped = true;
        /// <summary>
        /// WorkStatus是否已经停止
        /// </summary>
        public bool IsStoped
        {
            get { return isStoped; }
            set { isStoped = value; OnPropertyChanged("IsStoped"); }
        }

        private bool isRunning = false;
        /// <summary>
        /// WorkStatus是否正在运行
        /// </summary>
        public bool IsRunning
        {
            get { return isRunning; }
            set { isRunning = value; OnPropertyChanged("IsRunning"); }
        }

        private string _Parameter1;
        /// <summary>
        /// 公用参数1
        /// </summary>
        public string Parameter1
        {
            get { return _Parameter1; }
            set { _Parameter1 = value; OnPropertyChanged("Parameter1"); }
        }

        private string _Parameter2;
        /// <summary>
        /// 公用参数2
        /// </summary>
        public string Parameter2
        {
            get { return _Parameter2; }
            set { _Parameter2 = value; OnPropertyChanged("Parameter2"); }
        }

        private string _Parameter3;
        /// <summary>
        /// 公用参数3
        /// </summary>
        public string Parameter3
        {
            get { return _Parameter3; }
            set { _Parameter3 = value; OnPropertyChanged("Parameter3"); }
        }

        private string _Parameter4;
        /// <summary>
        /// 公用参数4
        /// </summary>
        public string Parameter4
        {
            get { return _Parameter4; }
            set { _Parameter4 = value; OnPropertyChanged("Parameter4"); }
        }

        private string _Parameter5;
        /// <summary>
        /// 公用参数5
        /// </summary>
        public string Parameter5
        {
            get { return _Parameter5; }
            set { _Parameter5 = value; OnPropertyChanged("Parameter5"); }
        }

        private string _Parameter6;
        /// <summary>
        /// 公用参数6
        /// </summary>
        public string Parameter6
        {
            get { return _Parameter6; }
            set { _Parameter6 = value; OnPropertyChanged("Parameter6"); }
        }

        private string _Parameter7;
        /// <summary>
        /// 公用参数7
        /// </summary>
        public string Parameter7
        {
            get { return _Parameter7; }
            set { _Parameter7 = value; OnPropertyChanged("Parameter7"); }
        }

        private string _Parameter8;
        /// <summary>
        /// 公用参数8
        /// </summary>
        public string Parameter8
        {
            get { return _Parameter8; }
            set { _Parameter8 = value; OnPropertyChanged("Parameter8"); }
        }

        private string _Parameter9;
        /// <summary>
        /// 公用参数9
        /// </summary>
        public string Parameter9
        {
            get { return _Parameter9; }
            set { _Parameter9 = value; OnPropertyChanged("Parameter9"); }
        }

        private int packagePCS;
        /// <summary>
        /// 二级包装,PCS数量
        /// </summary>
        public int PackagePCS_Count
        {
            get { return packagePCS; }
            set { packagePCS = value; OnPropertyChanged("PackagePCS_Count"); }
        }

        private bool isEndPacking=false;
        /// <summary>
        /// 是否是在装尾箱
        /// </summary>
        public bool IsEndPacking
        {
            get { return isEndPacking; }
            set { isEndPacking= value; OnPropertyChanged("IsEndPacking"); }
        }
  
        private int _WorkEndNumber;
        public int WorkEndNumber
        {
            get { return _WorkEndNumber; }
            set
            {
                _WorkEndNumber = value;
                OnPropertyChanged("WorkEndNumber");
            }
        }

        public void RaiseCustomEvent(object sender, string eventType, string portKey, string portName, object data)
        {
            RaiseCustomEvent(sender, eventType, portKey, portName, data, null);
        }

        public void RaiseCustomEvent(object sender, string eventType, string portKey, string portName, object data,
            object extend)
        {
            if (FlowCustomEvent != null)
            {
                EventClass ec = new EventClass(sender, eventType, portKey, portName, data, extend);
                FlowCustomEvent(this, new TEventArgs<EventClass>(ec));
            }
        }
        /// <summary>
        /// 重置上下文
        /// </summary>
        public void ResetContext()
        {
            OrderNo = string.Empty;
            MaterielCode = string.Empty;
            Progress = 0;
            L1Code = string.Empty;
            L1Count = 0;
            L2Code = "";
            L2Count = 0;
            L1CountEx = 0;
            TraySpecifications = "";
            Parameter1 = "";
            Parameter2 = "";
            Parameter3 = "";
            Parameter4 = "";
            Parameter5 = "";
            Parameter6 = "";
            Parameter7 = "";
            Parameter8 = "";
            Parameter9 = "";
            IsEndPacking = false;
        }
    }
}

好了,我的言传就是这样。我的表达能力就只能到这样了。