上一课走通了“填写参数 → 生成代码行 → 选回修改 → 加载代码”。这一课留在同一个教导页面,把表格本身讲透:一段属性怎样重复生成多行,输入怎样写回参数,同一个格子怎样在输入框和下拉框之间切换,输入框里又怎样放入“抓取当前位置”按钮。
这些都是标准组态已有的设计。本篇不增加教导项目,也不要求编写 C++。在 Qt Designer 中找到相应的 QTableWidget,查看它的 accessibleDescription,就能对照本篇阅读。QTableWidget 是这些表格的控件类型,accessibleDescription 是本系统存放表格配置文字的属性。
从顶部“教导”进入,右侧选“延时指令”。入口和整页位置可对照上一课的教导总览。本篇直接使用原生局部图,浏览器版可以点击放大。
| 位置 | 在哪里配置 | 表格名称 | 主要作用 |
|---|---|---|---|
| 左侧“程序”页 | env/teach/teach.ui |
tableWidget |
每页显示十个程序位置,点击后选回记录 |
| 左侧“配方参数表”页 | 同上 | formulaParamTable |
编辑配方参数名称和数值 |
| 右侧指令参数 | env/teach/ui/teach_7.ui |
codeParamTable7 |
延时指令的参数、数据源和选取框 |
| 右侧直线运动参数 | env/teach/ui/teach_1.ui |
codeParamTable1 |
使用同类配置,另有轴位置和位置读取按钮 |
点击截图可查看原图。
图中“延时时间(秒)”为 1.250,数据源为“数值”;“程序行号”没有勾选。页面不是事先画好了三个固定输入框,而是根据参数定义和下面的表格属性创建它们。
截图采用标准界面。坐标、文件名和参数数值为讲解示例,请以实际配置为准。
下面是 teach.ui 中 tableWidget 的完整 accessibleDescription。在 Designer 属性编辑器中看到的是解码后的文字;直接查看 UI 的 XML 时,<、& 等字符可能写成转义形式。
{
-header:(#1104itemtitle14),0
-header:(#1104itemtitle15)
-attr:-D
}
{
-filter:
-rows:10
-serial:0
-interval:1
-column:
-T(%%+#1551channel*10+1)
-C0(#1551!=(%%+#1551channel*10))
-C1(#1551==(%%+#1551channel*10))
-D
-A: (clickedEvent:/env/teach/refreshnode1[0](#1551=%%+#1551channel*10))
-column:
-T1(#1551itemdesc[%%+#1551channel*10])
-C0(#1551!=(%%+#1551channel*10))
-C1(#1551==(%%+#1551channel*10))
-D
-A: (clickedEvent:/env/teach/refreshnode1[0](#1551=%%+#1551channel*10))
}
先把它分成两部分读:第一个 {…} 说明表头和整个表格的属性;第二个 {…} 说明重复多少行、每一列放什么。两条 -column: 就是两列,不需要为十行分别写二十个控件。
| 原配置 | 怎样理解 |
|---|---|
-header:(#1104itemtitle14),0 |
标题来自语言文字“序号”;宽度为 0 且未指定调整方式,按内容调整宽度 |
-header:(#1104itemtitle15) |
标题为“程序体”;未给宽度,采用伸展方式分配空间 |
-attr:-D |
整个表格参加动态更新;列中的 -D 则用于相应格子的内容更新 |
-filter: |
本表没有写行筛选条件,十个位置都创建 |
-rows:10 |
创建十个位置;并不是宣布已有十条程序 |
-serial:0 |
本段用于代入 %% 的起始序号是 0 |
-interval:1 |
另一个“基准编号”的步长为 1;本表的分页公式直接使用 %% |
-T(...) |
给格子提供内容;若绑定参数项,还可能按其类型创建输入控件 |
-T1(...) |
这里将程序文字作为文本显示,不让用户直接在格子里改代码 |
%% 很像宏程序循环中的下标:创建第一行时代入 0,第二行代入 1,依次到 9。它不是“操作者当前选中的那一行”。 当前程序位置单独保存在 #1551。
本系统内部还计算一个基准编号。对从 0 开始的第 j 次重复,序号是 serial+j,基准编号是 serial+j×interval。不要把 -interval 改成 10 来替代本例的翻页公式;这里跨页靠的是 #1551.channel*10。
程序表用三个量分工:#1551.channel 保存页号,#1551 保存当前选中的程序下标,%% 表示本页正在创建的显示位置。页号和下标都从 0 开始。
| 情况 | #1551.channel |
%% |
读取的记录下标 %%+页号×10 |
左侧显示序号再加 1 |
|---|---|---|---|---|
| 第一页第一行 | 0 | 0 | 0 | 1 |
| 第一页第三行 | 0 | 2 | 2 | 3 |
| 第二页第一行 | 1 | 0 | 10 | 11 |
| 第二页第二行 | 1 | 1 | 11 | 12 |
为了看清这一点,本次从空程序开始,用上一课的 1.25 秒连续生成十二行,再单击第 11 行:
点击截图可查看原图。
此时 #1551.channel=1,#1551=10。蓝色行对应记录下标 10;第 13 个位置为空,因为只生成了十二行。
按“上一页”后,页号变成 0,当前下标变成 0:
点击截图可查看原图。
原按钮绑定的是 MouseButtonRelease,调用 env/teach/pageup。该脚本完整内容为:
#1500.channel=0
#1551.channel=#1551.channel-1
IF#1551.channel<0THEN#1551.channel=0
#1551=MOD[#1551,10]+#1551.channel*10
widgetFunc["SysFunc","0","parser","/env/teach/parsenode"]
MOD[#1551,10] 保留“本页第几个位置”,再加上新页的起始下标;最前面一页不会继续减成负数。最后 parsenode 按新的当前位置恢复右侧参数。不要把“上一页”改写成仅改变显示序号,否则选中的记录和右侧参数可能不一致。
程序表每一列都带有同一条动作:
-A: (clickedEvent:/env/teach/refreshnode1[0](#1551=%%+#1551channel*10))
第二页第一行的 %%=0,点击时先赋值 #1551=10,再进入 refreshnode1。脚本据此处理选回记录;具体的参数恢复过程见上一课。再次点击同一记录还可能进入该脚本的菜单分支,不能把每次点击都理解为同一种操作。
高亮来自另外两条条件:
-C0(#1551!=(%%+#1551channel*10))
-C1(#1551==(%%+#1551channel*10))
没有选中时用样式 0,选中时用样式 1。当前标准样式下,后一种显示为蓝底白字。原表设置了 NoSelection,所以这个蓝色条来自组态样式判断,并不依靠 Qt 自己的选中行外观。
这里先记住一个容易看错的标点:-C0(...) 是选样式 0;后面会遇到的 -C,0(...) 带有逗号,是选择第 0 个 -T 内容方案。
切到左侧“配方参数表”。本次在第 1 行把名称改成“延时时间”,数值改成 1.25,每次输入后按回车提交:
点击截图可查看原图。
其余行名称沿用本次配置中已经保存的 Parameter2、Parameter3。它们是可编辑的配方名称,不能因为当前使用中文界面就假定这些已有名称也会自动翻译。
这张表也是十行分页,但使用的是 #1550.channel 和 #1550。三个格子的核心定义分别是:
-T(%%+#1550channel*10+1)
-T5(#1550itemtitle[%%+#1550channel*10])
-T2(#1550item[%%+#1550channel*10])
在第一页第一行,名称框写回 #1550.itemtitle[0],数值框写回 #1550.item[0]。-T5 明确使用可编辑文字,-T2 使用实数输入。名称与数值共用同一个下标,却是两个不同字段。
本系统会根据输入格的绑定把提交值写回相应参数,因此无需给每个输入格另写一份“赋值脚本”。这表示当前内存中的参数已改变,不表示已经保存配方文件;需要保留到文件时,仍使用现有“保存配方”。完整表格属性见原配方表属性。
延时和直线运动的参数表使用相同思路:先查当前教导项目,再取得本项目的参数组。阅读长表达式时,先把这一步算出来。
| 项目 | 当前项目号 #1500 |
#1500.item[#1500] |
输入值所在参数组 | 配套设置所在参数组 |
|---|---|---|---|---|
| 延时指令 | 6 | 1513 | #1513 |
#1514 |
| 直线运动指令 | 0 | 1501 | #1501 |
#1502 |
所以延时页中的 #[#1500.item[#1500]].item[1] 就是 #1513.item[1];加一后的 #[#1500.item[#1500]+1].item[1] 就是 #1514.item[1]。
| 字段 | 在本组教导配置中的用途 |
|---|---|
工作参数的 .item[...] |
当前填写的数值,或者当前选择的索引 |
工作参数的 .itemtitle[...] |
行名称,如“延时时间(秒)” |
工作参数的 .itemversion[...] |
这一行的数据源选择 |
工作参数的 .itemdesc[...] |
决定是否提供数据源选项的固定标记 |
配套参数的 .item[...] |
对应行是否选取,例如是否输出某个轴编程字 |
配套参数的 .itemmin[...] |
这行显示的分类条件,如通用行、某个轴的行 |
配套参数的 .itemversion[...] |
输入方式或位置读取所需的类别、轴序号 |
这些是教导脚本约定的字段用途。这里的 .itemmin 并不是“允许输入的最小值”;两个参数组里的 .itemversion 也不是同一含义。不能只看字段名字,就套用其他参数窗口的解释。
%% 仍然指向原来的参数选择右侧“直线运动指令”。下面是原页面的参数表:
点击截图可查看原图。
它的 -rows:#[#1500.item[#1500]].itemnum 表示检查工作参数组中的各项;-filter: 再决定哪些项真正显示。当前直线运动组有十项,本图显示九行。
| 原参数下标 | 本次配套 .itemmin |
是否显示 | 本图中的位置 |
|---|---|---|---|
| 0 | 7 | 不满足直线运动页的显示条件 | 不显示 |
| 1 | 0 | 通用行,显示 | 第一行“编程模式” |
| 2 | 1 | 检查 #912.itemmax[0]==1 |
第二行“1号轴编程位置” |
| 3~7 | 2~6 | 逐轴检查 #912.itemmax[1]~[5] |
后续轴位置行 |
| 8、9 | 0 | 通用行,显示 | “移动速度”“程序行号” |
屏幕上的第一行此时取 %%=1,第二行取 %%=2。 过滤掉原下标 0 后,其余参数没有重新编号。这正是每行的输入和按钮仍能找到正确参数的原因。
原筛选表达式还包含 #1500==14 的特定项目分支;不能把整段表达式概括为“只检查轴是否启用”。完整原文见直线运动参数表属性。本篇运行核对的是当前直线运动和延时页,其他项目分支仍需按具体项目验证。
先认识本例实际使用的几种 -T 写法:
| 写法 | 在本例中的表现 |
|---|---|
-T(表达式) |
自动匹配;绑定参数项时参照其类型,标题或普通结果则用于显示 |
-T1(...) |
文本显示,如程序体 |
-T2(...) |
实数输入,如配方数值和带位置按钮的轴坐标 |
-T3(...) |
整数输入,如数量较多时直接填写配方索引 |
-T4(目标<<选项来源) |
下拉选择,把选项索引写到目标 |
-T5(...) |
可编辑文字,如配方名称 |
-T11(目标<<名称) |
勾选框,如“选取” |
这里的 2、3、4 是本系统规定的类型号,不是小数位数。实际创建的整数和实数格子是文字输入框;不要从类型名称推断它一定带上下箭头或自动限制数值范围。
右侧“参数值”这一列写了七个候选 -T。下面是 teach_7.ui 中该列的完整属性段:
-column:
-T4(#[#1500.item[#1500]].item%%<<#1550)
-T(#[#1500.item[#1500]].item%%)
-T2(#[#1500.item[#1500]].item%%<<"icon/local.png"<<0<<"/env/teach/localpos"<<#1104itemtitle24<<#11=%%)
-T4(#[#1500.item[#1500]].item%%<<#1031)
-T4(#[#1500.item[#1500]].item%%<<#1032)
-T4(#[#1500.item[#1500]].item%%<<#1104itemtitle29<<#1104itemtitle30<<#1104itemtitle31<<#1104itemtitle32<<#1104itemtitle33<<#1104itemtitle34)
-T3(#[#1500.item[#1500]].item%%)
-C(#319.item3==1)
-R0
-C,0(((#[#1500.item[#1500]].itemversion%%==1)||(#[#1500.item[#1500]+1].itemversion%%==9))&&(#1550itemnum<=20))
-C,6((#[#1500.item[#1500]].itemversion%%==1)||(#[#1500.item[#1500]+1].itemversion%%==9))
-C,5(#[#1500.item[#1500]].itemversion%%==2||#[#1500.item[#1500]].itemversion%%==3)
-C,1(#[#1500.item[#1500]+1].itemversion%%==0)
-C,3(#[#1500.item[#1500]+1].itemversion%%==7&&#[#1500.item[#1500]].item0==0)
-C,4(#[#1500.item[#1500]+1].itemversion%%==8&&#[#1500.item[#1500]].item0==0)
-C,2(1)
-D
候选内容按出现顺序编号 0~6。后面的 -C,编号(条件) 从上往下判断,第一个成立的内容选择条件生效。最后 -C,2(1) 总成立,作为前面都不满足时的选择;不能把它挪到最前面。
| 候选号 | 提供的内容 | 哪类条件会选到它 |
|---|---|---|
| 0 | 以 #1550 的字段名称列出配方选择 |
配方索引方式,且配方项目不超过 20 个 |
| 6 | 整数输入 | 同属配方索引方式,但没有命中前面的数量条件 |
| 5 | 六个轴选项 | 工作参数的数据源值为 2 或 3 |
| 1 | 按参数定义自动匹配 | 配套参数该项的 .itemversion==0 |
| 3、4 | 以 #1031 或 #1032 提供选项 |
满足原列中相应的类别和工作项 0 条件 |
| 2 | 带位置读取按钮的实数输入 | 最后的默认选择 |
这张表只是按原条件解释各分支,不代表每种分支都适合任意教导项目。图 1 的“延时策略”通过自动匹配得到下拉框,“延时时间(秒)”通过自动匹配得到数值输入框。
这段中还有一组独立的编辑限制:-C(#319.item3==1) 定义第 0 条条件,-R0 引用它。按当前源码,当解析器启动标志为 1 时,该列的文字输入框设为只读,下拉框设为禁用。这里 -R0 的 0 是条件序号,不是候选内容号;它也没有表示输入框里的所有子按钮都被禁用。本次没有启动加工,这个状态分支按源码说明,尚未做运行切换验证。
保留配方第 1 项“延时时间”=1.25。在右侧延时页,把“延时时间(秒)”这一行的数据源改为“配方参数索引”,再在参数值下拉框中选“延时时间”:
点击截图可查看原图。
此时有三个不同的量:
| 参数 | 值 | 含义 |
|---|---|---|
#1513.itemversion[1] |
1 | 当前选择配方参数索引方式 |
#1513.item[1] |
0 | 选择配方第 1 项,其下标为 0 |
#1550.item[0] |
1.25 | 配方里真正保存的时间值 |
下拉框显示名称,工作参数保存的是下标。名称“延时时间”、索引 0、时间 1.25 不是同一个东西。 切换数据源后还要明确选择所需项目,不能假定原先的秒数会自动变成正确的配方索引。
数据源列自身绑定工作项的 .itemversion,并用 .itemdesc 中的 可配置源、可配置源1 判断提供哪组选项。这两个中文字符串是脚本判断标记,英文界面也保留它们;要翻译显示名称,应改相应语言文字,不能把判断标记一起翻译掉。
原延时生成脚本据此选择生成常量,还是保留配方引用:
IF^11.itemversion[1]==1THEN#1558.item[2]="P"+"INT[#1550.item["+INT[^11.item[1]]+"]*1000]"
IF^11.itemversion[1]==0THEN#1558.item[2]="P"+INT[^11.item[1]*1000]
按本节这些值和单行延时方式,生成的完整代码为 G04PINT[#1550.item[0]*1000]。它保留配方引用,与固定代码 G04P1250 不同。生成、回读、修改配方和NC取值的完整例子见教导配方引用。
在左侧“配方参数表”页,把参数数量输入框改成 21,按回车。原输入框绑定 #1550.version,回车运行 env/teach/refreshFormulaParamTable,由脚本把实际 .itemnum 调整为这个数量,并准备新项名称和类型。
随后把右侧该行的数据源先切回“数值”,再切成“配方参数索引”,让原刷新动作重新生成输入格:
点击截图可查看原图。
#1550.itemnum<=20 不再成立,候选 0 被跳过;下一条条件仍成立,选候选 6,于是出现 -T3 整数输入。这里填写 0 表示第 1 项,不是延时 0 秒。
这也解释了为什么原页专门配置刷新事件:
-A:(comboBoxChanged:/env/teach/ui/refresh7[0])
refresh7 的完整内容是:
widgetFunc["QTableWidget","codeParamTable7","refresh",""]
直线运动页对应 refresh1 和 codeParamTable1。此调用重新解析并建立表格内容;-D 参与日常显示更新,两者作用不同。仅修改数量的脚本直接刷新的是左侧配方表,所以本节又切换一次右侧数据源,明确触发右侧参数表重建。
普通动态刷新不能一概当成“条件变化就重建所有输入控件”。尤其是改变某行要使用的控件类型,或者改变筛选对象但显示行数没有变化时,应核对相应的显式刷新。源码中的普通表格自动刷新会比较筛选后的行数;它并不据此证明每种同数量替换都能自动重建。
本次只验证了 20 项与 21 项的这个切换。整数框没有替你证明索引有效,不能因为能够输入 21 或负数就认为能访问该项;实际索引必须落在已存在项目中。将参数数量调小还会减少配方项目数,练习前应保留原配方。
回到“直线运动指令”,保持数据源为“数值”、编程模式为“工件绝对位置”。在“1号轴编程位置”右端,有一个蓝色圆形图标。它与输入框一起由候选 2 创建:
-T2(#[#1500.item[#1500]].item%%<<"icon/local.png"<<0<<"/env/teach/localpos"<<#1104itemtitle24<<#11=%%)
按 << 分成六部分读:
| 部分 | 本例含义 |
|---|---|
#[#1500.item[#1500]].item%% |
要显示和写回的数值参数 |
"icon/local.png" |
图标;此处加载时补上 images/,实际资源为 images/icon/local.png |
0 |
这个按钮使用主 Qt 脚本执行通道 |
"/env/teach/localpos" |
点击后运行的脚本 |
#1104itemtitle24 |
悬停提示使用的语言文字 |
#11=%% |
把本格对应的原参数下标交给脚本 |
在本图的第二个可见行中,%%=2,因此按钮写入 #11=2,而不是 1。localpos 随后读取 #1502.itemversion[2]=1,知道要读第一轴。
env/teach/localpos 完整内容如下:
N1
IF[#1500==0]*[#[#1500.item[#1500]].item[1]==2]GOTO3
IF#1500==14GOTO4
#[#1500.item[#1500]].item[#11]=#300.item[35+#[#1500.item[#1500]+1].itemversion[#11]]
GOTO2
N3
#[#1500.item[#1500]].item[#11]=#300.item[#[#1500.item[#1500]+1].itemversion[#11]-1]
GOTO2
N4
#[#1500.item[#1500]].item[#11]=#300.item[35+#[#1500.item[#1500]+1].itemversion[#11]]
N2
对当前“工件绝对位置”情况,主赋值语句代入后相当于:
#1501.item[2]=#300.item[36]
这里 36=35+1。在直线运动的“机床绝对位置”模式下,脚本会进入 N3,同一轴改读 #300.item[0]。不能把这个按钮解释成无论什么模式都读同一组坐标;其他模式的具体使用还应沿该脚本判断。
本次在离线核对环境中,把读取时的第一轴工件显示位置设为 12.345,点击原按钮后,输入框得到这个值:
点击截图可查看原图。
localpos 做的是读坐标、写工作参数,未调用 NC 运动。它也没有把这行变成已记录的程序;要生成记录,仍是上一课的“生成代码行”。图 8 不表示机床实际移动到了 12.345。
第一列的原定义中,勾选框绑定的是配套参数:
-T11(#[#1500.item[#1500]+1].item%%<<#1104itemtitle23)
同样取 %%=2,这里写的是 #1502.item[2];坐标输入框写的是 #1501.item[2]。取消勾选以后,坐标 12.345 仍保留:
点击截图可查看原图。
直线代码生成脚本 env/teach/line 中,生成 X 字的部分为:
IF^12.item[2]==0GOTO4
#1552.item[3]="X"
IF^11.itemversion[2]==0GOTO3
#1552.item[3]=#1552.item[3]+"#1550.item["+INT[^11.item[2]]+"]"
GOTO4
N3
#1552.item[3]=#1552.item[3]+^11.item[2]
N4
addcode 进入此脚本前设置 #11=1501、#12=1502;这里的 ^11、^12 是对应的间接参数引用。第一句不满足选取就跳过 X 字,所以取消选取既不需要清零,也不需要删除输入值。
先找 UI 中的表格名称和 accessibleDescription,再算当前项目对应的参数组;接着看 -rows 和 -filter,确认实际下标;最后才读每列的 -T、内容选择条件和动作。长表达式可以像上面一样先代入具体数字,逐列检查。
| 遇到的问题 | 优先检查 |
|---|---|
| 十条配置以外的记录找不到 | 页号、%%+页号×10、显示序号中的 +1 是否混在一起 |
| 点击后高亮或右侧参数不对 | 先赋的 #1551、refreshnode1、两列中的高亮条件是否一致 |
| 名称改不动 | 需要编辑的名称是否用了 -T5,而不是文本显示 -T1 |
| 配方选了名称,却以为保存了秒数 | 查工作参数中的索引,再查 #1550.item[索引] |
| 下拉框一直选错内容方案 | -C,编号 的编号从 0 开始;多个条件按先后顺序取第一个成立项 |
| 改了数据源,控件种类没变 | 原 comboBoxChanged 是否调用了与表格名称匹配的刷新脚本 |
| 隐藏一个轴以后,按钮写错位置 | 传入的是原参数序号 %%,还是误用屏幕上的可见行号 |
| 英文化后数据源列消失 | 可配置源 等判断标记是否被误译 |
| 填了坐标却没有对应编程字 | 配套参数中的“选取”是否勾选;仅填写值不会代替勾选 |
| 手动输入了一个索引就报错 | 检查项目数量和下标范围,别把输入类型当成完整有效性校验 |
| 输入已提交,重开配方后丢失 | 是否实际使用“保存配方”保存了文件 |
现在可以把这三张表连起来理解:程序表负责选中哪条记录,配方表提供可重用的名称和值,右侧参数表决定这一行怎样填写。它们使用同一套表格配置方法,再由教导脚本完成记录、恢复和生成代码。