简介围绕 WinCC 画面图层隐藏与显示的项目案例包面向已具备一定 WinCC 组态基础、希望掌握画面动态交互与 VBS 脚本控制的自动化工程师及学习者可用于理解分层画面设计、按钮触发与状态切换等常见需求。内容聚焦图层可见性管理借助 Layer.Visible 等脚本思路把按钮点击、数据加载、错误提示等事件与图层显示状态关联帮助读者掌握事件驱动逻辑并体会分层组织画面、简化复杂逻辑和提升项目可维护性的做法。压缩包共 298 个文件、约 10.1MB以 262 个 rpl 项目相关文件及 pdl 画面、sav、dum 等为主辅以 xml、ini 配置文件和 mdf/ldf 数据库文件并含 LayerVisible 相关脚本线索便于对照案例梳理工程结构与脚本调用关系。已有 2288 人学习可作为 WinCC 图层控制与事件驱动脚本的实操参考也适合在既有项目中借鉴可见性切换思路进行画面优化。1. 操作工在一屏四百个对象里找泵WinCC 画面图层隐藏显示要解决的事某水处理车间的 WinCC 项目案例里一张总览画面上四百多个对象全堆在一起罐体、管线、阀门、仪表读数、报警闪烁块、检修挂牌、调试注释。WinCC 画面图层的隐藏显示功能本来能把这些按层拆开看图形编辑器里勾一下就能只留下某一层可一进运行系统设计期勾好的层可见性就固定死了操作工还是得在一屏里找那台泵。这套方案要解决的就是这件事把「哪一层、给谁看、什么工况下看」从画面组态里抽出来做成一张可配置的层表和一段脚本用一个位掩码变量驱动整屏显隐。它适合正在做组态交付、需要按角色分视图的工程也适合把「组态软件高级应用」当作毕设或课设题目的读者——这套结构能直接搬进你的方案章节里。2. 图层能力的边界与四条替代路线图形编辑器里的层开关为什么管不到运行系统2.1 WinCC 的层是设计期工具不是运行期开关图形编辑器默认提供 32 个层0 到 31你把对象分配给某一层编辑时可以选择「只显示某几层」避免在同屏几百个对象里误选。这个可见性是编辑视图的属性作用对象是设计人员不是操作工。运行系统加载画面文件时所有对象都在层级只是挂在对象上的一个标签。很多方案一开始就翻车在这里以为给对象改个层号就能藏起来。VBS 里ScreenItem确实有Layer属性也能写但改了之后只是把这个对象挪到另一个层去归属画面上纹丝不动。 这行看着合理实际不产生任何隐藏效果只是改了对象的归属层 HMIRuntime.Screens(Main).ScreenItems(L03_Pipe_01).Layer 6真正在运行期起作用的是两个属性Visible是否绘制和Enabled是否响应操作。Visible False的对象不参与绘制、不接收鼠标事件Enabled False的对象还在画面上看得见但点不动。这个区别在「检修模式」里很关键——某台泵要留在图上让操作工知道位置只是不允许启停那就该动Enabled而不是Visible。再往上还有两个可用的容器手段组Group的Visible会连带所有子对象一起生效画面窗口Picture Window的「画面名称」和「显示」属性可以整块挂载或卸载一张子画面。2.2 四条路线摆在一张表里比遍历、编组、画面窗口、动态对话框知道能改什么之后剩下的是「怎么改代价最小」。实际项目里能走通的有四条路差别不在能不能实现而在对象数量上去之后的表现和维护成本。路线实现方式切换耗时400 对象量级适用场景后期维护A 对象遍历脚本遍历画面对象逐个设Visible300800 ms有明显停顿对象少、层数多、画面结构零散改组态不用改脚本B 组 Visible每层对象编成一个组脚本只动几个组50 ms 以内中型画面、组态时就能按层规划新增对象容易漏进组C 画面窗口叠加每层一张子画面挂在主画面的画面窗口上50 ms 以内层数固定、需要多人分工编辑子画面可独立复用D 动态对话框每个对象的Visible直接绑变量位中等取决于变量刷新周期只有几十个对象的演示画面改一层要动几十个对话框最贵选型上我的习惯是主画面结构稳定、层与层之间坐标能提前对齐的走路线 C画面已经画完、不想返工重画的走路线 B让脚本去认前缀只有二三十个对象的临时演示路线 A 最省事。路线 D 我只在补丁场景用比如客户临时要求「这块报警牌只在工程师登录时出现」单点加一个动态对话框比改脚本快。这里有个血泪经验不少人在路线 A 上花了大力气做优化结果发现真正拖慢刷新的不是脚本本身而是Visible频繁切换引起的整屏重绘。与其抠脚本不如把对象收敛成组或者子画面从结构上减少被操作的对象数量。2.3 Z 序和点击区域隐藏了不等于让出位置对象设成Visible False之后它在 Z 序里占的位置不会自动腾出来画面窗口之间的叠放顺序图形编辑器里置顶、置底那一组操作决定了谁盖住谁。这一点在路线 C 里尤其要注意三层子画面用的都是同一个画面窗口位置切换时如果窗口大小和坐标不一致操作工看到的就是「切一下就错位」。另一个常被误解的点是点击。Visible False的对象不接收鼠标事件这点很多人以为会实际不会。真正会吃掉点击的是那些「透明但可见」的对象——用来做背景分隔的矩形填充块、只有边框的框线、贴在设备上做热区的透明按钮。排查「点不到设备」的问题时先按 Ctrl 逐个选中看有没有透明覆盖物比翻脚本快得多。2.4 层和状态是两个概念别揉进一个变量层是静态分类设备层、仪表层、检修层、调试层。状态是动态量当前是正常运行、停车检修还是培训演练。层掩码回答「哪些层可见」工况回答「现在该用哪个掩码」。把两者揉成一个变量的后果是每加一种工况就得回头改脚本里的 if 分支改到第四五个工况时脚本基本没法读了。分开存的好处是扩展成本低新增工况只是往预设表里加一行掩码值脚本一行不动。我在方案评审时经常问一句「加一个新工况要改几个地方」答案超过两个就说明这层抽象没做对。3. 层表、掩码与命名前缀把「哪层给谁看」抽成配置3.1 层定义表六个字段把业务规则固化下来层表是整个方案的地基它把「业务上分几层、每层默认开不开、谁能看」写成数据脚本只负责执行。小项目我一般直接写成脚本里的常量数组稍大的项目放 SQL Server 用 ADO 读字段结构是同一套。CREATE TABLE hmi_layer_def ( layer_id TINYINT NOT NULL, -- 层号 0-31与对象名前缀里的两位数字对应 layer_name NVARCHAR(32) NOT NULL, -- 层名称配置界面和文档里用 bit_value INT NOT NULL, -- 该层在掩码中的权重等于 2 的 layer_id 次方 default_on BIT NOT NULL, -- 上电或复位后默认是否可见 min_user_level TINYINT NOT NULL, -- 可见所需的最低权限等级 remark NVARCHAR(128) NULL -- 用途说明给接手的人看 );bit_value看起来是冗余字段但它让层表可以直接被人读——运维翻表时看到 8 就知道是第 3 层不用心算。真正参与运算的只有layer_id和min_user_level。填好的数据大概长这样layer_idlayer_namebit_valuedefault_onmin_user_levelremark0基础设备1是0罐体、泵、管道骨架1仪表数值2是0温度、压力、流量读数2报警联锁4是0报警闪烁块、联锁标记3检修标注8否2挂牌、盲板、接地线4调试信息16否3变量地址、内部调试标签5辅助管线32否0备用线、跨线、旁路min_user_level这一列是权限闸门。层掩码算出来之后还要跟当前用户等级做一次与运算等级不够的层强制置 0避免有人用调试账号登录后手动勾开检修标记去改现场。3.2 位掩码一个字节装下八层可见性八个层用一个字节就够层 0 占最低位权重 1层 1 权重 2层 2 权重 4依次翻倍。可见性判断就一句位与。 判断第 n 层当前是否应该显示掩码对应位为 1 即可见 Function IsLayerOn(ByVal n) IsLayerOn ((SmartTags(g_LayerMask) And (2 ^ n)) 0) End FunctionWinCC 内部变量g_LayerMask建「无符号 8 位」范围 0 到 255正好覆盖八层。超过八层就换 32 位无符号或者在脚本里拆两个字节处理但十六层以上的画面通常说明该拆画面了不是靠掩码能救的。常见的工况预设值可以直接写在层表旁边的说明文档里也可以做成配置界面的下拉项工况掩码值说明正常运行7层 0、1、2 可见停车检修15加上层 3 检修标注工程师调试63层 0 到 5 全开培训演示3只留设备和仪表关掉报警闪烁避免干扰夜间巡检11设备 检修 报警关掉数值减少视觉噪音掩码值的计算不要手写十进制容易错。判断第 0 层和第 1 层可见写1 2 3写清楚比写 3 更好维护。3.3 对象命名前缀让脚本能自动认领对象路线 A 和路线 B 都依赖脚本按名字找对象所以命名规范必须提前定死。我用的是L{两位层号}_{类型简写}_{序号}比如L00_TANK_01、L02_ALM_07、L03_TAG_12。层号固定占两位脚本取第 2、3 个字符就能还原层号不用查表。 从对象名还原层号例如 L03_TAG_12 返回 3 Function LayerOf(ByVal objName) If Left(objName, 1) L Then LayerOf CInt(Mid(objName, 2, 2)) Else LayerOf -1 不符合规范的对象不参与显隐控制 End If End Function不要用对象的「注释」属性来存层信息。注释在图形编辑器里看得见但运行期读取要走GetPropChar之类的接口代价高而且改注释的人往往不知道它承担着控制逻辑。名字是唯一一个改错了立刻会暴露的字段让它承担这个责任最稳。类型简写这一层主要是给人看的脚本不解析但它有个实际好处接手的人翻对象列表时能一眼看出这是设备、文字、报警还是标注排查「为什么这个对象没跟着隐藏」时省很多时间。3.4 变量放在哪、谁写谁读三个内部变量足够撑起整个机制g_LayerMask当前生效的层掩码、g_UserLevel当前登录用户的权限等级、g_WorkMode当前工况编号用于界面回显。前两个参与显隐计算第三个只是给人看的。g_LayerMask要设成掉电保持。不设的后果是现场断电重启后所有层回到默认值检修班组一上班发现昨天挂的牌全没了只能重新勾一遍。保持属性在变量属性里勾选即可也可以在主画面Open事件里按层表的default_on重新算一次两条路都行后者更可控——我一般两个都做保持属性防意外启动脚本兜底。多客户端场景要提前想清楚。全局变量在服务器上是共享的一个客户端把检修层关掉另一个客户端跟着变。如果现场是「中控室统一视图」的模式这正好是想要的效果如果是几个岗位各看各的就得把掩码放到客户端的本地脚本变量里用HMIRuntime.Tags的本地作用域或者干脆每个客户端登录时各算各的。这件事在方案评审阶段就要问清楚事后改的成本不小。4. 脚本实现从一口气遍历到只动三个对象4.1 VBS 批量遍历带组递归的基础版本路线 A 的核心就是遍历画面对象、按名字解析层号、按掩码设置可见性。难点不在逻辑在于组对象ScreenItems只遍历顶层组里面的对象要递归进去否则编了组的设备永远不受控。 递归处理一个容器内所有带层前缀的对象 Sub ApplyMaskToContainer(ByVal container, ByVal mask) Dim obj, lNo For Each obj In container.ScreenItems lNo LayerOf(obj.ObjectName) 复用 3.3 的解析函数 If lNo 0 Then obj.Visible ((mask And (2 ^ lNo)) 0) End If 组对象要继续往下走试探它有没有子集合有就递归 On Error Resume Next Dim n n obj.ScreenItems.Count If Err.Number 0 Then ApplyMaskToContainer obj, mask If lNo 0 Then obj.Visible ((mask And (2 ^ lNo)) 0) End If Err.Clear On Error GoTo 0 Next End Sub 画面打开时或掩码变化时调用 Sub Main() ApplyMaskToContainer HMIRuntime.Screens(MainOverview), SmartTags(g_LayerMask) End Sub几个细节值得说明。For Each比按下标取对象安全不用纠结集合索引从 0 还是 1 开始。递归判断用On Error Resume Next试探ScreenItems.Count是否可读而不是靠Type属性比对字符串——不同版本对组类型的字符串表示不完全一致试探法更耐版本。组自身的Visible要在递归之后重设一次因为子对象设Visible True在某些情况下会把组的可见状态带回来重设一遍省心。这个版本在两百个对象以内切换基本感觉不到停顿超过四百个就能看到明显的一次白屏这就是路线 A 的天花板。4.2 C 脚本版本对象过千时把切换压进 100 ms对象数量上去之后VBS 的解释执行和逐个属性写入就顶不住了。换成 C 脚本跳过遍历、直接对着预生成的名单批量写属性速度能差一个数量级。#include apdefap.h /* 名单由层表工具生成两个数组一一对应长度固定在 g_LayerObjCount */ extern char g_LayerObjName[][32]; extern unsigned char g_LayerObjBit[]; extern int g_LayerObjCount; void ApplyLayerMask(char* lpszPictureName, unsigned char mask) { int i; BOOL bWant; for (i 0; i g_LayerObjCount; i) { bWant (mask g_LayerObjBit[i]) ? TRUE : FALSE; /* 先读后写状态没变就不写省掉一次无谓的重绘 */ if (GetPropBoolean(lpszPictureName, g_LayerObjName[i], Visible) ! bWant) { SetPropBoolean(lpszPictureName, g_LayerObjName[i], Visible, bWant); } } }lpszPictureName是画面名由触发脚本传进来mask是当前掩码从内部变量读。名单数组不用手写写个小工具按层表扫一遍 PDL 文件生成头文件改了组态重新生成一次比手工维护靠谱。GetPropBoolean那层判断是性能关键——四百个对象里一次切换真正变状态的通常只有几十个省下的属性写入直接反映在切换手感上。C 脚本的代价是可读性接手的人得能读 C。我的判断标准是对象总数稳定超过六百或者切换频率高到操作工一天要切几十次才值得上 C否则 VBS 加路线 B 的组合更省事。4.3 画面窗口方案工程上更稳的主线做法如果画面还没开始画或者愿意返工我优先推路线 C。每层做成一张独立子画面Layer0_Base.PDL、Layer3_Maint.PDL…主画面上按位置摆一排画面窗口窗口大小和坐标完全一致叠在一起。切换时只动这几个窗口的属性跟画面里有多少对象完全无关。 按掩码切换一组画面窗口的可见性窗口名与层号绑定比如 winLayer3 对应层 3 Sub SwitchLayerWindows(ByVal mask) Dim scr, i, win, want Set scr HMIRuntime.Screens(MainOverview) For i 0 To 5 层数由层表决定这里假设用了 6 层 Set win scr.ScreenItems(winLayer i) want ((mask And (2 ^ i)) 0) If win.Visible want Then win.Visible want End If Next End Sub这个方案的好处不止快。子画面可以分给不同的人编辑设备专业画设备层仪表专业画仪表层互不打架改一层不用动主画面层与层之间的对齐靠统一的画面尺寸和坐标原点保证一次性对齐好后面加内容不会错位。代价是层与层之间需要「跨层」的元素会比较别扭——比如一根管道从设备层出发、穿过检修层那就得在每个相关子画面里各画一段或者把这根管道单独提到一个「公共层」常显。这个取舍在方案设计阶段要跟业主确认别等交付前才发现。还有一点画面窗口的Visible False之后窗口位置不会响应鼠标也不会盖住下层这对操作安全性是好事。但如果用的是「切换画面名称」而不是切换可见性空画面窗口在某些版本下仍然是个透明覆盖物会吃掉点击。我一般直接用Visible不用切画面名。4.4 触发链与权限联动从按钮到整屏刷新只走三步整个机制的执行链要短长了就容易出现「按钮点了没反应也不知道卡在哪」的黑匣子。第一步按钮或工况选择写入g_WorkMode第二步一段脚本根据工况查预设表算出掩码和权限闸门写g_LayerMask第三步g_LayerMask配一个变量触发值变化时执行显隐函数。三步各管一件事出问题时看中间变量就知道断在哪。权限闸门放在第二步里算不要放到第三步的显示函数里否则每次刷新都要重算一遍权限而且调试时容易忘记当前是什么权限等级。 用户登录成功后调用算权限再算掩码 Sub OnUserLogin(ByVal userName) Dim lvl, baseMask lvl QueryUserLevel(userName) 查用户表返回 0-3 SmartTags(g_UserLevel) lvl baseMask SmartTags(g_LayerMask) 权限不足的层强制关掉层 3 需要等级 2层 4 需要等级 3 If lvl 2 Then baseMask baseMask And (255 - 8) If lvl 3 Then baseMask baseMask And (255 - 16) SmartTags(g_LayerMask) baseMask 写回触发刷新 End Sub255 - 8这种写法比直接写 247 好读意思是「把第 3 位清零」。写回g_LayerMask会触发第三段的变量触发脚本整屏刷新一次操作工看到的就是「换了个人登录视图跟着变」。用户切换、退出登录都要走这条链退出时按层表默认值复位别留着上一个人的视图给下一个人。5. 避坑与排查图层显隐最容易翻车的五个点5.1 改完 Visible 画面一刷新又弹回来现象脚本执行了对象也确实隐藏了一瞬间但画面下一次刷新时全部恢复原样日志里看不出报错。原因通常有三个。一是对象上还挂着动态对话框Visible被变量直接驱动脚本写进去的值下一周期就被覆盖二是同一个属性有两个脚本在写比如画面Open事件写一次、全局周期脚本又写一次三是改的其实是组内对象但组本身的Visible仍是True看起来像没生效。解决先按 Ctrl 选中对象看属性面板里Visible右边有没有小三角有就是挂了动态。有的话把动态去掉统一交给脚本。然后在全局脚本编辑器里搜一遍对象名确认没有第二个写入点。组的情况直接把脚本改成先算子对象再设组或者干脆只设组不设子对象。5.2 隐藏的对象还能被点到现象检修层已经关掉了操作工点原来挂牌的位置居然弹出了挂牌详情画面。原因不是Visible失效而是上面盖着一个透明热区。做画面时经常在设备上贴一个「透明按钮」用于点击跳转这个按钮如果没编进层组、名字也不带层前缀脚本就管不到它它就一直躺在那里吃点击。解决把所有透明热区纳入命名规范跟着所属设备一起编层。排查时按 Ctrl 全选把填充设为无、边框设为无的对象挨个看一遍或者用对象的「Tab 顺序」列表逐个点能点的都会亮。这件事在画面评审阶段做一遍比交付后现场救火便宜得多。5.3 切一次层画面卡两秒现象点工况切换按钮画面明显停顿操作工以为死机连点几下之后状态彻底乱了。原因基本是路线 A 在对象过量的画面上硬扛每个Visible写入都触发一次局部重绘四百个对象就是四百次。另外如果脚本挂在周期触发上比如 1 秒一次那就不只是切换时卡平时也一直在偷偷跑。解决短期把触发方式从周期改成变量变化触发只在掩码真变的时候跑中期把对象收敛成组脚本操作的对象数从四百降到六个长期换路线 C动的是画面窗口跟对象数彻底解耦。另外切换期间可以把主画面的更新时间临时调大切完再恢复属于应急手段别当常规方案用。5.4 两个客户端互相把对方的层关掉了现象中控室的操作员关掉调试层隔了两个工位的另一个客户端上调试层也跟着消失两边互相以为对方在乱点。原因就是g_LayerMask用了全局变量服务器上一份所有客户端读同一份。这不是 bug是全局变量的语义但如果业务上本来就是「各岗位各看各的」那就是设计没对上需求。解决先确认业务模式。要统一视图就保留现状但要在界面上加个提示说明当前视图是全局同步的避免误会。要各自独立就把掩码改成客户端本地变量登录时各算各的服务器只存用户等级这类不随视图变化的数据。改的成本主要在回归测试所有跟掩码联动的按钮都要重测一遍。5.5 中文对象名和大小写在脚本里匹配不上现象脚本里写的是L03_TAG_12画面上对象名也是这个但就是不生效换成另一个对象又能用。原因通常是复制粘贴时带了尾随空格或者对象名里的下划线是全角字符肉眼几乎看不出来。另一种情况是接手的人改了对象名脚本里的字符串没跟着改这种在 VBS 里不会报错只是静默不生效。解决命名规范里明确规定只用半角字符、不加空格脚本里做前缀匹配而不是全等匹配Left(objName, 1) L比objName L03_TAG_12宽容得多交付前写一段自检脚本把所有带L前缀的对象名打印出来人工扫一遍十秒的事能省掉半天的现场排查。6. 验收与答辩用一张表证明显隐逻辑真的可靠这套东西最怕的是「演示时好好的交付后说不清」。验收不能只靠点几下看看得有能写进文档、能复现的条目。我在项目里通常用下面这张表每一项都对应一个具体操作和一个通过标准验收时逐条打勾。验收项操作方法通过标准层切换正确性依次切五种工况逐层核对每层可见性与层表default_on一致无残留切换响应时间秒表计时重复十次取平均单次切换 ≤ 300 ms路线 C 应 ≤ 100 ms权限闸门用三个不同等级账号分别登录低等级账号看不到检修层和调试层掉电恢复切到检修工况后断电重启重启后掩码与断电前一致组内对象单独切到某层检查编组设备组内对象与组外对象同步显隐点击安全关掉检修层后点原挂牌位置无弹窗、无跳转、无操作响应多客户端一致性两个客户端同时登录一个切层按业务模式验证同步或独立符合设计说明与报警联动触发一条报警切到培训工况报警层按掩码规则显示不误闪答辩时最容易被追问的是「你为什么不用图层自带的隐藏」。这个问题答不好会显得方案是绕路走的答好了反而是加分项。我的答法是先讲清楚图层是设计期属性、运行期改层号不改变可见性这个事实再把手上的四条路线摆出来对比说明为什么在对象数量、维护成本、多人协作这几个约束下选了当前这条。评委通常不介意你选了哪条介意的是你知不知道别的路存在、知不知道为什么没走。第二个高频问题是「加一个新工况要改哪里」。这个问题直指设计质量。答案是只往预设表加一行掩码值脚本和画面都不动——能当场把表翻出来指给对方看比讲十分钟架构都有说服力。演示环节有个技巧不要只演示「正常情况」。故意切到一个权限不足的账号让对方看到检修层确实出不来故意在切换后点一下原来对象的位置让对方看到点不到。把边界情况主动演出来比藏着掖着强现场提问也多半围绕这些点提前演过就不慌。从那以后我每次做这类显隐方案都会在动手画第一根管道之前先把层表和命名规范定死哪怕只有六层也要写成文档让业主签字确认。返工改名字的成本比前期多开一次评审会高太多。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?