前言
做上位机界面开发的朋友应该都清楚,从零搭建一套功能完整的可视化工具界面有多折腾——拖控件、绑数据、调通信、写脚本,每个环节都得自己撸一遍。
一套面向工业监控、设备管理、数据可视化场景的桌面端画面设计系统。这篇文章就聊聊它的整体思路、具体功能和我们实际落地时的考量。
项目介绍
项目本质上是一个所见即所得的画面设计器加运行时引擎,你可以把它理解成"工业级PPT + 脚本环境 + 通信网关"的混合体。它不像传统组态软件那样笨重,也不像通用UI框架那样需要写大量前端代码。
核心定位是:让实施人员通过拖拽配置就能完成大部分界面工作,遇到复杂逻辑时又能用C脚本灵活兜底。 整个工程基于.NET 8开发,渲染层用SkiaSharp保证跨平台一致性,脚本引擎走Roslyn动态编译,通信层支持Modbus、OPC UA、MQTT等主流协议。
项目功能
按实际使用流程把功能拆成八个模块,下面这张表可以快速看清全貌:
模块 | 核心能力 |
画面设计 | 多页面管理、图层分层、对齐辅助、网格吸附、批量操作 |
控件与插件 | 基础控件库(按钮/图表/仪表盘等)+ 插件化扩展机制 |
变量与数据 | 多类型变量管理、Excel导入导出、双向绑定、表达式运算 |
事件与脚本 | |
工程与资源 | 多目录工程结构、增量保存、资源去重与引用溯源 |
预览与发布 | 实时预览、跨平台打包(Windows/Linux可执行画面包) |
系统与主题 | 多语言切换、明暗主题、插件SDK、权限管理 |
通信与IO | 驱动管理器、设备映射、数据采集队列、异常重连 |
项目特点
配置优先,脚本兜底:大部分交互逻辑通过数据绑定和表达式就能搞定,实在复杂的再上C脚本,不至于把简单事情搞复杂。 插件化第一:从控件到通信驱动全部走插件加载,第三方集成时不用改主程序代码。
跨平台发布:一套工程既能出Windows版本也能出Linux版本,界面渲染保持一致。
调试体验友好:变量监控、脚本断点、通信报文收发都在一个环境里完成,不用来回切工具。
资源管理严谨:引用溯源和未使用标记功能避免工程膨胀,多人协作时减少冲突。
项目技术
技术项 | 选型与说明 |
运行时 | .NET 6/8(跨平台支持) |
渲染引擎 | SkiaSharp(硬件加速,矢量渲染) |
脚本引擎 | Roslyn(动态编译,热重载) |
数据绑定 | 自研属性绑定框架,支持双向同步 |
序列化 | System.Text.Json(工程文件存储) |
通信库 | 自研驱动适配层,封装Modbus/OPC UA/MQTT |
本地数据库 | SQLite(日志、报警、历史数据存储) |
UI框架 | Avalonia UI(跨平台原生界面) |
插件架构 | MEF(Managed Extensibility Framework) |
项目代码(核心片段)
变量绑定与表达式求值的简化实现:
// 变量绑定核心:属性变化时同步更新绑定的变量publicclassBindingExpression{ privatereadonlystring _expression; privatereadonly Dictionary<string, IVariable> _variables; public object Evaluate() { // 解析表达式中的变量占位符,如 [温度] var parsed = _expression.Replace("[", "").Replace("]", ""); if (_variables.TryGetValue(parsed, outvarvar)) { // 如果是简单变量直接返回值 returnvar.GetValue(); } // 如果是复杂表达式,调用内置函数库解析 return ExpressionParser.Parse(_expression, _variables); }}// 控件属性绑定示例:将按钮文本绑定到变量 "设备状态"var binding = new BindingExpression("[设备状态]", variableManager);button.TextBinding = binding;// 变量变化时自动刷新控件variableManager.OnVariableChanged += (name) => { if (name == "设备状态") button.Text = binding.Evaluate().ToString();};插件加载机制的简化版本:
[Export(typeof(IControlPlugin))]publicclassGaugePlugin : IControlPlugin{ publicstring DisplayName => "仪表盘"; public void Render(SKCanvas canvas, ControlProps props) { // 使用SkiaSharp绘制刻度、指针和数值 DrawGauge(canvas, props.Value, props.Min, props.Max); } public List<propertydescriptor> GetProperties() { // 返回属性定义,自动映射到属性面板 returnnew List<propertydescriptor> { new FloatProperty("MinValue", 0), new FloatProperty("MaxValue", 100), new IntProperty("TickCount", 10) }; }}项目效果
通信稳定性方面,在Modbus TCP轮询100个寄存器、100ms间隔的场景下,CPU占用控制在5%以内,内存稳定在120MB左右。脚本热重载功能在现场调试时非常救命——修逻辑不用停运行中的画面,直接改完保存就能生效。
预览和发布的一致性做得比较到位,编辑时看到什么样,发布的exe跑起来就是什么样,没出现过"编辑时好好的,打包后变形"的情况。资源管理里的引用溯源功能帮我们清理了好几次历史遗留的未用图片,工程体积从800多MB降到了200多MB。
项目源码
总结
这套系统的初衷很简单——不想每次做项目都重复造界面和通信的轮子。从实际效果来看,配置驱动加脚本兜底的模式确实能兼顾效率和灵活性。技术选型上.NET生态的跨平台能力和Roslyn的脚本编译能力是两大支柱,SkiaSharp保证了一致的渲染表现。当然,不足也很明显:复杂动画的性能还有优化空间,表达式引擎目前不支持自定义函数扩展,这些是下一版本要补的短板。
如果大家是做设备监控、产线看板、数据中台前端这类工作,这套思路或许能给你一些参考。工具终究是工具,关键还是用对场景,我们不追求大而全,只求在工业界面这块领域里做得顺手、踏实。
关键词
画面设计器、数据绑定、C脚本引擎、Roslyn编译、工业组态、Modbus通信、OPC UA、SkiaSharp渲染、插件化架构、变量监控、跨平台发布、表达式求值、热重载、MEF插件、Avalonia UI 最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号[DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
作者:小码编匠
出处:gitee.com/smallcore/DotNetCore
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!
觉得有收获?不妨分享让更多人受益
关注「DotNet技术匠」,共同提升技术实力
</propertydescriptor></propertydescriptor>
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!