简介:这是一份 Iocomp Components v4.02 SP2 的完整源码资源,面向使用 Delphi、C++Builder 及 RAD Studio 各版本进行工控界面开发的工程师,适合需要仪表显示、实时曲线、过程控制面板等场景的二次开发与学习。压缩包内共 938 个文件,容量 17.91MB,以 289 个 pas 源码文件、137 个 dfm 界面文件、80 个 res 资源文件及 bmp 位图素材为主,同时包含 dpk 组件包、ocx 控件、dll 动态库等编译与运行所需文件,覆盖从源码编译、界面设计到控件部署的完整体系,rar 压缩包组织清晰,便于解压后按目录快速定位。资源支持 Delphi 4 至 2009、C++Builder 5/6、BDS 2006 与 RAD Studio 2007/2009 等多版本 IDE,并内置对应各版本工程文件,便于在相应环境中直接打开构建。已有 837 人浏览学习。借助这些源码,开发者可深入理解控件内部绘图与消息处理机制,按需定制仪表外观和交互逻辑,并直接复用官方组件包提升工控项目开发效率;同时可根据项目需要拓展新组件,减少重复开发成本。
1. 这包控件解決的不只是画界面:为什么工控上位机要照抄它的实现
做工业上位机的头几个月,我用 GDI+ 硬画仪表盘,画到第三个指针就放弃了——刻度、量程、指针动画、趋势图回放全是自己造轮子,两周过去还在调反锯齿。Iocomp Components v4.02 SP2 Full Source 解决的就是这件事:一批做好的仪表、趋势图、状态灯、数字显示控件,拖进窗体就能用,而且这份资源带全部源码,DLL 或 OCX 内部的行为都能改。适合给串口、OPC、PLC 采集过来的数据做实时界面的开发者,也适合被商业控件黑盒限制的人——报警闪烁逻辑、坐标反推、背景画法这些,源码在手都不是秘密。下文只写我从拿到资源到落地交付之间走过的完整路径。
2. 拿到压缩包先分目录:编译顺序直接决定成败
2.1 四个目录分别是什么料
解压之后第一件事不是找安装程序,而是把目录结构看清。大多数这类完整源码包的布局都遵循同一套逻辑,Iocomp v4.02 SP2 也不例外,通常能看到以下几类内容:
| 目录 | 存放内容 | 备注 |
|---|---|---|
| Bin / Release | 编译好的 ActiveX 控件(.ocx)或 .NET 程序集(.dll) | 安装程序会帮你注册,也可以手动 regsvr32 |
| Source | 每个控件的源码工程,VB6 下是 .vbp / .dsp,.NET 下是 .sln / .csproj | 这是整包资源里最值钱的部分 |
| Samples / Tests | 示例工程,每个控件带一个演示窗体 | 新手先打开 Sample 跑通,再动自己的工程 |
| Docs | 帮助文档与属性索引 | 老版本控件没有在线文档,离线版非常关键 |
我拿到资源后的第一个动作是先把 Bin 里编译好的控件注册进系统,跑一遍 Samples,确认环境能显示出界面,再回头啃 Source。一上来就编译源码,一旦注册表权限、引用路径、依赖顺序有偏差,会花掉大半个下午,而其实前置的检查步骤很简单。
2.2 先注册 Bin 还是先编译 Source:这一步的选择影响后续所有工程
常见做法是分两步走。第一步,运行资源包根目录下的 Setup.exe(如果有的话),让安装程序把控件文件复制到系统目录并写入注册表;第二步,用 Regsvr32 手动注册 Bin 下那几个核心 OCX。手动注册的目的一是确认文件不是坏的,二是拿到明确的错误信息。
# 在管理员权限的命令行里执行,注册 32 位 ActiveX 控件 regsvr32 "C:\Iocomp\Bin\iocomp.ocx" # 卸载控件用这个,调试时很常用 regsvr32 /u "C:\Iocomp\Bin\iocomp.ocx"逻辑说明:regsvr32 调用 DLL 内部的 DllRegisterServer 函数,把控件的 CLSID、类型库、工具箱位图写进注册表。如果控件是 64 位系统下的 32 位 OCX,路径需要带上 SysWOW64 下的 regsvr32 再去注册,这一点在 5.2 里会展开。
参数说明:/u是卸载参数,调试版本反复替换文件时,先卸载再复制是安全顺序,避免文件被系统占用锁定。
我一般会先跑 Setup,再手动 regsvr32 一次。两件事都做了,后面编译 Source 时 IDE 的“组件”对话框里就能直接看到控件,引用关系不会断。
2.3 源码工程的引用链:先编底层控件还是先编示例
Source 目录下通常不止一个工程,而是按控件分组,比如仪表一组、趋势图一组、显示一组,外加一个公共基础库。编译顺序错了,会报“找不到文件或项目引用”的错误。我打开过几个模拟项目X,最后总结出一套稳妥顺序:
- 先编译公共基础库工程(名字里常带 Base / Common / Core 的),这是所有控件依赖的底层类。
- 再编译各控件组工程,生成对应的 ocx 或 dll。
- 最后编译 Samples,这一步能验证前面所有工程有没有遗漏依赖。
' VB6 环境下设置工程引用(常见操作,不是命令行工具能替代的) ' 打开"工程"菜单 -> "引用",勾选刚注册的 Iocomp 类型库 ' 然后再打开"部件"对话框,把控件拖到工具箱逻辑说明:VB6 的工程引用必须在 IDE 里手动勾选,命令行没法代劳。类型库名称一般在安装后出现在引用列表里,如果没出现,说明 2.2 的注册步骤有遗漏。
参数说明:注意区分“引用”和“部件”两个对话框——引用管的是类型库(非可视类),部件管的是可视化控件。漏掉后者,工具箱里拖不出控件。
这块最常见的翻车记录是:编译完 Source 后直接打开 Sample,结果 Sample 里的工程引用指向的是绝对路径下的 Bin 文件,而 Bin 文件还没注册,于是 IDE 报“内存访问冲突”。把注册顺序提前半小时做完,这类问题基本不会遇到。
3. 四个高频控件的上手路径:仪表、趋势图、状态灯、数字显示
3.1 仪表控件:量程、刻度与指针动画的底层逻辑
Iocomp 仪表控件在设计上把所有刻度计算封装在控件内部,对外只暴露量程和格式属性。这一点和自绘控件的思路完全不同——自绘时你得自己算刻度步长、标签位置、指针角度,而这里只需要设置最小最大值。
' VB6 环境,仪表控件 iGauge1 With iGauge1 .Min = 0 .Max = 100 .Value = 0 .MajorTickCount = 10 ' 主刻度数量 .MinorTickCount = 5 ' 主刻度之间的次刻度数量 .FormatString = "0.0" ' 数字显示格式 End With ' 运行时赋值,指针自动过渡 iGauge1.Value = 63.5逻辑说明:Setting 了 Min / Max 之后,控件内部会按 MajorTickCount 均匀划分刻度区间,指针角度由 Value 与量程的比例计算得出。FormatString 影响的是表盘中央数字标签的文本格式,“0.0”表示保留一位小数。
参数说明:MajorTickCount 设置 10,表盘上就有 11 个主刻度位置,从 0 到 100 每隔 10 一个刻度;MinorTickCount 把每个主刻度区间切成 5 格。这两个值配得不好,刻度标签会重叠,常见做法是让 MajorTickCount 在 5 到 20 之间,不要超过表盘物理尺寸能容纳的范围。
运行时的指针动画不需要你手动插值,直接赋值 Value 即可,控件内部有动画线程。这里有个隐藏知识点:Value 属性的赋值方式会影响动画长度,有些版本支持设置过渡时间,v4.02 SP2 里表现为 AnimationTime 属性。如果追求仪表指针快速响应,把它设成 0 或很小的值。
3.2 趋势图控件:从过程变量到坐标映射的数据管线
趋势图是整个控件包里最吃经验的部分。你需要先搞清楚它把数据点存在哪、按什么规则滚动画面。Iocomp 趋势图的数据模型按通道组织,每个通道一个数组,数组长度有上限,超出后自动丢弃最旧的数据。
// C# 环境,.NET 版趋势图控件 iTrendChart1 iTrendChart1.Channels.Clear(); iTrendChart1.Channels.Add("温度", Color.Red); // 获取或创建通道对象 ITrendChannel ch = iTrendChart1.Channels[0]; ch.Data.Add(12.5); // 添加一个数据点 ch.Data.Add(12.8); // 设置时间窗宽度,单位毫秒 iTrendChart1.FixedTimeWindow = true; iTrendChart1.TimeWindow = 60 * 1000; // 显示最近 1 分钟逻辑说明:Add 方法把数值追加到通道末位,控件内部根据时间戳映射到 X 轴,Y 轴范围默认跟随数据自动缩放。FixedTimeWindow 开启后,画面固定显示最近 TimeWindow 毫秒的数据,旧点自动滑出,这是做实时监控的标配写法。
参数说明:TimeWindow 的单位是毫秒,60 * 1000 表示 1 分钟。这个值决定内存占用——每秒一个数据点,1 分钟窗口就是 60 点,而如果你的采集频率是 10ms 一次,1 分钟就是 6000 点,数量级完全不同。趋势图控件不是无限存储,点太多会触发翻车,细节在 5.4 里专门写。
数据管线还有一个容易被忽略的点:Y 轴自动缩放。默认状态下,新数据进来后 Y 轴会反复跳动,视觉体验很差。常见做法是手动固定 Y 轴范围,或者开启“自动缩放但带缓冲”模式。
' VB6 环境固定 Y 轴范围 iTrendChart1.YMax = 100 iTrendChart1.YMin = 0 ' 关闭自动缩放,避免画面跳动 iTrendChart1.AutoScale = False逻辑说明:AutoScale = False 时,趋势图完全按你给的 YMax / YMin 绘制;某些采集场景信号波动大,自动缩放会在每次刷新时改变坐标上下限,反而让人看不清趋势。
3.3 状态灯与报警逻辑:不要用 Timer 轮询去做闪烁
状态灯控件的接口很简单,核心属性是 OnColor / OffColor / Blink。但很多新手的做法是开一个 500ms 的 Timer,每次切换灯的颜色,这是错误的思路——闪烁行为应该由控件自己完成,你只负责告诉它“当前是报警态”。
' VB6 环境,状态灯 iStatus1 iStatus1.OnColor = vbRed ' 报警时显示红色 iStatus1.OffColor = vbGreen ' 正常时显示绿色 iStatus1.Blink = True ' 让控件自己闪烁 iStatus1.Active = False ' 先处于正常态 ' 检测到报警条件时,只改 Active Private Sub CheckAlarm() If Pv1 > 80 Then iStatus1.Active = True ' 灯亮红并闪动 iStatus1.Blink = True Else iStatus1.Active = False iStatus1.Blink = False End If End Sub逻辑说明:Active 是最上层的状态开关,Blink 控制是否启用闪烁;两者独立,可以做到“常亮报警”或者“闪烁报警”。控件内部用线程控制闪烁频率,不占用 UI 消息循环。
参数说明:Blink 的频率由控件的 BlinkInterval 属性控制,默认 500ms。有些版本还支持设置闪烁比例(亮的时间占比)。我这里有一个建议:报警闪烁不要只靠颜色区分,最好同时配合布尔变量输出到日志,这样事后排查时能看到报警发生的确切时间。
4. 把实时数据喂进控件:串口采集、UI 刷新频率与线程模型
4.1 串口 / OPC 数据接入:采集线程与控件解耦
大部分工控项目的真实数据来源是串口或 OPC 服务器。串口读取是阻塞式的,如果放在 UI 线程里执行,界面会在等待串口响应期间完全卡死。常见做法是把采集放在后台线程或 Timer 里,把数据放进队列,UI 再批量取出来赋值给控件。
' VB6 里的简化模型:用 Timer 模拟采集线程 ' 实际项目中串口读取在独立线程里做,这里只演示队列机制 Private Type DataPacket TagName As String Value As Double End Type ' 定义全局队列(伪代码示意) Dim DataQueue As Collection ' 每次 Timer 触发时,取出队列里的数据并更新控件 Private Sub Timer1_Timer() Dim pkt As DataPacket Dim item As Variant ' 把队列里所有数据一次性推给控件 For Each item In DataQueue pkt = item Select Case pkt.TagName Case "TEMP" ' 温度 -> 趋势图 iTrendChart1.Channels(0).Data.Add pkt.Value Case "PRESS" ' 压力 -> 仪表 iGauge1.Value = pkt.Value Case "ALARM" ' 报警 -> 状态灯 iStatus1.Active = (pkt.Value > 0.5) End Select Next Set DataQueue = New Collection ' 清空队列 End Sub逻辑说明:数据进队列、UI 定时拉取,是最稳妥的跨线程方案。好处是 UI 刷新频率可以独立于采集频率——采集端可以 100ms 一次进队列,UI 端 200ms 或 500ms 批量渲染一次。这样趋势图不会因为频繁 Add 而频繁重绘。
参数说明:Timer 间隔的决定逻辑是——仪表类控件可以 50ms 更新,人眼感知不到延迟;趋势图 1 秒内 100 个点视觉上没问题;状态灯则没必要超过 10Hz。如果采集频率本身是 1 秒 1 个点,UI 刷新间隔就设 1 秒,不要设 50ms,那只会空转。
4.2 刷新频率与历史缓冲区:趋势图的窗口宽度设置
趋势图控件有一个很容易被忽略的点:它并非只画“当前值”,而是维护一个历史缓冲区。这个缓冲区的大小决定了你能回放多长时间的曲线。
| 采集周期 | 1 秒 1 点 | 100ms 1 点 | 10ms 1 点 |
|---|---|---|---|
| 1 分钟窗口 | 60 点 | 600 点 | 6000 点 |
| 10 分钟窗口 | 600 点 | 6000 点 | 60000 点 |
| 1 小时窗口 | 3600 点 | 36000 点 | 360000 点 |
这个表格我每次做方案时都会贴给自己看。很多人直接把历史时间窗设成“24 小时”,那意味着如果采集周期是 100ms,缓冲区要存 864000 个点,GDI 绘制压力会直接把刷新率拖到 1 帧以下。
' 建议写法:设定合理的缓冲区大小 iTrendChart1.FixedTimeWindow = True iTrendChart1.TimeWindow = 10 * 60 * 1000 ' 只保留 10 分钟历史逻辑说明:FixedTimeWindow 开启后,趋势图会周期性地丢弃窗口之外的数据,这个操作在内部遍历通道数组,频率和数据的入队频率一致。窗口设得越大,每次清理的开销越大,界面就越容易掉帧。
参数说明:10 * 60 * 1000 是 10 分钟。我的经验值是,如果采集周期是 1 秒,最多保留 1 小时;如果采集周期是 100ms,保留 10 分钟已经足够排查绝大多数工艺波动。真正需要长期历史数据时,把数据存入数据库,而不是塞给控件画。
4.3 两种赋值方式的差别:直接改 Value 还是走缓冲区
你是直接改属性的“简单派”,还是走数据缓冲的“保守派”?这取决于你有没有读源码。看过 Iocomp 的源码后你会发现,Value 属性背后有一整套非法值检查、事件触发、重新绘制逻辑;而趋势图的 Add 方法背后还带时间戳赋值、通道容量检查。
// 直接赋值:代码少,但每次赋值都触发完整的刷新流程 iGauge1.Value = reading; // 趋势图的批量添加:一次多条数据,降低刷新开销 for (int i = 0; i < readings.Length; i++) { iTrendChart1.Channels[0].Data.Add(readings[i]); }逻辑说明:两种写法在功能上等价,在性能上前者适合低频仪表控件,后者适合高频数据流。批量添加还有一个隐藏好处:数据点的时间戳可以通过参数控制,比如指定历史时间回放,而直接赋值只能取当前时刻。
参数说明:多数版本支持在 Add 时重载时间戳参数。如果你要回放一段历史数据,只要把时间戳设成对应的过去时间,趋势图就会在时间轴正确的位置画出每个点。
关于线程模型还有一个安全建议:不要把 End 到 UI 的跨线程赋值直接写在串口接收事件里。Iocomp 的 ActiveX 版本没有内置线程亲和性保护,接收线程直接改 UI 控件属性,轻则闪烁错乱,重则直接进程崩溃。用队列中转、在 Timer 里刷新,是经历过多次翻车后留下的纪律。
5. 避坑手册:注册失败、水印、假死与内存告警
5.1 注册成功但运行报“未授权”错误
现象:双击安装包安装结束后,手动 regsvr32 也提示成功,打开示例程序运行,窗体加载时弹出“License not found”或类似授权错误,控件不显示界面。
原因:这类商业控件包的授权验证依赖一个后台服务或特殊注册表项,安装程序通常会在系统服务里注册一个验证进程,端口被占用或服务启动失败时,控件检测不到授权信息,就进入评估模式。
解决:打开服务管理器,找到安装程序写入的 Iocomp 相关服务,确认未停止、未禁用;如果服务自动停止,尝试重启该服务并重新打开示例工程。如果重启后仍然无效,以管理员身份重新运行一次安装程序的“修复”选项。
5.2 64 位系统下 VB6 / VS 工具箱里看不到控件
现象:系统是 64 位 Windows,控件已经通过 regsvr32 C:\Windows\System32\regsvr32.exe 注册成功,IDE 的“部件”对话框里却找不到 Iocomp 控件,或者看到但拖到窗体时报“类未注册”。
原因:Iocomp v4.02 的 ActiveX 版本是 32 位 OCX。在 64 位系统上,System32 目录下的 regsvr32 是 64 位的,它能把 32 位 OCX 的注册信息写进 64 位注册表,但 IDE 进程是 32 位的,读不到那边的注册表,所以看不到。
解决:改用 32 位注册工具,命令如下:
# 在 64 位系统上用 SysWOW64 下的 regsvr32 注册 32 位 OCX C:\Windows\SysWOW64\regsvr32.exe "C:\Iocomp\Bin\iocomp.ocx"逻辑说明:SysWOW64 目录里的 regsvr32 才是 32 位版本,它是为 WOW64 兼容层准备的。32 位 IDE 只能读取注册表 Wow6432Node 分支下的信息,只有用 32 位 regsvr32 注册才会写进那个分支。
参数说明:如果你的 IDE 是 .NET 版本,不需要注册 OCX,直接引用 dll 文件即可;引用方式在 2.3 里讲过。这里针对的是 VB6 或老 VC6 场景。
5.3 设计界面显示“Trial”水印,运行界面正常
现象:控件在 VB6 窗体设计器里显示评估版水印字样,但运行后水印消失、功能正常。
原因:设计时控件需要设计期许可证,这个许可证存在注册表的 Licenses 键里。安装程序有时只安装了运行期授权,没有把设计期授权写入当前用户注册表。IDE 进程读取设计期授权失败后,回退到评估模式显示水印。
解决:确认当前登录用户是管理员,重新执行安装程序的注册授权动作;如果安装包附带授权脚本,检查是否在资源包根目录里有 .reg 文件或激活工具。运行 .reg 文件后需要重启 IDE 才能生效。这一步具体的修复方式不同安装包有差异,我自己遇到时是把 .reg 文件手动导入注册表,重新打开工程就正常了。
5.4 趋势图运行几小时后界面假死
现象:工艺站连续运行 4 小时以上,趋势图区域点击后无响应,CPU 占用率高达 30% 以上,整个上位机界面拖不动。
原因:90% 的概率是历史缓冲点数量超限。趋势图控件对通道数据点数量有内部上限,如果不设 FixedTimeWindow 或窗口设成“无限大”,数据点会一路增长,每次重绘都要遍历全部点,绘制时间随点数量线性增长直到卡死。
解决:检查是否开启了 FixedTimeWindow,检查 TimeWindow 是否合理。如果确实需要长时间趋势,但不需要密集刷新,可以考虑把趋势图暂停刷新只读数据,或者定期调用 ClearData 清理旧数据:
' VB6 环境:定时清理前 60 秒之外的数据 If iTrendChart1.Channels(0).Data.Count > 3600 Then iTrendChart1.Channels(0).Data.ClearTo 120 End If逻辑说明:ClearTo 是常见的清除方法,参数表示保留最近 120 个点,其余丢弃。3600 表示超过 3600 点才触发,这样避免频繁清理造成界面抖动。
参数说明:ClearTo 的“120”是点数量,不是秒数,看控件定义。如果通道数据每秒 1 点,保留 120 点就是保留最近 2 分钟。如果你需要保留更多历史,建议把它调到与 TimeWindow 匹配的值。
5.5 关闭工程或窗体时 VB6 崩溃“内存不能读”
现象:点击窗体右上角关闭按钮,或者结束调试返回设计器时,VB6 直接崩溃,报错信息常为“0x??????指令引用的 0x?????? 内存。该内存不能为 read”。
原因:Iocomp 控件内部有事件回调机制,比如鼠标悬停事件、值变更事件。如果窗体卸载时没有将控件引用置为空,而控件内部还持有窗体指针,事件回调就会访问已销毁的窗体对象,触发内存访问冲突。这类问题在源码级定制后特别容易出现,因为你可能改动了事件触发时机。
解决:在 Form_Unload 里显式释放控件引用:
Private Sub Form_Unload(Cancel As Integer) ' 先把控件的事件回调断开,再卸载窗体 Set iTrendChart1 = Nothing Set iGauge1 = Nothing Set iStatus1 = Nothing End Sub逻辑说明:Set 为 Nothing 会调用控件的析构逻辑,让它主动解除对窗体句柄的引用。顺序不能反过来——先卸载窗体再释放控件,等于先杀了回调接收者,控件再触发事件就没地方去了。
参数说明:如果窗体里控件数量多,建议把释放代码统一放在一个 Sub 里调用,不要散落在各事件过程里。排查崩溃时,先注释掉事件过程里的自定义代码,确认是回调问题还是你的代码问题,再逐段放开。
6. 源码级定制:挑趋势图游标彻底改透
有源码和没源码的差别,只有当你需要“多一点功能”时才感受得到。我举一个最实际的例子:现场调试人员要求趋势图上有一个可拖动的游标,鼠标点某个位置时显示该时间点对应的过程值。控件自带的交互模式里没有这个能力,在源码里加却很直观。
' VB6 环境:利用控件的 MouseMove 事件反推坐标值 ' 核心思路:X 像素坐标 -> 时间偏移量 -> 通道数据索引 Private Sub iTrendChart1_MouseMove(Button As Integer, Shift As Integer, x As Single, y As Single) Dim timeOffset As Double Dim yValue As Double ' 将像素 x 坐标映射为时间偏移(常见做法:按控件宽度均分) timeOffset = (x / iTrendChart1.Width) * iTrendChart1.TimeWindow ' 从通道数据中取出对应点的值 Dim idx As Long idx = iTrendChart1.Channels(0).Data.Count - (timeOffset / 1000) - 1 If idx >= 0 And idx < iTrendChart1.Channels(0).Data.Count Then yValue = iTrendChart1.Channels(0).Data.Item(idx).Value Label1.Caption = "游标值: " & Format$(yValue, "0.00") End If End Sub逻辑说明:这个写法不是控件原生 API,而是基于“趋势图时间轴线性映射”这一基本假设推导的近似解法。把鼠标 X 像素坐标除以控件像素宽度,再乘时间窗口毫秒数,就得到鼠标所在位置距当前时刻的时间偏移;用通道数据点位数量减去偏移点数,就定位到对应的数据点。
参数说明:Data.Item(idx) 返回的是数据点对象,含 Value 和时间戳属性。如果通道内数据点太多导致索引偏差,最稳妥的办法是直接遍历数据点的时间戳,找到距离目标时间最近的那一个,代价是 O(n),但趋势图数据量通常只有几百到几千点,完全可接受。
这个游标功能在交付前的现场调试里救了命——工艺人员指着屏幕上某个时间点问“这个点当时的温度是多少”,以前我只能暂停趋势图再肉眼估读,加上这个小工具之后整个反馈流程变短了。但从那以后我每次改完源码,都强制在 Form_Unload 里删掉新加的游标事件处理,避免发布版本关闭时再崩一次。这个习惯救过我不止一次,希望帮到你。
本文还有配套的精品资源,点击获取