一、工业曲线图:设计与实现
1. 先认清原生 Chart 的边界
原生 .NET Framework 的 Chart 控件支持 Line、Spline、FastLine 等多种类型,具备数据绑定、X/Y 轴自动缩放、滚动视图、实时添加数据点、触发重绘等基础能力。但默认性能在高频率(>50Hz)更新场景下易出现卡顿、丢帧、UI 假死,根源在于 Chart 控件采用 GDI+ 绘图且未做深度优化,每次 AddXY 都会触发完整重绘,导致大量冗余像素计算与句柄操作。
所以选型上有个基本判断:低频展示(秒级刷新、几十个点)用 Chart 足够;高频多通道就要考虑自绘或轻量库。
2. 自绘曲线的技术要点
专业级实时曲线系统往往需绕过 Chart 控件,基于 Graphics 对象进行底层手动绘图:通过双缓冲(SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true))消除闪烁;使用 Point 数组预分配内存池避免 GC 抖动;采用增量式坐标变换(将原始数据值经线性映射转为屏幕像素坐标);引入环形缓冲区管理最近 N 个数据点,支持无限滚动与历史回溯;结合 Timer 或 DispatcherTimer 控制刷新节奏,并配合 BackgroundWorker 或 Task.Run 将数据采集逻辑移出 UI 线程,再通过 Invoke/BeginInvoke 跨线程安全更新绘图缓存。
一个健壮的曲线控件至少应提供:多通道支持(同一坐标系叠加多条不同颜色/样式的曲线)、动态 Y 轴范围自适应(MinMax 算法实时统计窗口内极值并平滑缩放)、时间轴滚动与缩放、数据标注与光标追踪、导出功能(PNG/SVG 或 CSV 原始数据)。
3. 多通道场景的实战模式
以采集卡 32 路为例,比较成熟的做法是:数据采集端使用 System.Threading.Timer 或后台线程以高频(如 20ms 间隔)读取硬件数据,将解析后的 32 通道数据点封装后存入线程安全队列;UI 刷新端使用 System.Windows.Forms.Timer 以固定频率(如 33ms/30FPS)从队列中批量取出数据,避免逐点刷新导致的界面卡顿。图表初始化时为各通道分别创建 LineItem,绑定独立的 PointPairList 或 RollingPointPairList(容量设为 1000 或略大以留缓冲),设置 Symbol.IsVisible=false 减少渲染开销;点数超过阈值时移除旧数据实现滑动窗口;坐标轴采用固定范围(MaxAuto=false)或仅在数据变化超阈值时调用 AxisChange(),常规刷新仅调用 Invalidate() 标记重绘区域。多通道数据需通过 YAxisList 支持多 Y 轴或统一量纲映射。
4. 案例分析:CPU 从 70% 降到 8% 的思路
有个可参考的优化路径:核心是"只入队、批量刷"。采集回调里只做解析和 Enqueue,不碰 UI;用一个 100ms 的定时器批量取出缓存数据,再在 Invoke 里一次性更新图表和表格。这样把 UI 更新频率从 10ms/次降至 100ms/次,单次更新数据量增加但总绘制次数减少,图表类控件 CPU 占用可降低 50% 以上。
配套两件事:DataGridView 启用虚拟模式(VirtualMode=true)只渲染可见区域的行,有实测 100 万行数据时 CPU 占用从 30% 降至 2%;图表侧限制绘制区域、只保留最近 1000 个数据点,超过则移除旧数据,并用 Invalidate() 而非 Refresh() 只重绘图表区域。
二、生产报表导出
1. 库的选型
核心两个库:NPOI 无 Office 依赖,是工控首选;EPPlus 仅支持 xlsx 且商用需授权。现场工控机大概率没装 Office,所以优先 NPOI。
2. 导出与导入的基本骨架
导出侧按扩展名区分工作簿(xlsx 用 XSSFWorkbook,xls 用 HSSFWorkbook),固定表头适配产线、时间、数值、状态等字段,填数据时注意空值处理,最后 AutoSizeColumn 自适应列宽并用 FileStream 保存。
导入侧必须带校验:从第 2 行开始读跳过表头,对必填项(如设备编号)做非空判断并抛带行号的异常,数值字段用空值赋默认,日期字段用 TryParse 做格式容错。
3. 工控场景的几个必加项
超过 1 万行的导入导出要分批读写避免内存溢出;数据校验要覆盖必填项、数据范围(比如转速不能为负)和格式,防止写入 PLC 出错;路径选择用 SaveFileDialog 和 OpenFileDialog 贴合 WinForm/WPF;异常要包裹 try-catch 并提示具体行号,方便现场排查。
另外提醒一点:导出"参数模板"给现场填写时,最好锁定表头只允许编辑数据行,减少误改风险。
三、报警中心开发
1. 防抖是能不能验收的关键
裸奔的告警逻辑在现场基本没法直接用:传感器信号干扰导致数值瞬间跳一下马上恢复、485/网口采集偶尔出现瞬时脏数据、设备启停瞬间数值抖动,不做防抖系统会一秒几十条误告警,运维直接崩溃、项目验收不通过。
工业级标准逻辑是:数值第一次超限不立即告警,必须连续 N 次采集或持续 N 秒异常才触发;恢复同样需要防抖,数值回落正常后要持续稳定再消除告警。实现上给规则加 filterCount 和 recoverFilterCount 两个字段,配合一个按 pointId 缓存 errorCount、normalCount、isAlarmStatus 的结构,异常时异常次数累加、正常次数清零,达到阈值才置位;正常时反之,持续正常达到恢复阈值才解除。
2. 压缩与抑制:不止防抖
成熟告警中心的压缩机制通常包含几种:告警去重(对未提供唯一标识的告警源,通过一个或多个字段生成事件唯一 ID,默认以告警对象、告警指标、告警等级三者生成,重复 ID 自动计数而不产生新事件);防抖收敛(通过 X 时间周期内发生 N 次的规则收敛抖动类无效告警);时间屏蔽(变更维护期内对指定对象的告警自动屏蔽);依赖屏蔽(当 A 对象发生 X 告警时,屏蔽 T 时间内 B 对象的告警)。
处理与分派环节也值得参考:自愈处理针对处理操作固定的常见告警做自动化,同时增加人工审批环节让自愈更可控;自动转工单适合跨团队协作场景,一般同样采用插件化设计接入不同工单系统;分派上可配置升级机制,告警一定时间未处理时自动升级给上级或其他管理员。
3. 接入层建议插件化
考虑到要适配各种告警源,把各类接入模块抽象为独立插件、定义插件开发框架和告警标准格式,落地时仅需开发对应的告警源插件,随插件积累复用可进一步降低接入成本。这个思路和你在 C# 侧做协议驱动抽象是一致的。
四、仪表盘制作
1. 区分"单机仪表"和"监控大屏"
这是两类东西,别混着设计。
单机/单参数仪表:用于展示单一模拟量的当前值与范围(如转速、压力、温度),优先使用圆形仪表盘或线性仪表盘,支持数值显示、刻度标注、超限标红,适配工业现场的传统操作习惯。
监控大屏/驾驶舱:定位是"决策驾驶舱",集成 3D 场景、关键指标 KPI、实时告警、视频监控、数据分析图表等元素,设计遵循"核心突出、信息分层"原则——中央区域通常展示核心 3D 孪生体作为视觉焦点,周围环绕最重要的 KPI 仪表盘,侧边或底部设置实时告警滚动条和视频监控窗口,且各元素实时联动。
2. 视觉与可读性硬指标
工控 UI 一般要求:核心数据与报警文字字号建议 ≥24px,普通按钮与标签 ≥18px,辅助说明 ≥14px;文字与背景对比度至少 7:1;状态显示遵循工业标准色(正常绿、警告黄、错误红、待机灰),并采用"形状+文字+颜色"三重保险机制;同一界面主题色不宜超过 3 种。
大屏侧的规范略有不同:主色 1 个、辅助色 1-2 个、点缀色 1 个,总颜色不超过 4 种,色相对比度 ≥4.5:1,避免彩虹色、过度渐变及霓虹光效;布局还需考虑液晶拼接屏间隙,严禁关键数据、核心指标被拼缝分割。
3. 图表选择与数据分层
比较通用的对应关系:比较类用柱状图,流程类用漏斗图,占比类用饼图/环形图,区间类用仪表盘,趋势类用折线图/面积图,空间分布用热力图/散点图/地图,四维以上多维对比用雷达图或平行坐标系;避免使用 3D 饼图和复杂 3D 图表。
动态数据建议分层:一级数据(每秒更新)展示核心数值,二级数据(每分钟更新)展示趋势/占比,三级数据(每小时更新)展示详细表格或分布。
4. 案例分析:一个"信息洪水"的解法
某百万千瓦级电厂的场景挺有代表性:四台机组叠加的 DCS 测点数量庞大,再加上 SIS、视频安防、门禁、消防、环保 CEMS 等独立子系统,数据从四面八方涌来,最终汇聚到值长面前依然是十几块屏幕、几十个窗口来回切换,典型矛盾是"测点多看不全、系统多连不通、数据多用不好"。
对应的思路是在 DCS 和 SIS 之上再搭一层"一屏观全厂",三者是"操控层—分析层—决策层"的递进关系。界面架构上比较成熟的范式是中间全厂高精度三维数字孪生场景、两侧生产/安全/消防/环保等模块的实时数据面板;面板面积有限时可采用翻转式滚动设计,每个模块由前后两个数据面定时交替显示;面板底色通常用深蓝色系配合高亮数据色,确保控制室 3—5 米观看距离依然清晰可读——观看距离是驾驶舱的核心设计参数。
五、四块怎么串起来
一个务实的整合建议:曲线、报表、报警、仪表盘共用同一份聚合数据源。历史数据按分钟级聚合后再分别喂给曲线控件和报表引擎,报警引擎走实时通道但不直接驱动大屏刷新,仪表盘读的是预计算好的 KPI 快照。这样能避免"某个报表查询把实时曲线拖卡"这类最常见的相互干扰问题。
如果你说一下具体是 WinForm 还是 WPF、数据量级大概多少点/秒、以及报表是要给现场看还是给管理层做 KPI,我可以把这几块的表结构和模块边界再收敛一下。