做上位机这几年,我最大的感受是:写逻辑不难,画界面烦。尤其是工业现场那种设备监控界面,今天加个仪表盘,明天加个管道动画,后天又要搞一组趋势曲线。每样东西单独看不复杂,但一旦数量多起来,布局、绘制、刷新、交互全部堆在一起,工作量就变得非常吓人。后来我开始用HslControls这个C#控件库,相当于给自己备了一个工业界面的“零件箱”,哪些零件常用、哪些场景能直接用、哪些地方还得自己动手,这篇文章一次性说清楚。
HslControls的核心定位就是给C#上位机开发人员提供一套开箱即用的工业控件。它并不是要替代你写业务逻辑的能力,而是把工业界面里那些高频出现的可视化元素——仪表、传感器、管道、阀门、指示灯、进度条、趋势图——全部封装成现成的控件类,拖到窗体上绑定数据就能用。不管你是刚入门上位机的新手,还是在WinForm里挣扎多年的老开发,这套控件库都能明显缩短界面部分的工作量。下面我从实际使用角度把它的原理、用法、坑和选型边界都讲透。
1. 为什么我放弃了一个个手写控件:上位机界面的重复造轮子困境
1.1 一套典型的上位机界面到底由什么组成
先说个具体场景。假设你要做一台设备的上位机,设备有温度、压力、转速三个模拟量信号,还有电机启停、报警输出两个开关量,外加一个连续运行的产量统计。一个常规的监控界面大概需要这些元素:温度表、压力表或者数字显示框、运行状态指示灯、启停按钮、当前产量数字、一段历史趋势曲线、报警信息列表。
这些东西拆开来看没有一个是高新技术。温度表本质就是画个圆弧加指针,指示灯是一个圆形区域变颜色,趋势曲线是折线图,报警列表是带颜色的列表控件。但问题在于,如果每个项目都从零开始写,你会发现所有项目都在重复相同的工作——画刻度、算指针角度、处理重绘闪烁、做线程安全的界面刷新。这些工作既繁琐又容易出低级bug,而它本身并不产生业务价值。
1.2 自己造轮子的真实成本
我早期做过一个温度仪表盘控件,前前后后花了大概两个工作日。第一天在做的事情是:用GDI+画圆弧、画刻度线、画指针,处理不同尺寸下的缩放,控制重绘频率。第二天在解决的是:指针旋转的平滑度、数据刷新时界面闪烁、控件在高DPI显示器下拉伸变形。做完以后说实话只满足当时的项目需求,换一种表盘样式又要改一大片代码。
如果按这个速度算,一个完整监控界面涉及的七八种控件,全部自己实现需要两三周,而且测试量还不小。这个成本放到项目周期里是完全不划算的。HslControls解决的正是这个时间黑洞——它把这些工业界面高频元素做成了现成控件,你不需要理解刻度怎么画、指针怎么转,只需要决定数据源从哪里来。
1.3 HslControls的设计思路:用“组装”代替“绘制”
HslControls的设计理念和市面上通用UI控件库有明显区别。通用控件库比如DevExpress、Telerik,强调的是数据表格、输入校验、页面布局,面向的是管理系统;HslControls的核心方向是工业可视化,面向的是设备面板、传感器状态、参数监控。
它在代码层面做了两层封装:一层是控件外观和绘制逻辑,比如HslMetroButton这个按钮为什么要做成扁平风格,HslThermometer这个温度计控件为什么能显示上下限颜色;另一层是数据驱动逻辑,比如HslCurveHistory这种曲线控件,它内置了环形缓冲区,你只需要持续往里喂数据点,曲线控件自动滚动展示窗口。
这个“组装代替绘制”的思路,就是它“零件箱”属性的由来。你要做一个温度监控页面,不需要研究怎么画温度计,拖一个HslThermometer过来,设置好量程上限、下限、单位,把实时温度值写进Value属性,界面就活了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. HslControls的零件架:主要控件体系一次盘清
2.1 工业数据显示类控件
这类控件解决的是“怎么把数值变得直观”。核心成员包括:
HslInstrument(仪表盘),它是最典型的工业控件,外形是一个圆形表盘,可以设置起始角度、终止角度、主刻度数量、次刻度数量、量程上下限、报警阈值颜色。它适合显示转速、流量这类需要“一眼看出大概范围”的参数。
HslThermometer(温度计),竖直条状的液柱显示,支持设置上下限和单位,适合温度类信号的直观显示。
HslDigitalTube(数码管),模拟工业现场的LED数码管效果,显示数字时可以设置小数位、是否有负号、是否显示冒号,适合产量、批次号这类简单的数字展示。
HslLedDisplay(LED显示屏),在数码管基础上扩展,还可以滚动显示文本,适合展示报警文字、设备状态提示。
HslText(文本显示),比WinForm自带的Label多了工业风格背景设定,可以设置边框、底色、字体颜色,适合做设备名称标签、区域标识。
这类控件共同的特点是:绘制逻辑全部写好,你只需要设置范围和数据值。我在实际使用中,经常把HslInstrument和HslText组合使用,仪表盘显示实时值,下方用HslText显示当前运行模式和设定值,比单纯堆Label好看得多。
2.2 设备状态与流程指示类控件
如果说数据显示控件解决的是“数值怎么看”,状态指示类控件解决的是“设备状态怎么表达”。
HslConduit(管道)可以绘制一段带颜色的管线,支持横管、竖管、弯管几种形状。它本身并不复杂,但一旦配合其他控件,就能组成完整的流程示意图。比如一段红色管道表示高温介质,蓝色管道表示冷却水,利用颜色区分介质状态。
HslValve(阀门)、HslPump(水泵)、HslMotor(电机)分别对应工业流程中的阀门、泵、电机。这几个控件内部集成了动画逻辑,比如HslPump可以选择旋转动画,运行时叶轮会转动,非常直观地告诉操作员“这台泵当前在运行”。
HslLamp(指示灯)是使用频率很高的控件。它有多种形状可选:圆形、方形、菱形,颜色可以绑定设备状态。用绿色表示运行、红色表示故障、黄色表示待机,这是工业界面约定俗成的颜色逻辑。它还支持闪烁效果,报警时可以以一定频率闪烁,比静态颜色更有警示作用。
HslSwitch(开关)既可以做显示也可以做交互,鼠标点击后状态切换,适合做手动/自动模式切换、设备启停按钮。它比WinForm的Button更像物理开关,工业现场的人操作起来没有违和感。
这类控件的核心价值在于“语义直接”。甲方看到界面时不需要你解释,一眼就能分辨出哪台设备在运行、哪根管道里有什么介质。这是用通用控件很难做到的视觉效果。
2.3 数据展示与交互类控件
除了设备状态,上位机界面还需要表格、曲线、按钮这些基础交互元素。HslControls在这块也提供了对应方案。
HslCurveHistory是趋势曲线控件,支持多条曲线同时显示,每条曲线可以设置不同颜色,内置了历史数据缓冲区,可以回放查看。它比自绘折线图省力很多,滚动窗口、自动缩放、曲线颜色这些常见功能全部现成。
HslCurve(曲线控件)和HslCurveHistory的区别在于它更偏实时刷新,适合高频数据流展示,比如传感器实时波形。
HslTable(表格控件)用于显示批量数据,支持固定表头、滚动加载,样式偏向工业风格。它不追求像DataGridView那样强大的编辑能力,但胜在显示效果清爽,适合做参数列表、报警记录。
HslButton / HslMetroButton是按钮控件,后者是扁平化风格,可以设置不同的状态颜色,适合做操作按钮。
HslTime(时间显示)、HslBattery(电量显示)、HslProgress(进度显示)属于辅助类控件。HslTime可以实时显示当前时间,HslBattery适合做手持设备的上位机界面,HslProgress适合在操作批处理任务时显示进度。
2.4 常用控件速查表
| 控件名 | 核心用途 | 关键属性/方法 | 典型场景 |
|---|---|---|---|
| HslInstrument | 仪表盘显示 | Value, MaxValue, MinValue, MainColor | 转速、流量实时显示 |
| HslThermometer | 温度计显示 | Value, MaxValue, MinValue, Unit | 温度监控 |
| HslDigitalTube | 数码管数字显示 | Value, DigitCount, Shows | 产量、批次号 |
| HslLamp | 指示灯 | LampColor, IsBlinking | 设备状态指示 |
| HslSwitch | 开关交互 | SwitchStatus, SwitchChanged事件 | 启停控制、模式切换 |
| HslConduit | 管道显示 | PipeType, PipeColor | 流程示意 |
| HslValve/HslPump/HslMotor | 阀门/泵/电机显示 | RunStatus, RotateSpeed | 设备运行状态 |
| HslCurveHistory | 历史趋势曲线 | AddCurve, AddData | 温度、压力历史曲线 |
| HslCurve | 实时曲线 | AddData | 高频数据波形 |
| HslTable | 工业表格 | Columns, Rows | 参数列表、报警记录 |
| HslMetroButton | 扁平风格按钮 | ButtonType, BackColor | 操作按钮 |
| HslProgress | 进度条 | Value, MaxValue | 任务执行进度 |
| HslBattery | 电量显示 | Value, MaxValue | 手持设备 |
这张表只是常用的一部分。每添加一个控件到窗体后,建议先看一眼它的属性面板,因为很多控件都提供了独立的颜色、文字、动画开关设置,不需要改代码就能调整出不同风格。
3. 把零件箱搬进项目:引用方式与Demo验证
3.1 通过NuGet引入HslControls
引入HslControls最直接的方式是通过Visual Studio的NuGet包管理器。在“解决方案资源管理器”中右键项目引用,选择“管理NuGet程序包”,在浏览页搜索HslControls,找到后点击安装。
安装完成之后,项目引用里会出现HslControls.dll。需要注意的一点是,HslControls基于.NET Framework开发,如果项目用的是.NET Core或.NET 5+,需要确认目标框架兼容性。如果是老式的WinForm项目,.NET Framework 4.5以上基本没什么问题。用.NET 6/8新工程的话,建议先在一个测试窗体里拖一个控件验证编译通过,再决定是否全面使用。
3.2 在工具箱里让控件显示出来
安装完dll后,工具箱不会自动出现HslControls的控件,需要手动添加。操作步骤是:打开一个窗体设计器,在工具箱空白处右键点击“选择项”,在弹出的“选择工具箱项”窗口中点击“浏览”,找到项目bin目录下或包目录下的HslControls.dll,确认后工具箱会出现一组带Hsl前缀的控件。
这一步做完以后,HslControls的控件就和其他标准控件一样,可以直接拖拽到窗体上。控件在工具箱中的分组名称一般是“HslControls”,找到后也不会和其他标准控件混淆。
3.3 跑通官方Demo,重点看哪几个示例
HslControls的GitHub仓库里附带了一套Demo工程,这是了解控件能力最快的方式。强烈建议把Demo完整跑一遍,不要只看截图。
跑Demo时候我推荐重点看三个页面:第一个是仪表盘综合示例,它能直观展示不同仪表盘的样式差异和属性设置效果;第二个是流程管道示例,能看到HslConduit、HslValve、HslPump这些控件组合起来的流程示意效果;第三个是曲线示例,能看到HslCurveHistory如何接收数据并滚动显示。
跑Demo的时候留意一下各个控件的属性变化对显示效果的影响,比如把HslInstrument的MainColor从绿色改成红色,界面风格立刻不一样。这比直接开项目边写边调要节省很多时间。我把分配两三个小时跑Demo看作一个必做步骤,很多控件的能力边界在看演示的过程中就能心里有数。
3.4 版本与后续维护的注意点
HslControls和作者另一个开源项目HslCommunication是独立发布的。前者是控件库,后者是通信库,两者没有强制性依赖,但经常一起使用。HslCommunication负责PLC、Modbus、串口通信,HslControls负责界面展示,搭在一起做一套完整的上位机正好。
版本方面要留意:HslControls的GitHub上可能有release版本和源码版本,release版本适合直接引用,源码版本适合需要魔改控件内部实现的场景。我的建议是优先用release版本,除非你对某个控件的绘制逻辑有强烈的定制需求,否则没有必要自己编译源码。
4. 拿零件箱组装一个温度/压力/流量监控面板
4.1 场景设定与控件选型
聊了这么多,直接上一个我实际做过的例子。设想有台设备需要监控三个信号:冷却水温度、主管道压力、出水流量。温度范围20-80摄氏度,压力范围0-1.6MPa,流量范围0-100升每分钟。另外还有一台水泵需要显示启停状态。
控件选型如下:温度用HslThermometer,压力和流量各用一个HslInstrument仪表盘,水泵状态用HslLamp结合HslPump动画,右侧放一个HslCurveHistory显示三路信号的历史趋势。界面顶部用HslText显示系统名称,底部放两个HslMetroButton做“启动采集”和“停止采集”操作。
这个布局的核心设计原则是:主次分明,趋势曲线占较大面积,仪表盘排在显眼位置,操作按钮固定在右下角,避免操作人员误触。
4.2 布局和关键属性设置
拖完控件后,先设置静态属性。HslThermometer的MaxValue设为80,MinValue设为20,Unit设为℃,报警上限可以设到70,超过后液柱颜色自动变红。HslInstrument表盘一(压力)的MaxValue设为1.6,表盘二(流量)的MaxValue设为100。HslLamp初始状态设为红色,等启动采集后再根据实际信号变色。
HslCurveHistory需要创建三条曲线。在窗体的Load事件中,调用它的CreateCurve方法分别创建温度、压力、流量曲线,并设置每条曲线颜色。比如温度用红色,压力用蓝色,流量用绿色。曲线控件的显示范围设置为最近一分钟,这样操作员可以看到一分钟内的变化趋势。
布局时注意尺寸调整。HslInstrument仪表盘在窗体尺寸变化时,控件本身要设置Anchor属性,确保最大化后仪表盘跟随窗口等比放大。这里有个小坑:如果你的窗体允许用户调整大小,建议先设置好Form的MinimumSize,避免窗口缩得太小时布局错乱。
4.3 数据刷新的后台逻辑
界面搭好以后,数据来源模拟一个后台线程。用一个System.Timers.Timer,每500毫秒触发一次,在回调中随机生成一组温度、压力、流量数据,然后更新对应控件的属性。
这里需要注意跨线程访问控件的问题。WinForm的控件有线程亲和性要求,不能在非UI线程直接修改控件属性。最简单的做法是使用Control.Invoke或者写一个线程安全的赋值方法。我在项目中是用一个委托把数据更新的动作封送到UI线程执行,每次刷新只更新控件属性,不做其他的重计算,保证UI流畅。
温度表控件更新Value属性后会自动重绘,不需要手动调用Invalidate。这是直接用成熟控件的好处——绘制刷新逻辑内部已经处理好了。如果用自绘控件,每次数据变化都还要考虑重绘区域,甚至处理双缓冲避免闪烁,省下的精力非常可观。
HslCurveHistory的更新方式是调用AddCurveData方法,第一个参数是曲线索引,第二个参数是数据值。它内部维护了一个环形缓冲区,数据量超过显示范围后自动丢弃旧数据,滚动效果是内置的。这个机制我在一个连续运行72小时的测试中验证过,内存占用稳定,不会因为长时间运行而增长。
4.4 实测效果与再调整
上面这套面板从拖控件到跑起来,我大概用了不到半天。这个时间主要包括属性设置和数据绑定,没有写任何绘制代码。运行起来后,温度表液柱随数据上下浮动,压力表指针不停摆动,流量表数字跳动,曲线图三条曲线滚动前进,水泵指示灯和泵动画联动。整体视觉风格统一,深蓝色背景配上彩色指示,有一种工控屏的感觉。
如果发现仪表盘颜色和整体风格不搭,可以统一调整HslInstrument的BackColor和边框颜色。HslControls的控件大多支持颜色属性,把背景色统一为窗体的背景色,界面就干净很多。
5. 实际项目里的踩坑实录:这些问题不提前知道会多花时间
5.1 高DPI缩放导致界面错位
先说最常见的坑:显示器的DPI缩放。开发机上如果设置了125%或150%缩放,WinForm的自动缩放可能会让HslControls控件的字体和绘制区域错位。现象是控件能显示出来,但文字模糊、表盘刻度不对齐、控件大小在运行时和设计时看起来不一样。
这个问题的根源是WinForm在高DPI下的缩放机制和GDI+绘制之间的配合问题。HslControls的控件大量使用GDI+绘制,对DPI的适配逻辑不完全等同于标准控件。解决方法是优先使用Windows窗体应用的“应用程序清单”里的PerMonitorV2设置,并且在代码里调用SetProcessDpiAwarenessContext。如果项目环境不允许改清单,退而求其次的做法是设置窗体的AutoScaleMode为None,用固定像素布局,但这会造成高分屏下界面偏小。
我给一个实用建议:把开发机显示缩放设置为100%,布局完成后,再到实际工控机上看效果。工控机屏幕的缩放通常也是100%,这样能最大程度避免奇奇怪怪的显示问题。
5.2 控件数量多时刷新卡顿
第二个坑是控件数量爆炸。一个复杂的监控画面可能会有几十个HslControls控件,再加上曲线、管道动画,如果每500毫秒全部刷新一次,界面可能掉帧。
我在一次项目中遇到过这个问题。当时一个流程画面放了20多个HslLamp和10多根HslConduit管道,刷新频率是300毫秒一次。运行起来后CPU占用居高不下,操作员反馈画面“发飘”。
排查后发现,问题不在于数据量,而在于所有控件都在同一个UI线程中执行GDI+绘制,刷新频率又高,导致UI线程忙不过来。解决思路是降低刷新频率、非关键数据不要高频刷新、指示灯状态变化时才刷新。HslLamp控件有属性可以控制闪烁频率,闪烁本身就消耗性能,尽量减少需要同时闪烁的控件数量。
如果画面确实需要大量控件实时刷新,另一个方案是把界面拆成多个窗体或UserControl,用异步通信传递数据,避免一个窗体的刷新拖垮整个界面。HslControls本身不限制控件数量,但WinForm的绘制机制决定了控件数量需要控制在一定范围内,经验值是单个窗体内活动控件不超过50个比较稳妥。
5.3 与HslCommunication一起用时的联动坑
HslControls的作者还开发了HslCommunication通信库,很多项目会把它和HslControls一起用来做PLC通信。有一个容易被忽视的坑是:两个库的版本更新节奏不是同步的,直接引用最新版可能出现个别的API不兼容情况。
遇到过的一个具体例子是,某个项目的通信数据是字符串格式的,HslCommunication读取到PLC数据后直接字符串转float,然后赋值给HslInstrument的Value属性。结果在数据中包含空格或非法字符时,转换抛出异常。这个问题其实不是控件库的锅,但在实际项目中,数据解析和控件赋值写在一起,排查时容易误以为是控件的Bug。
我现在的做法是:所有从通信层拿到数据,先在业务层完成解析、校验、单位换算,再赋值给界面控件。界面只负责显示,不承担数据解析职责,这样排查问题时边界清晰很多。
5.4 控件版本和源码魔改的取舍
有些项目需要在HslControls控件上增加特殊效果,比如在仪表盘上叠加公司Logo、在管道上显示介质名称。HslControls源码是公开的,你可以直接改源码重新编译,但这会带来后续同步官方更新的麻烦。
我的经验是:优先用属性、事件实现定制,其次是叠加其他控件实现,最后才考虑改源码。比如在仪表盘上叠加Logo,可以用一个PictureBox放在仪表盘中心区域,不一定要改HslInstrument本身。叠加显示介质名称,可以用一个半透明的HslText放在管道控件上方位置。
如果真的需要深度魔改,建议fork一份源码保留自己的版本,并在代码里标记所有修改点。后续官方更新时,合并操作会容易一些,但也要做好长期维护的准备。
6. HslControls不是万能的:它适合什么场景,不适合什么场景
6.1 控件库的能力边界
再好的零件箱也不能覆盖所有需求。HslControls主要面向WinForm,虽然作者也有对WPF的规划,但如果你当前项目是WPF架构,用它就不太合适,需要另找方案。
它也不适合做复杂的报表管理界面。比如你要做数据透视表、复杂树形结构、多人协作分页编辑,这些是通用表格控件和第三方报表控件的领域,硬用HslTable去实现反而吃力。
另一个边界是更新维护。HslControls是一个开源免费项目,作者用业余时间维护,版本迭代速度和企业级商业控件没法比。如果你的项目需要严格的技术支持、代码审计、持续保障升级,开源控件库就不如商业控件合适。这个风险要在项目选型时提前评估。
6.2 在项目中如何与自研控件共存
实际项目中,HslControls控件和自研UserControl完全可以共存。我的习惯是:设备状态显示、传感器数值、流程示意这类“工业味道”明显的画面用HslControls;而业务配置页面、系统设置页面、权限管理页面用标准控件或自研组件。
这种共存策略的好处是各取所长。HslControls的特长是工业可视化,标准控件的特长是表单布局和文本编辑,不要让一个库承担所有职责。
如果你在做一个公司内部的上位机框架,可以把HslControls封装成公司自己的公共控件库。项目里统一引用封装后的dll,之后如果控件库有更新或替换,只需要改封装层,不影响业务页面代码。
6.3 选型时的一些个人判断标准
我选不选HslControls一般看三个条件。第一,项目界面是否以设备监控、参数显示为主;第二,项目框架是否是WinForm;第三,项目对界面风格是否有一个“工业感”的预期。三个条件都满足时,HslControls通常是合适的选择。
如果项目是面向普通消费者的软件,或者界面交互复杂度高、需要大量自绘动画,我会倾向于使用更主流的UI框架。HslControls的设计语言毕竟偏向工业触控风格,放在普通软件中会显得突兀。
这套“零件箱”的价值在于你可以快速搭出一个甲方认可的设备画面,把省下来的时间投入到通信、数据、业务逻辑这些更有技术含量的地方。它不是银弹,但在C#上位机这个细分领域里,它是很实用的一套工具。最后再分享一个使用习惯:每做一个新项目,把用到的控件和属性配置记录下来,慢慢会沉淀出自己的一套项目模板,这才是真正属于你的“零件箱”。
