办法总比困难多(不会或不想用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; } } }
好了,我的言传就是这样。我的表达能力就只能到这样了。