ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

MFC ActiveX曲线控件在工控上位机中的应用与集成

MFC ActiveX曲线控件在工控上位机中的应用与集成 简介这是一套基于MFC ActiveX技术开发的工控图形控件集包含曲线、折线、柱状图三种绘制控件面向Windows桌面应用开发者主要解决工业监控系统中传感器数据与历史数据的实时可视化、报表展示等问题。资源包共158个文件以C头文件与源文件、BMP/ICO/CUR界面资源、编译好的DLL/OCX组件及VS工程文件为主体同时附带PDF/DOCX说明文档和演示项目整体仅2.32MB便于下载、集成和二次开发。目前已有85人学习下载。控件集针对工控数据高实时性、高准确性的特点做了性能与安全性设计开发者可通过源码研究实现原理也可结合示例快速上手并按实际需求调整图表颜色、线条样式、坐标轴格式等。对于需要快速提升数据可视化能力、缩短自绘控件开发周期的MFC程序员而言是一套可直接落地的实用工具。1. 工控上位机的实时曲线为什么都绕不开 MFC ActiveX 控件一块触摸式工控一体机跑着 MFC 对话框写的上位机软件温度、压力、流量每隔 200ms 刷新一次还要带历史回放和报表导出。第一次用 GDI 自绘曲线的工程师多半会踩同一个坑单独画一条折线很容易但一旦要滚动窗口、缩放、右键标尺、定制曲线颜色重绘逻辑立刻膨胀UI 线程开始卡顿。相比之下把画曲线封装成一个 ActiveX 控件注册后像标准组件一样拖进对话框数据通过 OLE 接口下发宿主程序完全不关心坐标变换——这是工控行业沉淀了很多年的成熟做法。这套基于 MFC ActiveX 的曲线、折线、柱状图绘制控件源码目录叫 ST_Curve-master曲线用于温度压力趋势折线用于阈值变化过程柱状图用于产量电量统计。它适合准备在 MFC/VB 上位机里做实时监控的工程师也适合要二次开发、把图表改成自家品牌风格的团队。下面从解压后的文件开始讲注册、接口、集成和排错。2. 解压后的资源文件与 ActiveX 控件注册机制2.1 压缩包里那串 .bmp 位图不是装饰品解压后看到的 Textora.bmp、Textmauv.bmp、menuimages_hc.bmp、06.bmp、46.bmp、101.bmp 很容易被当成无关杂项扔掉其实它们是控件自带工具栏和菜单的图标资源。Textora / Textmauv 从命名看是文本横向、竖向排列的切换图标在曲线控件里对应 X 轴文字方向、标签旋转这类功能menuimages_hc.bmp 是高彩色菜单图标条多个 16×16 或 24×24 图标横向排在一张位图里运行时用 CImageList 按索引切片加载06.bmp、46.bmp、101.bmp 这类数字命名的位图通常是 16 色老式图标重复出现的那一份是给禁用态或高亮态用的。它们的分工可以整理成下表。文件推断用途二次开发注意点Textora.bmp / Textmauv.bmp文字横排/竖排切换图标修改时保持 .rc 里的文件名引用一致menuimages_hc.bmp工具栏/菜单高彩图标条切片顺序由 CImageList 的 Add 顺序决定06.bmp / 46.bmp / 101.bmp16 色功能图标正常/禁用两套换图标只替换像素别改尺寸处理外部位图的常见做法是解压后不要乱改名保持与 .rc 资源脚本里的引用一致。如果二次开发时发现控件工具栏空白或图标变灰先检查这些 bmp 是否在可执行文件同目录或者有没有被 .rc 用BITMAP IDB_xxx res\\xxx.bmp写成编译期嵌入。我一般会先用 Visual Studio 的图像编辑器直接改位图像素确认效果后再回 MFC 源码里调整 CImageList 的 Add 顺序——顺序一旦错位所有按钮图标会整体串位排查起来非常费时间。2.2 regsvr32 注册控件32 位与 64 位的坑ActiveX 控件本质是一个 COM 进程内服务 DLL注册的动作是让它把 CLSID、ProgID、类型库写进注册表宿主程序才能按名字ProgID例如STCurve.STCurveCtrl找到它。手工注册用 regsvr32最常用的两条命令rem 注册/s 静默不加 /s 会弹成功对话框 regsvr32 /s D:\work\ST_Curve\Release\STCurve.ocx rem 反注册 regsvr32 /u /s D:\work\ST_Curve\Release\STCurve.ocx注意在 64 位 Windows 上C:\Windows\System32\regsvr32.exe是 64 位版本C:\Windows\SysWOW64\regsvr32.exe才是 32 位版本。绝大多数工控 ActiveX 控件是 32 位编译的必须用 SysWOW64 下的 regsvr32 注册否则会报模块已加载但找不到入口点 DllRegisterServer。注册完成后用下面命令验证 ProgID 是否写入了注册表reg query HKCR /f STCurve /s /kHKCR是 HKEY_CLASSES_ROOT 的缩写/f指定要查找的字符串/s递归搜索子键/k表示只匹配键名。能查到STCurve.STCurveCtrl和CLSID\{...}这样的键就说明注册成功。网上常见的call to DllRegisterServer for xxx.ocx returns 0x80040200报错本质是控件内部注册函数执行失败。按经验工控机上多半是三种原因机器没装对应版本的 VC 运行库、ocx 依赖的其他 DLL 缺失、注册时没有管理员权限。分别处理装 vcredist_x86注意是 x86 不是 x64、用 Dependency Walker 或 Process Explorer 看加载失败的模块、用管理员 CMD 重新注册。2.3 不写代码先验证ActiveX Control Test Container控件注册成功后不建议立刻往项目里塞先花两分钟用测试容器做冒烟。老版本 Visual Studio 的工具菜单里有 ActiveX Control Test Container程序名 tstcon32.exe打开后 Edit → Insert New Control在列表里挑 ST Curve Control控件会出现在一个窗口里。此时可以做三件事右键调出 Invoke Methods 窗口手动执行 AppendPoint 往曲线里塞点打开属性页改 BackColor、AxisColor 看是否即时重绘拖动窗口边缘看控件是否按比例缩放。这三项过了再进对话框集成否则排查范围会被放大到宿主工程里很难定位是控件问题还是容器问题。新版 VS 不再带 tstcon32替代方案是快速建一个空的 MFC 对话框工程把控件拖上去跑一下成本差不多效果一样。3. 曲线、折线、柱状图的绘制模型与数据绑定接口3.1 三种图表在底层是三种不同的数据模型很多工程师以为三种图只是画法不同、改个参数就行实际上它们的输入数据单位都不一样接口设计必须分开。折线图的数据单位是离散点绘制时 MoveToEx / LineTo 逐个连接适合等间隔采样但不需要平滑的场景曲线图在折线基础上增加平滑插值常见做法是只在缩放时按像素步长用 Catmull-Rom 样条重采样保证放大后曲线不出现明显折角。柱状图的数据单位不是点是类别桶每个桶对应一个矩形矩形高度就是数值。类型数据单位工控典型场景底层绘制曲线连续点 (x, y)温度、压力、液位趋势折线 样条插值折线连续点 (x, y)阈值变化、启停记录LineTo 直接连线柱状图类别值班产量、电量、故障次数FillRect 矩形填充改造源码时不要把柱状图数据硬塞进曲线数组里否则坐标轴、图例、Tooltip 全要跟着改。实时曲线和折线可以共用一套点数组接口柱状图单独用按类别赋值的接口这是这套控件源码里最值得保留的设计。3.2 属性、方法、事件ActiveX 控件对外暴露的三张脸MFC ActiveX 控件对外暴露三样东西属性Property、方法Method、事件Event在 .odl 或 .idl 文件里对应 dispinterface 和 event interface。以 ST_Curve 改造后的接口为例属性一般有 BackColor、AxisColor、LineWidth、XMin/XMax、YMin/YMax、ShowLegend方法有 AppendPoint、SetSeriesData、ClearSeries、SetXRange事件有 OnPointClick鼠标点中数据点后通知宿主参数是系列号、索引和坐标值和 OnRangeChanged用户滚轮缩放后通知宿主同步其他控件。在 MFC 宿主里ClassWizard 会生成一个包装类本质是 IDispatch 的封装。追加一个点的包装函数长这样void CSTCurveCtrl::AppendPoint(short nSeries, double x, double y) { static BYTE parms[] VTS_I2 VTS_R8 VTS_R8; // 参数类型声明 InvokeHelper(0x11, // DISPID来自 .odl DISPATCH_METHOD, VT_EMPTY, NULL, parms, nSeries, x, y); }InvokeHelper是 MFC 调用自动化接口的标准入口第一个参数是调度 ID由 .odl 里id(0x11)决定parms数组声明后面三个实参的类型VTS_I2 是 16 位整数VTS_R8 是 64 位双精度浮点顺序必须与控件侧的类型声明完全一致。常见坑是宿主工程是 32 位而控件侧参数声明为 long32 位两边字节数对不上数据一多就乱点。事件到达宿主代码通常表现为对话框 ClassWizard 里多出 OnPointClick 这样的成员函数。鼠标点在柱状图上时宿主弹出 Tooltip 显示当班产量 86.3 件比每次去读整个数组再计算高效得多。3.3 数据下发两条路逐点追加与整段覆盖实时监控和报表回放对接口的要求完全不同。实时场景下比如 250ms 一帧数据是流水账用逐点追加宿主每帧调一次 AppendPoint控件内部维护环形缓冲区旧点被自动覆盖。报表或历史回放场景下数据是整块查出来的几百上千行逐点跨进程调用 InvokeHelper 要几千次性能不可接受必须整段覆盖。整段覆盖的常见做法是传 SAFEARRAYCOleSafeArray sa; DWORD dims[2] { (DWORD)nCount, 2 }; // nCount 行、2 列x 和 y sa.Create(VT_R8, 2, dims); // 双精度浮点二维数组 long idx[2]; for (int i 0; i nCount; i) { idx[0] i; idx[1] 0; sa.PutElement(idx, xs[i]); // 第 i 行第 0 列 idx[1] 1; sa.PutElement(idx, ys[i]); // 第 i 行第 1 列 } m_curve.SetSeriesData(0, sa); // 0 号系列整段替换dims里的行数列数顺序、PutElement的行索引和列索引都要和控件侧COleSafeArray::AccessData的解析顺序一致。这个模型与 .NET Chart 控件里 Series 数据绑定是同一个思想数据源整体替换控件只重算布局不在调用层一层层拷贝。区别是 ActiveX 走 COM 的变体参数跨语言都能调代价是调试时问题藏得深。SetSeriesData 之后通常还要补一个InvalidateControl(FALSE)有些实现里重绘是惰性的不主动刷界面会一直显示旧数据。4. 在 MFC 对话框工程中集成 ActiveX 控件从拖拽到实时刷新4.1 插入控件与包装类的生成前提是第 2 章的注册已经通过测试容器冒烟。打开 MFC 对话框工程的 .rc右键对话框空白处选插入 ActiveX 控件在列表里挑 ST Curve Control列表读的是注册表里的控件条目没注册就看不到拖到对话框上得到 IDC_CURVE_CTRL。右键控件选添加变量VS 会生成 CSTCurveCtrl 包装类并把它挂到对话框成员上DoDataExchange 里多一行void CMonitorDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_CURVE_CTRL, m_curve); // 控件与成员变量绑定 }DDX_Control在对话框创建时把资源里的 ActiveX 实例包装成 CSTCurveCtrl 对象之后 m_curve 的任何方法调用都走 IDispatch。设计期属性BackColor、AxisColor 等存在对话框资源里换机器不丢运行期改属性只影响当前实例。要改默认外观直接在属性页里调不要每次 OnInitDialog 都手动 SetBackColor除非确实需要根据工况动态切换主题。4.2 定时器驱动曲线滚动刷新实时趋势的核心是新数据进来、旧数据滑出去。常见做法是在 OnInitDialog 里SetTimer(IDT_DATA, 250, NULL)配合 ON_WM_TIMER 消息映射由定时器驱动采集和刷新void CMonitorDlg::OnTimer(UINT_PTR nID) { if (nID IDT_DATA) { double t m_tick * 0.25; // 时间轴单位秒 double v ReadPlcFloat(m_iChannel); // 读一个通道实时值 m_curve.AppendPoint(0, t, v); // 追加到 0 号系列 // 滚动窗口只保留最近 600 个点的可见区间 if (m_tick 600) { m_curve.SetXMin((m_tick - 600) * 0.25); m_curve.SetXMax(t); } m_tick; } CDialogEx::OnTimer(nID); }ReadPlcFloat 是示意函数实际工控环境里对应 Modbus 读保持寄存器、OPC DA 异步读或者厂商 SDK 的一次取数。注意 250ms 的定时器跑在 UI 线程上如果一次读数超过几十毫秒比如走网口且对端响应慢画面就会卡。我一般会改成工作线程负责采集PostMessage(WM_DATA_READY, wParam, lParam)通知 UI 线程只做 AppendPoint——控件的所有接口都必须在创建它的线程里调用跨线程调 InvokeHelper 会偶发崩溃。滚动窗口用 SetXMin/SetXMax 比整体重置数据数组便宜得多控件只需重新计算坐标映射点数组不动。如果客户要的是风机特性曲线这类横轴流量、纵轴压头的静态曲线就不需要滚动窗口把标定点整段 SetSeriesData 一次性灌进去再打开数据点标记属性即可。4.3 柱状图的批量推送与点击联动柱状图对应的典型场景是24 小时产量这类横轴为类别的统计。整段数据一次性推入比逐根柱子赋值快一个量级double values[24]; for (int i 0; i 24; i) { values[i] m_hourOutput[i]; // 每小时产量 } COleSafeArray sa; sa.CreateOneDim(VT_R8, 24, values); // 一维数组按元素序对应类别 m_bar.SetBarData(sa); if (values[23] 1000) { m_bar.SetBarColor(23, RGB(220, 60, 60)); // 超限柱子标红 }CreateOneDim 第一个参数仍是 VT_R8 双精度第二个是元素个数第三个是数据源指针MFC 会拷贝一份进 SAFEARRAY调用后原数组可以立即释放。类别的标签00:00、01:00…一般在初始化时逐个设置标签字体颜色跟随 AxisColor。超限标红用的 SetBarColor 要确认颜色参数约定是 OLE_COLOR 还是普通 long搞反了会出现明明设了红色却显示成蓝色的玄学问题排查时先看 .odl 里属性类型。事件侧OnPointClick 传回柱子索引宿主拿它联动另一块文本框显示该时段故障 N 次比轮询取值省事也是组态类软件常见的交互方式。5. 高分辨率自适应、双缓冲与加载失败排查5.1 自适应屏幕分辨率与双缓冲两个改动工控机从 1024×768 换到 1920×1080 触屏控件最容易出的问题是只画了左上角一块原因几乎都是 OnDraw 里用了写死的像素坐标。MFC ActiveX 控件的绘制入口是 OnDrawrcBounds 是控件当前实际边界正确做法是所有元素基于 rcBounds 计算相对位置void CSTCurveCtrl::OnDraw(CDC* pdc, const CRect rcBounds, const CRect rcInvalid) { CRect rc rcBounds; // 当前实际大小不能用固定值 m_rcPlot CRect(rc.left rc.Width() / 12, rc.top, rc.right - rc.Width() / 12, rc.bottom - rc.Height() / 10); // 画轴、画线全部基于 m_rcPlot 的 Width()/Height() 计算像素位置 }再配合 COleControl 的 GetControlSize 在初始化时读一次设计尺寸把 X、Y 两个方向的缩放因子存成成员变量数据点转像素的函数里统一乘上因子宿主窗口变大变小时控件就能自动跟随缩放。曲线点数超过 1000 或 OnDraw 里同时画网格、轴、图例和三条曲线时直接画必然闪烁标准做法是先画到兼容内存位图再用 BitBlt 整体贴回屏幕配合宿主端 SetRedraw(FALSE) 批量更新、最后 RedrawWindow 合并重绘。5.2 加载失败与编译报错的排查表现象可能原因处理方法注册报 0x80040200DllRegisterServer 失败缺 VC 运行库、依赖 DLL 缺失、无管理员权限装 vcredist_x86查依赖管理员 CMD 重注册64 位系统拖不进控件或对话框崩溃用 64 位 regsvr32 注册了 32 位 ocx改用C:\Windows\SysWOW64\regsvr32.exe注册插入后黑屏只有边框OnDraw 用固定坐标绘制按 5.1 改成 rcBounds 相对布局编译源码报此项目需要 MFC 库工程没链接 MFC 或本机没装 MFC 组件VS Installer 安装 MFC 组件工程属性→MFC 使用共享 DLL控件图标空白或工具条串位外部 bmp 未随包分发或 CImageList 顺序错保持解压目录完整检查 .rc 的 BITMAP 引用路径最后是一个验证技巧控件改完属性或数据结构后先开测试容器或临时对话框工程手动执行一次 AppendPoint 加 SetXRange 冒烟确认数据点与轴线对齐再回到正式工程跑定时器。这样能把数据下发、坐标换算、重绘三层问题拆开定位不会一上来就怀疑是控件本身坏了。本文还有配套的精品资源点击获取
返回列表