简介本资源为基于 DevExpress v23.1 开发的固定式扫码系统 C# 源码面向从事工业自动化、产线数据采集与上位机开发的 C# 工程师及学习者。项目围绕固定式扫码枪的串口通信展开封装了 SerialPort 工具类包含串口对象管理、接收状态控制与数据回调事件等核心逻辑并完善了扫描报警与数据库查询功能可直接用于产线扫码追溯、条码采集等场景的二次开发。压缩包共 572 个文件约 120.67MB以 170 个 dll 依赖库、107 个 xml 配置文档、28 个 cs 源码文件为主另含 nupkg 包、pdb 调试文件、resx 资源及 sql 脚本等覆盖编译、配置与部署所需内容。目前已有 175 人学习下载。读者可获取完整的串口通信封装思路、报警处理与数据库查询实现以及 DevExpress 界面与扫码业务的整合方式适合作为上位机扫码项目的参考模板与排错借鉴。1. 固定式扫码系统为什么还在用 WinForms 这套老骨架产线上那台扫码枪架在传送带上方工件一过红光一闪条码内容要在一百毫秒内落到 MES 的数据库里同时屏幕上得立刻刷出这一条记录。这种场景下WPF 的动画再顺滑也没人看Web 页面再现代也扛不住车间断网真正被反复验证过的组合反而是 DevExpress v23.1 加 WinForms。标题里的「固定式扫码系统」说的就是这类设备端软件扫码器固定安装工件移动软件常驻运行7×24 小时不关操作工只做三件事——看结果、补录异常、导出报表。DevExpress v23.1 在这个组合里的价值不在花哨控件而在 GridControl 的虚拟滚动、LookUpEdit 的即时过滤、以及 Scheduler 和报表模块能直接拼出班次统计。C# 源码层面它是一套典型的「串口/网口收码 → 解析 → 校验 → 入库 → 界面刷新」的桌面程序。适合谁读手上有一台固定式扫码器、需要做上位机、又不想从零画表格的 C# 开发者或者已经有一套老系统想升级到 v23.1 但不确定控件 API 变了多少的人。下面按我实际搭过一遍的顺序讲从环境到收码到避坑能直接抄。2. 把 DevExpress v23.1 和扫码链路搭起来环境、控件与最小可跑框架2.1 为什么选 WinForms DevExpress 而不是自己画 DataGridView固定式扫码系统的界面本质是一张不断追加行的表。原生 DataGridView 在几千行以后开始卡几万行直接假死而产线一个班次轻松过万条记录。DevExpress 的 GridControl 默认开启虚拟模式只渲染可视区域的行滚动条拖动时不会一次性创建所有 Row 对象。v23.1 里这个行为由GridView.OptionsBehavior下的EnableSmartRefresh和OptionsView.RowAutoHeight共同影响后者如果打开每行都要测量高度虚拟化收益会被吃掉一半。我一般把RowAutoHeight关掉固定行高 28 像素一万行滚动依然跟手。另一个理由是 LookUpEdit。补录异常时操作工要从产品型号列表里选原生 ComboBox 在数据源上千条时下拉会卡顿DevExpress 的 LookUpEdit 带增量搜索输入两个字符就过滤配合Properties.TextEditStyle DisableTextEditor可以防止乱填。这些控件在 v23.1 里已经稳定不需要额外装 NuGet 包装完 DevExpress 的安装器后工具箱里直接有。2.2 环境准备与项目骨架先确认三件事DevExpress v23.1 已安装并激活、项目目标框架选 .NET Framework 4.7.2 或以上v23.1 也支持 .NET 6/7 的 WinForms但产线机器常是 Win10 老系统4.7.2 兼容性最好、扫码器驱动已装好并能用厂商工具收到码。新建 WinForms 项目后把主窗体拆成三块顶部状态栏、中间 GridControl、底部补录面板。// MainForm.cs —— 最小骨架只保留扫码系统必需的部分 using DevExpress.XtraEditors; using DevExpress.XtraGrid; using DevExpress.XtraGrid.Views.Grid; using System.IO.Ports; public partial class MainForm : XtraForm { private SerialPort _port; // 串口扫码器走这里 private GridControl _grid; // DevExpress 表格 private GridView _view; // 表格视图虚拟化开关在它身上 private System.Windows.Forms.Timer _flushTimer; // 批量刷界面用 public MainForm() { InitializeComponent(); BuildUi(); InitSerial(); } private void BuildUi() { _grid new GridControl { Dock DockStyle.Fill }; _view new GridView(_grid); _grid.MainView _view; _grid.Dock DockStyle.Fill; // 关键关掉自动行高保住虚拟滚动性能 _view.OptionsView.RowAutoHeight false; _view.RowHeight 28; _view.OptionsBehavior.Editable false; // 主表只读补录走独立面板 Controls.Add(_grid); } private void InitSerial() { _port new SerialPort(COM3, 9600, Parity.None, 8, StopBits.One); _port.DataReceived OnDataReceived; _port.Open(); } }这段代码的逻辑说明BuildUi里创建 GridControl 和 GridViewRowAutoHeight false是性能分水岭Editable false防止操作工误改已入库数据。InitSerial里串口参数 9600-8-N-1 是多数固定式扫码器的出厂默认但实际要以厂商手册为准改错一个参数就是收不到码。参数说明COM3换成设备管理器里看到的端口号DataReceived是串口事件它在后台线程触发不能直接碰控件后面会讲怎么切回 UI 线程。2.3 收码、解析与线程切换固定式扫码器一般有两种输出串口和网口 TCP。串口简单网口适合长距离。不管哪种收到的都是一串字节末尾常带\r\n或\r。解析要做三件事去空白、校验前缀后缀、判断条码类型。很多产线条码带固定前缀比如两位工厂码加日期解析时按位截取比正则快。private void OnDataReceived(object sender, SerialDataReceivedEventArgs e) { // 注意此方法在后台线程执行禁止直接操作 _grid string raw _port.ReadLine().Trim(); // ReadLine 依赖 NewLine 属性默认 \n if (string.IsNullOrEmpty(raw)) return; var record ParseBarcode(raw); if (record null) return; // 切回 UI 线程再刷界面否则跨线程异常 BeginInvoke(new Action(() { _view.AddNewRow(); // 追加一行 _view.SetRowCellValue(_view.FocusedRowHandle, Code, record.Code); _view.SetRowCellValue(_view.FocusedRowHandle, Time, record.Time); _view.UpdateCurrentRow(); })); } private ScanRecord ParseBarcode(string raw) { // 假设格式两位工厂码 12 位序列号 1 位校验 if (raw.Length 15) return null; string factory raw.Substring(0, 2); string serial raw.Substring(2, 12); if (!VerifyCheckDigit(serial, raw[14])) return null; // 校验失败直接丢 return new ScanRecord { Code serial, Factory factory, Time DateTime.Now }; }逻辑说明ReadLine会阻塞到收到换行符所以扫码器必须配置成每条码后发\r\n否则会一直等。BeginInvoke是 WinForms 里把回调排到 UI 消息队列的标准做法比Invoke安全不会在窗体已销毁时抛异常。ParseBarcode里先判长度再截取避免Substring越界校验位算法各厂不同常见的是模 10 或模 36这里用占位函数表示实际要按条码规范实现。参数说明raw.Length 15这个阈值随条码规则变改之前先拿真实码量一遍长度分布。2.4 数据落库与界面刷新的节流如果每收一条码就INSERT一次数据库SQLite 还能扛SQL Server 在高峰期会明显拖慢。我一般用两个缓冲内存里攒 50 条或满 500 毫秒批量写一次界面刷新也走同一个定时器避免高频AddNewRow导致重绘抖动。private readonly ListScanRecord _buffer new ListScanRecord(); private readonly object _lock new object(); private void OnFlushTick(object sender, EventArgs e) { ListScanRecord batch; lock (_lock) { if (_buffer.Count 0) return; batch new ListScanRecord(_buffer); _buffer.Clear(); } BulkInsert(batch); // 批量入库具体 SQL 按数据库类型写 RefreshGrid(batch); // 批量刷界面 }逻辑说明_lock保护_buffer因为收码在后台线程、定时器在 UI 线程两边都会碰它。BulkInsert用事务包住多条INSERTSQLite 下能快一个数量级。RefreshGrid里如果一次加几百行建议先_grid.BeginUpdate()再EndUpdate()否则每加一行都触发一次布局计算。参数说明50 条和 500 毫秒是经验值产线节拍快就调小批量、调短间隔节拍慢就反过来减少数据库压力。3. 固定式扫码系统的核心参数串口、触发方式与条码校验怎么定3.1 串口参数与触发模式的选择固定式扫码器的触发方式分三种常亮、外部光电触发、命令触发。常亮最省事但扫码器一直读容易把相邻工件上的码也读进来外部光电触发靠产线上的光电开关给信号最准但要多接一根线命令触发是上位机发指令让扫码器读一次适合节拍可控的场景。我一般优先推外部光电触发因为固定式安装位置固定光电开关能精确卡住工件到位那一刻。串口参数里最容易翻车的是流控。默认Handshake.None但有些扫码器数据量大时会要求RequestToSend。如果发现收码偶尔丢字符先把流控改成RequestToSend试再查波特率是否和扫码器一致。波特率 9600 在长条码下会成瓶颈一条 30 字符的码要 30 毫秒左右节拍快就上 115200。参数常见值什么时候要改波特率9600 / 115200条码长、节拍快时上调数据位8几乎不动校验位None扫码器要求偶校验时改 Even停止位One不动流控None丢字符时改 RequestToSend触发方式外部光电常亮误读时改光电或命令3.2 条码校验与重复码过滤产线上同一个码被读两次很常见尤其是工件在扫码器下停留时。去重逻辑不能只靠数据库唯一索引因为批量入库时冲突会整批失败。我一般在内存里维护一个最近 200 条码的队列新码进来先查队列命中就丢。队列用Queuestring加HashSetstring组合查得快、淘汰也快。private readonly Queuestring _recent new Queuestring(); private readonly HashSetstring _recentSet new HashSetstring(); private const int DedupWindow 200; private bool IsDuplicate(string code) { if (_recentSet.Contains(code)) return true; _recent.Enqueue(code); _recentSet.Add(code); if (_recent.Count DedupWindow) { string old _recent.Dequeue(); _recentSet.Remove(old); } return false; }逻辑说明HashSet负责 O(1) 查询Queue负责按顺序淘汰最老的码。DedupWindow设 200 是因为一个工件从进入到离开扫码区最多几十条码200 足够覆盖。参数说明如果产线有回读场景同一个码隔几分钟又出现窗口要按时间算而不是按条数那就得存时间戳复杂度上升一般没必要。3.3 界面刷新与操作工反馈操作工不看数据库只看屏幕。收码成功要绿底闪一下失败要红底加声音。DevExpress 的 GridView 支持RowStyle事件按行数据改背景色。但注意RowStyle在虚拟滚动下只对可视行触发所以颜色逻辑要轻别在里面查数据库。private void View_RowStyle(object sender, RowStyleEventArgs e) { if (e.RowHandle 0) return; var status _view.GetRowCellValue(e.RowHandle, Status)?.ToString(); if (status OK) e.Appearance.BackColor Color.FromArgb(220, 255, 220); else if (status NG) e.Appearance.BackColor Color.FromArgb(255, 220, 220); }逻辑说明e.RowHandle 0是过滤行等特殊行必须跳过。颜色用浅色深色在车间强光下反而看不清。参数说明Status列的值在入库时写死不要在RowStyle里做判断逻辑否则滚动时会重复计算。4. 避坑与排查固定式扫码系统上线后最容易翻车的五件事4.1 跨线程操作控件导致随机崩溃现象程序跑几小时后突然弹「线程间操作无效」或者直接闪退日志里没有明显规律。原因串口DataReceived在后台线程触发如果直接_view.AddNewRow()WinForms 的消息泵在特定时序下会抛异常。解决所有碰控件的地方一律走BeginInvoke包括改状态栏文字、弹提示框。别用CheckForIllegalCrossThreadCalls false去关检查那只是把异常藏起来该崩还是崩。4.2 批量入库时数据库锁死现象高峰期界面卡住十几秒之后一次性刷出几百条。原因每条码单独开事务SQLite 写锁被反复争抢或者 SQL Server 的连接池被占满。解决改成批量事务50 条一提交连接用using包住及时释放如果还是慢把入库放到独立线程界面只负责显示两者用队列解耦。4.3 扫码器丢字符或读到半个码现象偶尔出现长度不对的码或者校验位对不上。原因波特率不匹配、流控没开、或者串口线太长受干扰。解决先换短线下测试排除线材再把流控改成RequestToSend最后检查扫码器的结束符配置有些扫码器默认只发\r而ReadLine等的是\n会一直阻塞到超时。4.4 DevExpress 控件版本不匹配导致设计器报错现象打开窗体设计器提示找不到某某类型或者运行时控件显示成空白。原因项目引用的 DevExpress 版本和安装的 v23.1 不一致常见于从旧项目升级。解决用 DevExpress 自带的 Project Converter 工具统一升级别手动改引用升级后清理bin和obj再重新生成否则旧程序集会残留。4.5 界面行数过万后滚动卡顿现象跑了一个班次后拖动滚动条明显掉帧。原因RowAutoHeight没关或者RowStyle里做了重计算或者绑定了DataSource而不是逐行追加。解决关自动行高RowStyle只读缓存好的状态列如果数据量真的很大改用分页或只显示最近 500 条历史数据走查询面板。5. 进阶把固定式扫码系统做成可远程查历史、可导出报表的形态基础版跑通后下一步通常是让主管在办公室也能看数据。最省事的做法是在同一台产线机器上起一个极简的 HTTP 服务用HttpListener就行不需要额外框架。它只暴露两个接口一个按时间范围查记录一个导出 CSV。这样不引入 Web 服务器也不影响扫码主线程。private void StartHttpListener() { var listener new HttpListener(); listener.Prefixes.Add(http://:8080/scan/); listener.Start(); Task.Run(async () { while (true) { var ctx await listener.GetContextAsync(); string path ctx.Request.Url.AbsolutePath; if (path /scan/query) { string from ctx.Request.QueryString[from]; string to ctx.Request.QueryString[to]; var rows QueryRecords(from, to); // 查数据库 byte[] buf System.Text.Encoding.UTF8.GetBytes(ToJson(rows)); ctx.Response.ContentType application/json; ctx.Response.OutputStream.Write(buf, 0, buf.Length); } ctx.Response.Close(); } }); }逻辑说明HttpListener的前缀需要管理员权限或 URL ACL 注册产线机器一般用管理员账号跑问题不大。Task.Run里循环处理请求QueryRecords走只读连接不干扰写入。参数说明端口 8080 别和扫码器厂商的调试端口冲突先netstat查一下。导出 CSV 时注意条码里的逗号用双引号包住字段。验证方法很简单浏览器打开http://产线机器IP:8080/scan/query?from2025-01-01to2025-01-02能返回 JSON 就说明通了。如果返回 503多半是 URL ACL 没注册用netsh http add urlacl加一下当前用户即可。最后说个我自己的习惯每次改完扫码逻辑先拿一个固定条码在扫码器前晃十次看是不是稳定去重再拿一个错误码晃三次看是不是稳定拒绝。这两个动作花不了一分钟但能挡掉八成上线后的翻车。固定式扫码系统这东西功能不复杂难的是长时间跑不出错参数和线程这两块盯住了基本就稳了。希望帮到你。本文还有配套的精品资源点击获取