跳转至

plcui 工控 UI 核心库

作者:江访 | 最后修改:2026-11-11

一、概述

plcui 是 LuatOS 的工控界面核心库,用「切图 + 布局文件 + Lua 脚本」三样东西就能做出工控人机界面:切图决定观感,XML 写布局,Lua 更新数据和响应操作。库内置 image、label、textarea、keyboard、container、table、chart 七种组件,覆盖工控现场最常见的按钮、状态指示、文字显示、参数录入、分组面板、数据表格和趋势曲线。

核心优势:

  • 布局与逻辑分离:界面写在 XML 里,业务写在 Lua 里,改版界面不动业务代码,改数据不动布局
  • 所见即所得:按钮的常态、按下、选中、禁用四种状态各用一张切图,界面观感由美工决定,脚本里不用描样式
  • 输入开箱即用:输入框配上键盘组件即可完成数值和文本录入,自带确认、取消、字符过滤和长度限制
  • 多页界面简单:一份布局文件写多页界面,一行代码切换页面
  • 数据更新轻量:刷新界面数据只需调用组件的 set 接口,不需要重新加载界面

开发流程:

需求描述
  │
  ① HTML 交互原型(唯一的视觉和交互迭代环节)
  │    · 设计规范先行:色板、字号阶梯、间距网格、组件风格
  │    · 布局和交互逻辑都在 HTML 里做完整:页面切换、按钮四态、输入框确认与取消、数据刷新,能点的都能点
  │    · 只用 PLCUI 的七种组件来搭,别用渐变、hover、动画这类网页才有的效果,否则搬不过去
  │    · 看截图自查 2~3 轮,重叠、对齐、层次、交互流程问题在这里全部消化
  │    · 素材图直接嵌进原型预览,所见即所得
  │
  ② 素材流水线(脚本处理,结果确定)
  │    · 设备图元:矢量源图栅格化,按状态派生按下、选中、禁用图
  │    · 控件小图:随界面配色生成
  │    · 背景 = 隐藏动态层后整页烘成一张图,动态层只留状态图、文字、控件
  │
  ③ 原型导出 XML(脚本处理)
  │    · 提取组件树、坐标、尺寸、事件 → main.xml + 资源表
  │
  ④ 交互逻辑搬成 Lua(不需要视觉反馈,可以交给 AI)
  │    · 逻辑结构照搬原型:页面跳转 → xml:show,改文字改状态 → set 接口,事件 → 回调函数
  │    · 坐标、颜色、交互行为都对着原型取,不要凭印象重新设计
  │    · 架构固定:窗口模块 + 数据层 + 总装层
  │    · 具名函数、功能注释、接口约定,照规范执行
  │
  ⑤ 验证闭环:PC 模拟器用 plcui.snapshot 截图对照原型 → 下载到真机逐屏对照原型截图

界面好不好看、好不好用,问题几乎都出在原型环节而不是代码环节:控件重叠、对齐不齐、层次混乱、交互流程走不通,等写完 XML 和 Lua 再回头调,改一处动一片。所以 布局和交互逻辑都在 HTML 原型里定稿,后面四环只是搬运和验证:XML 是原型布局的导出结果,Lua 是原型交互换一套调用写法,两者都不需要重新设计。原型是唯一事实来源:XML 的坐标、尺寸、颜色,Lua 的交互行为,都对着 design/index.html 取值,不要凭印象重新设计;原型写法与 PLCUI 写法的对照见 7.4 交互逻辑搬成 Lua。第一环和第四环可以交给 AI 完成,第二、三、五环交给脚本完成,人只负责定规范和验收。每个环节该守的规则见 七、开发规范说明。

注意事项

  1. plcui、sys 都是 LuatOS 内建核心库,脚本中不需要 require,可直接调用;
  2. 属性名中只有 font_size 使用下划线,其余属性一律使用连字符,例如 max-length、on-click、point-count,写成 font-size 会报 Unknown attribute;
  3. 布局文件中声明的每个事件,都必须在 plcui.load 的 handlers 参数中给出同名处理函数,缺少任何一个都会加载失败;
  4. 库版本和布局文件版本不是同一个值:库版本是 1.0.0(plcui.version()),布局文件版本是 0.1(<ui version="0.1">),后者必须精确等于 0.1;
  5. 图片请使用 Baseline JPG 或 PNG,暂不支持渐进式 JPG;
  6. 真机上必须把触摸设备绑定给界面引擎,按钮点击才会响应:tp.init 拿到触摸句柄后,在 plcui.init 之后调用 plcui.device_bind_touch,完整写法见 2.4 真机部署与触摸初始化;PC 模拟器用鼠标模拟触摸,可以不绑定;
  7. 资源文件打包下载时目录会摊平:布局文件、切图、Lua 脚本全部平铺进 /luadb,文件名少于 23 字节且不重名,布局文件里 src 只写切图文件名;
  8. 需要 Air1603 的 104 号固件。

二、核心示例

1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;

2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;

3、更加完整和详细的 demo,请参考 LuatOS 仓库 中的 157.plcui 示例工程

本示例实现一个带参数录入的泵站首页:显示运行状态,点击按钮启停设备,在输入框里修改设定值。

2.1 核心代码

PROJECT = "plcui_quickstart"
VERSION = "1.0.0"

xml = nil

-- 更新状态栏文字
function update_status(text)
    xml:find("main", "status"):set_text(text)
end

-- 启动按钮点击事件
function on_start_click(event)
    update_status("已启动")
end

-- 设定值提交事件
function on_setpoint_submit(event)
    update_status("设定值 " .. event.text)
end

-- 设定值取消事件,plcui 已自动恢复编辑前的值
function on_setpoint_cancel(event)
    update_status("已取消")
end

-- 界面初始化
function ui_main()
    plcui.init({width = 480, height = 320})
    xml = plcui.load("/luadb/main.xml", {handlers = {
        start_click = on_start_click,
        setpoint_submit = on_setpoint_submit,
        setpoint_cancel = on_setpoint_cancel
    }})
    update_status("就绪")
end

sys.taskInit(ui_main)
sys.run()

handlers 的键名就是布局文件里 on-click、on-submit 这些属性的值,两处必须一一对应。

2.2 布局文件

界面尺寸必须与 plcui.init 的画布尺寸完全一致;切图文件放在布局文件同目录下。

<?xml version="1.0" encoding="UTF-8"?>
<ui version="0.1" width="480" height="320" entry="main">
  <resources>
    <image id="btn_start" src="btn_start.jpg"/>
    <image id="btn_start_pressed" src="btn_start_pressed.jpg"/>
  </resources>
  <pages>
    <page id="main" background="#101820">
      <label id="status" x="20" y="20" w="200" h="26" text="就绪"
             font="hzfont" font_size="20" color="#e1fffa" align="left"/>
      <image id="start" x="20" y="60" w="140" h="34"
             src="btn_start" src-pressed="btn_start_pressed"
             clickable="true" on-click="start_click"/>
      <textarea id="setpoint" x="20" y="110" w="120" h="26" text="75.0"
                font="hzfont" font_size="20" color="#edf7fb"
                one-line="true" max-length="5" accepted-chars="0123456789."
                keyboard="num_kb" on-submit="setpoint_submit" on-cancel="setpoint_cancel"/>
      <keyboard id="num_kb" x="20" y="150" w="300" h="150" layout="number"
                font="hzfont" font_size="20"/>
    </page>
  </pages>
</ui>

2.3 使用说明

1、把上面的 main.xml 和两张切图放在同一个目录,下载到 Air1603(104 号固件)运行本工程即可看到界面,按钮要点得动还需要绑定触摸,见下一节;

2、plcui.load 成功后界面立即显示,xml:find(页面ID, 组件ID) 拿到组件句柄,再调用组件的 set 接口更新界面;

3、点击按钮触发 start_click,输入框里点确认触发 setpoint_submit,event.text 就是刚提交的文本,点取消触发 setpoint_cancel,输入框内容自动还原;

4、切换页面用 xml:show(页面ID),切页后原来的组件句柄都失效,必须重新 xml:find,如果是在事件里切页,要延时 30 毫秒再去访问新页面:

-- 切换到详情页
function on_show_details(event)
    xml:show("details")
    sys.timerStart(update_details, 30)
end

2.4 真机部署与触摸初始化

打包下载

1、布局文件、切图、Lua 脚本一起打包成 luadb 下载到设备,包内所有文件平铺进 /luadb,源码里的目录层级不保留;

2、文件名少于 23 字节,不用中文和空格,整个工程不出现重名文件(不同目录下的同名文件也会冲突);

3、main.lua 里用 /luadb/main.xml 这样的绝对路径加载布局文件,布局文件里的 src 只写切图文件名,luadb 里文件都在同一目录,天然对得上。

触摸初始化

真机上要先初始化触摸芯片,再把触摸句柄绑定给 plcui,按钮的 on-click 才会被触发。固定顺序是:i2c.setup → tp.init → plcui.init → plcui.device_bind_touch,其中 plcui.device_bind_touch 必须在 plcui.init 之后调用:

-- 第一步:初始化触摸芯片(在 plcui.init 之前完成)
i2c.setup(1, i2c.FAST)
local tp_device = tp.init("gt911", {
    port    = 1,       -- 触摸挂载的 I2C 端口号
    pin_rst = 14,      -- 复位引脚,按板子原理图填
    pin_int = 51,      -- 中断引脚,按板子原理图填
    w       = 1024,    -- 触摸分辨率,与屏幕一致
    h       = 600,
})

-- 第二步:初始化界面引擎,再绑定触摸
function ui_main()
    plcui.init({width = 1024, height = 600})
    local ok, err = plcui.device_bind_touch(tp_device)
    if not ok then
        log.error("ui", "触摸绑定失败", err.code, err.message)
    end
    xml = plcui.load("/luadb/main.xml", {handlers = handlers})
end

补充说明:

1、tp.init 一次上电只能调用一次,重复调用返回 nil 并打印 tp has been initialized,业务重连触摸时只需重新配置 I2C 总线(i2c.setup),不要重调 tp.init;

2、plcui.device_bind_touch(nil) 可解除绑定;PC 模拟器调用同样兼容(输入由 SDL 鼠标提供),所以同一套代码在模拟器和真机上都能跑;

3、完整可运行的触摸初始化模块(含失败处理、I2C 总线恢复),参考 LuatOS 仓库 中 Air8607 的 plcui 演示工程 tp_app.lua。

三、工程组织与多文件

3.1 Lua 脚本支持多个文件吗

支持。一个工程可以放任意多个 .lua 文件,但入口只有一个 main.lua,其余脚本都要用 require 引进来才能执行。

1、工程目录里必须有 main.lua,它是唯一的入口,程序从它开始跑;其他 .lua 文件只有被 require 到才会执行; 2、require 只写文件名、不写路径、不带 .lua 后缀,例如 require("alarm_page") 引入同目录下的 alarm_page.lua; 3、目录层级只影响源码组织,打包后所有脚本都摊平成文件名,所以不同目录下也不能出现重名文件; 4、只在 main.lua 的末尾调用一次 sys.run(),其它脚本里不要再写; 5、require 里写的是常量字符串才认得出来,用变量拼出来的文件名识别不了,会被当成没用到的文件剔除。

3.2 拆成多个 Lua 文件的优势

  • 各管一摊:界面逻辑、数据采集、报警处理、通信各自一个文件,改动只影响一处,排查问题也更快
  • 包体更小:打包时只把从 main.lua 一路 require 到的脚本打进去,没被引用的文件自动不进包,拆得越细越省
  • 好复用:单位换算、报警表、协议解析这类公共代码写成一个文件,多个界面共用,不用复制粘贴

3.3 怎么拆

以泵站界面为例,常见的拆法是「一个入口 + 每个页面一个文件 + 公共代码一个文件」:

pump_station/
├── main.lua        -- 入口:初始化、页面路由
├── main.xml        -- 布局文件
├── page_main.lua   -- 首页逻辑
├── page_detail.lua -- 详情页逻辑
├── alarm.lua       -- 报警处理
└── assets/         -- 切图
-- main.lua:入口只做初始化和页面路由
PROJECT = "pump_station"
VERSION = "1.0.0"

xml = nil

-- 界面初始化
function ui_main()
    require("alarm")
    require("page_main")
    require("page_detail")
    plcui.init({width = 800, height = 480})
    xml = plcui.load("/luadb/main.xml", {handlers = handlers})
    ui_page_main()
end

sys.taskInit(ui_main)
sys.run()
-- alarm.lua:公共逻辑,被 main.lua 引入
alarm_list = {}

-- 记录一条报警
function alarm_add(code, text)
    alarm_list[#alarm_list + 1] = {code = code, text = text}
end

跨文件共用的变量和函数一律不写 local,否则别的文件访问不到。

3.4 XML 布局文件支持多个吗

支持放多个,但同一时刻只显示一份界面。plcui.load 可以反复调用,每次加载新文件成功后,旧界面会自动关闭,旧的组件句柄随之全部失效,必须重新 xml:find。

1、布局文件里没有 include、import 之类的拼装机制,不能把几个文件合成一个界面; 2、一份布局文件写多个页面,靠 <page> 分页,用 xml:show 切换,这是多页界面的推荐写法; 3、资源表按文件各自独立,256 个资源、512 个组件、16 页、256KB 都是一份布局文件的上限,不是整个工程的总量,换成另一份文件重新计数; 4、同一张切图在不同布局文件里可以重复声明,各自占一个资源名额; 5、换界面时新文件加载失败的话,原有界面不受影响,可以继续用。

3.5 一个工程怎么组织界面文件

推荐一个工程一份 main.xml,页面多了就在里面多写几个 <page>,Lua 侧按页面拆文件。只有在「整套界面要换掉」的场合才用多个 XML 文件,例如:

  • 换机型:同一套业务代码配 800x480.xml 和 1024x600.xml,按屏幕尺寸加载对应文件
  • 换主题:亮色 light.xml、暗色 dark.xml,运行时切换
-- 按屏幕尺寸加载对应的布局文件
function ui_main()
    plcui.init({width = 1024, height = 600})
    xml = plcui.load("/luadb/1024x600.xml", {handlers = handlers})
end

四、XML 布局详解

4.1 文件骨架

<?xml version="1.0" encoding="UTF-8"?>
<ui version="0.1" width="800" height="480" entry="main">
  <resources>
    <image id="bg" src="bg.jpg"/>
    <font id="cn" builtin="hzfont"/>
  </resources>
  <pages>
    <page id="main" background="#101820">
      <!-- 这里放组件 -->
    </page>
  </pages>
</ui>

元素的层级关系如下,只能按这个层级嵌套:

ui                                      根元素,全文件唯一
├── resources                           资源区,必须出现且只出现一次,允许为空
│   ├── image                           切图资源声明
│   └── font                            字体资源声明
└── pages                               页面区,必须出现且只出现一次
    └── page                            页面,最多 16 页
        ├── container                   面板,是唯一能放子组件的组件
        │   └── (任意组件)
        ├── image                       图片
        │   └── 无子节点
        ├── label                       标签
        │   └── 无子节点
        ├── textarea                    输入框
        │   └── 无子节点
        ├── keyboard                    键盘
        │   └── 无子节点
        ├── table                       表格
        │   └── cell                    单元格,行列都从 1 开始
        └── chart                       图表
            └── series                  曲线,每张图 1~8 个

4.1.1 ui 属性

参数

ui

参数含义:根元素 ui 的属性
数据类型:XML 属性
是否必选:是
取值范围:包含以下属性:
{
    参数含义:布局文件版本
    数据类型:string
    取值范围:固定 "0.1"
    是否必选:必选
    注意事项:必须精确等于 0.1,与核心库版本 1.0.0 不是同一个值
    参数示例:version="0.1"
    version = ,

    参数含义:界面宽度
    数据类型:number
    取值范围:1到2048,单位像素
    是否必选:必选
    注意事项:必须与 plcui.init 的 width 完全一致
    参数示例:width="800"
    width = ,

    参数含义:界面高度
    数据类型:number
    取值范围:1到2048,单位像素
    是否必选:必选
    注意事项:必须与 plcui.init 的 height 完全一致
    参数示例:height="480"
    height = ,

    参数含义:开机首屏显示的页面
    数据类型:string
    取值范围:本文件中某个 page 的 id
    是否必选:必选
    注意事项:写错会报 E_NOT_FOUND
    参数示例:entry="main"
    entry = ,
}

4.1.2 page 属性

参数

page

参数含义:页面元素 page 的属性
数据类型:XML 属性
是否必选:是
取值范围:包含以下属性:
{
    参数含义:页面 ID
    数据类型:string
    取值范围:63 字节以内,整个文件里不重名
    是否必选:必选
    注意事项:xml:show 和 xml:find 都要用它
    参数示例:id="main"
    id = ,

    参数含义:页面背景色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:不填则为透明,页面最多 16 个;纯色背景直接用本属性,不要再铺背景切图,渲染更快
    参数示例:background="#101820"
    background = ,
}

4.1.3 书写约定

1、<image> 是一个双用途标签:写在 <resources> 下是切图资源声明,写在 <page> 或 container 下是显示组件,靠父标签区分; 2、所有文字都写在属性里,标签之间不允许出现文字内容; 3、元素名和属性名大小写敏感,写错或多余都会报 Unknown attribute。

4.2 通用属性

所有组件都支持下面这组属性,各组件章节的「创建」一节会完整列出一次,这里先集中说明。

参数

通用属性

参数含义:所有组件都支持的通用属性
数据类型:XML 属性
是否必选:是
取值范围:包含以下属性:
{
    参数含义:组件 ID
    数据类型:string
    取值范围:63 字节以内,同一页里不重名
    是否必选:必选
    注意事项:运行时用 xml:find(页面ID, 组件ID) 查找组件,重名报 E_DUPLICATE_ID
    参数示例:id="status"
    id = ,

    参数含义:组件左上角 X 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角;放在 container 里时会被 padding 和 border-width 平移
    参数示例:x="20"
    x = ,

    参数含义:组件左上角 Y 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角;放在 container 里时会被 padding 和 border-width 平移
    参数示例:y="20"
    y = ,

    参数含义:组件宽度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:图片会被拉伸填满这个宽度
    参数示例:w="200"
    w = ,

    参数含义:组件高度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:表格的行数乘以最小行高不能超过这个高度
    参数示例:h="26"
    h = ,

    参数含义:初始是否隐藏
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false,运行时可用 obj:set_hidden 改
    参数示例:hidden="true"
    hidden = ,

    参数含义:初始是否禁用
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false,置灰并禁用,连同子组件一起;运行时可用 obj:set_disabled 改
    参数示例:disabled="true"
    disabled = ,
}

label、textarea、keyboard、table 四种文字类组件额外支持下面两个属性,image、container、chart 不支持:

参数

文字类属性

参数含义:文字类组件额外支持的属性
数据类型:XML 属性
是否必选:是
取值范围:包含以下属性:
{
    参数含义:字体
    数据类型:string
    取值范围:字体资源 ID,或直接写 "hzfont"
    是否必选:可选
    注意事项:默认值随组件不同,label 和 textarea 用系统默认字体、keyboard 用 montserrat_20、table 用 hzfont
    参数示例:font="hzfont"
    font = ,

    参数含义:字号
    数据类型:number
    取值范围:1到255
    是否必选:可选
    注意事项:默认 16;只对 hzfont 有效,给其他字体会报错
    参数示例:font_size="20"
    font_size = ,
}

颜色一律写 #RRGGBB;align 只接受 left、center、right 三个值,指的是文字对齐方式,不是组件位置。

4.3 资源声明

切图和字体都要先在 <resources> 里声明,组件里填的是资源 ID,不是文件名。纯色背景不需要切图:直接用 page 的 background 属性,或铺一个纯色 container 当背景,少一张大图就省下它的闪存占用、解码耗时和每帧合成开销,启动和刷新都更快。

参数

image 资源

参数含义:切图资源声明,写在 <resources> 下
数据类型:XML 元素
是否必选:可选
取值范围:包含以下属性:
{
    参数含义:资源 ID
    数据类型:string
    取值范围:63 字节以内,整个文件里不重名
    是否必选:必选
    注意事项:不能叫 hzfont,那是保留名
    参数示例:id="btn_start"
    id = ,

    参数含义:切图文件名
    数据类型:string
    取值范围:相对布局文件所在目录的文件名,不支持 ..,不支持绝对路径,不支持子目录
    是否必选:必选
    注意事项:推荐 Baseline JPG 或 PNG,渐进式 JPG 不支持;单个文件不超过 16MB,解码后 宽×高×4 不超过 8MB;
              打包下载后所有文件平铺进 /luadb 与布局文件同目录,src 写文件名即可,文件名少于 23 字节
    参数示例:src="btn_start.jpg"
    src = ,
}

参数

font 资源

参数含义:字体资源声明,写在 <resources> 下
数据类型:XML 元素
是否必选:可选
取值范围:包含以下属性:
{
    参数含义:资源 ID
    数据类型:string
    取值范围:63 字节以内,整个文件里不重名
    是否必选:必选
    注意事项:组件里 font 属性填的就是这个 ID
    参数示例:id="cn"
    id = ,

    参数含义:内置字体名
    数据类型:string
    取值范围:"hzfont"、"montserrat_14"、"montserrat_16"、"montserrat_20"、"montserrat_24"、"montserrat_32"
    是否必选:必选
    注意事项:只支持内置字体,外部字体文件暂不支持
    参数示例:builtin="hzfont"
    builtin = ,
}

使用说明

1、加载界面时会把所有切图都检查一遍,任何一张不合格都加载失败; 2、一张切图可以被多个组件引用,但资源 ID 只能声明一次; 3、不同布局文件之间资源各自独立,同一张图可以在多个文件里重复声明,各占一个资源名额。

4.4 事件

参数

事件属性

参数含义:组件的事件绑定属性,取值是 handlers 里的函数名
数据类型:XML 属性
是否必选:可选
取值范围:包含以下属性:
{
    参数含义:点击事件
    数据类型:string
    取值范围:handlers 里的函数名,只能写在 image 上
    是否必选:可选
    注意事项:必须同时写 clickable="true",事件对象里没有 text 字段
    参数示例:on-click="start_click"
    on-click = ,

    参数含义:文本变化事件
    数据类型:string
    取值范围:handlers 里的函数名,只能写在 textarea 上
    是否必选:可选
    注意事项:程序里调用 textarea:set_text() 不触发;连续输入会合并,只通知最后一次
    参数示例:on-change="setpoint_change"
    on-change = ,

    参数含义:确认提交事件
    数据类型:string
    取值范围:handlers 里的函数名,只能写在 textarea 上
    是否必选:可选
    注意事项:由键盘的确认键触发,事件对象的 text 是刚提交的文本
    参数示例:on-submit="setpoint_submit"
    on-submit = ,

    参数含义:取消事件
    数据类型:string
    取值范围:handlers 里的函数名,只能写在 textarea 上
    是否必选:可选
    注意事项:键盘取消键或点击输入框和键盘以外的区域触发,文本自动还原,事件对象的 text 是还原后的原文本
    参数示例:on-cancel="setpoint_cancel"
    on-cancel = ,
}

其余组件没有事件属性。事件处理函数的写法见 6.1.12 事件回调。

4.5 书写规则

1、必填属性:<ui> 的 version、width、height、entry,<image> 资源的 id、src,<font> 资源的 id、builtin,<page> 的 id,组件的 id、w、h,image 组件的 src,table 的 rows、columns,<cell> 的 row、column,<series> 的 id; 2、on-click 必须配合 clickable="true"; 3、font_size 只能配 hzfont,取值 1~255; 4、y-min 必须小于 y-max,<series values> 的个数必须等于 point-count; 5、col-widths、col-aligns 的个数必须等于 columns; 6、textarea 的 keyboard 必须指向同一页的 <keyboard>,可以写在 textarea 的后面,但不能跨页; 7、只有 container 可以放子组件,<cell> 只能放在 table 下,<series> 只能放在 chart 下; 8、hit-x/y/w/h 触摸区必须完整落在组件范围内; 9、ID 不重名:资源 ID 和页面 ID 在整个文件里不重名,组件 ID 在同一页里不重名; 10、属性名必须写对,写错或多余都会报 Unknown attribute。

五、常量详解

核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;plcui 的界面取值都写在布局文件属性里,脚本中会用到的固定取值如下;

5.1 事件类型 event.type

参数含义:事件回调参数 event 中 type 字段的取值
数据类型:string
取值范围:"click"  — 点击事件
          "change" — 输入框文本变化
          "submit" — 输入框确认提交
          "cancel" — 输入框取消编辑
是否必选:—
注意事项:取值不带 on- 前缀,与布局文件里的 on-click、on-change 等属性名不同
参数示例:if event.type == "submit" then update(event.text) end

5.2 文字对齐 align

参数含义:布局文件 align 属性的取值
数据类型:string
取值范围:"left"   — 左对齐
          "center" — 居中
          "right"  — 右对齐
是否必选:可选,默认 left
注意事项:只作用于文字,组件本身的位置由 x、y 决定
参数示例:<label id="t" x="10" y="10" w="120" h="26" align="center"/>

文字上下居中

align 只管水平方向,文字在控件里默认贴顶,没有垂直对齐属性。要上下居中,用行高公式摆坐标:hzfont 一行文字的高度固定是 font_size + 3,把控件的 h 设成这个值,再把 y 摆到目标区域的垂直中心即可。

按钮上的文字(label 叠在按钮图上):

<!-- 按钮图 x="416" y="518" w="120" h="34",文字 font_size 15,行高 = 15 + 3 = 18 -->
<label id="btn_start_text" x="416" y="526" w="120" h="18"
       font="hzfont" font_size="15" align="center" text="启动"/>
<!-- y = 518 + (34 - 18) / 2 = 526 -->

输入框里的文字(textarea 透明叠在输入框背景上):

<!-- 输入框背景 x="100" y="100" w="200" h="40",文字 font_size 16,行高 = 16 + 3 = 19 -->
<textarea id="qty" x="100" y="110" w="200" h="19"
          font="hzfont" font_size="16" one-line="true" keyboard="num_kb"/>
<!-- y = 100 + (40 - 19) / 2 = 110,除不尽时取整即可 -->

总结:水平方向 x、w 与目标区域对齐(配 align="center"),垂直方向 h = font_size + 3、y = 目标 y + (目标 h - 行高) / 2;多行输入框 h = 行数 × (font_size + 3)。

5.3 键盘布局 layout

参数含义:布局文件 keyboard 的 layout 属性取值
数据类型:string
取值范围:"number" — 数字键盘
          "text"   — 英文键盘
是否必选:可选,默认 number
参数示例:<keyboard id="num_kb" x="20" y="150" w="300" h="150" layout="number"/>

5.4 图表类型 type

参数含义:布局文件 chart 的 type 属性取值
数据类型:string
取值范围:"line" — 曲线图
          "bar"  — 柱状图
是否必选:可选,默认 line
参数示例:<chart id="trend" x="10" y="10" w="300" h="160" type="line"/>

5.5 错误码 err.code

参数含义:接口失败时返回的错误表中 code 字段的取值,按字符串比较
数据类型:string
取值范围:"E_SCHEMA"          — 参数或布局文件不符合规范
          "E_VERSION"         — 布局文件版本不是 0.1
          "E_XML_SYNTAX"      — 布局文件语法错误
          "E_DUPLICATE_ID"    — ID 重复
          "E_RESOURCE"        — 切图、字体等资源缺失或不合规
          "E_HANDLER"         — 事件处理函数缺失
          "E_MEMORY"          — 内存不足
          "E_NOT_INITIALIZED" — 没有先调用 plcui.init
          "E_BUSY"            — 正在事件回调或界面刷新中,不能加载新界面
          "E_UNSUPPORTED"     — 当前环境不支持这个操作,例如真机上调用 plcui.snapshot
          "E_DISPLAY_BUSY"    — 显示已被占用,见 6.1.1 plcui.init
          "E_PLATFORM"        — 平台初始化失败
          "E_NOT_FOUND"       — 页面或组件 ID 不存在
          "E_PAGE_INACTIVE"   — 目标页面不是当前页
          "E_OBJECT_CLOSED"   — 句柄已失效
          "E_TYPE"            — 接口不支持该组件类型
          "E_INPUT_SESSION"   — 输入状态冲突
          "E_INTERNAL"        — 内部错误
是否必选:—
注意事项:失败统一返回 nil, err,err 里还有 message、id、attribute、line、column 等定位信息
参数示例:local result, err = xml:find("main", "status")
         if not result then log.warn("plcui", err.code, err.message) end

5.6 数量上限

参数含义:一份布局文件能写多大界面的上限
数据类型:—
取值范围:页面 16 页,组件 512 个,资源 256 个,嵌套 16 层,文件 256KB;
          ID 和事件名 63 字节,文本 511 字节,事件队列 64 条;
          表格 128 行 × 16 列且不超过 1024 格,图表 2~1024 点 × 1~8 条曲线
是否必选:—
注意事项:这些上限按一份布局文件算,换成另一份文件重新计数;
          组件数是整个文件的总数,不是单页的上限
参数示例:—

六、函数详解

6.1 核心接口

6.1.1 plcui.init(options)

功能

初始化 plcui 界面引擎,设置画布尺寸,之后界面刷新自动运行。真机上还要在 init 之后调用 plcui.device_bind_touch 绑定触摸设备,触摸操作才会生效。界面刷新由 plcui 自己按 60Hz 调度,业务上不要再写一个刷新定时器去调 plcui.refresh。

参数

options

参数含义:初始化参数表
数据类型:table
是否必选:必选
取值范围:包含以下子参数:
{
    参数含义:画布宽度
    数据类型:number
    取值范围:1到2048,单位像素
    是否必选:必选
    注意事项:必须与布局文件 <ui width="..."> 一致;真机上与 height 的组合不超过 1024 × 600
    参数示例:width = 800
    width = ,

    参数含义:画布高度
    数据类型:number
    取值范围:1到2048,单位像素
    是否必选:必选
    注意事项:必须与布局文件 <ui height="..."> 一致;真机上与 width 的组合不超过 1024 × 600,且显示方向固定不能旋转,横屏竖屏由画布尺寸决定
    参数示例:height = 480
    height = ,
}

返回值

local result = plcui.init(options)

含义说明:初始化是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = plcui.init({width = 800, height = 480})
注意事项:整个工程只初始化一次。重复初始化、AirUI 已经在用、或者别的显示界面还开着,
          都会失败并返回 E_DISPLAY_BUSY;AirUI 和 plcui 不能同时用,换用 plcui 前先结束 AirUI,
          需要重来先调 plcui.deinit

示例

-- 初始化界面引擎
plcui.init({width = 800, height = 480})

6.1.2 plcui.load(path, options)

功能

加载布局文件并显示界面,同时把事件和处理函数绑定起来。

参数

path

参数含义:布局文件路径
数据类型:string
取值范围:绝对路径,例如 "/luadb/main.xml",不能包含 \ 和 :
是否必选:必选
注意事项:布局文件里引用的切图,相对布局文件所在目录查找
参数示例:"/luadb/main.xml"

options

参数含义:加载参数表
数据类型:table
是否必选:可选
取值范围:包含以下子参数:
{
    参数含义:事件处理函数表
    数据类型:table
    取值范围:键为字符串,值为函数,键名对应布局文件里 on-click、on-change、on-submit、on-cancel 的取值
    是否必选:可选,但布局文件里写了事件就必须提供
    注意事项:布局文件里声明的每个事件都要给出同名函数,缺少任何一个都加载失败
    参数示例:handlers = {start_click = on_start_click, setpoint_submit = on_setpoint_submit}
    handlers = ,
}

返回值

local xml = plcui.load(path, options)

含义说明:界面对象,用它查找组件、切换页面
数据类型:userdata 或 nil, table
取值范围:成功返回界面对象,失败返回 nil 和错误表
返回示例:xml = plcui.load("/luadb/main.xml", {handlers = handlers})
注意事项:加载失败时原有界面不受影响;重复加载新的布局文件时,新界面显示后旧界面自动关闭;
          不要在事件回调里加载新界面,会返回 E_BUSY,要换界面用 xml:show 切页,或者延后到回调之外再 load;
          切图在页面第一次显示时才校验,SD 卡上的切图换过之后要重新 load 才会生效

示例

-- 加载布局文件
xml = plcui.load("/luadb/main.xml", {handlers = handlers})

6.1.3 plcui.refresh()

功能

立即刷新一次界面。界面刷新本来是自动的,只有需要马上看到更新效果时才调用。

返回值

local result = plcui.refresh()

含义说明:调用是否成功
数据类型:boolean
取值范围:恒为 true
返回示例:plcui.refresh()

示例

-- 立即刷新界面
plcui.refresh()

6.1.4 plcui.deinit()

功能

关闭界面引擎,停止界面刷新并释放占用的资源。退出界面程序时调用;只关当前界面、程序还要继续跑,用 xml:close。

返回值

local result = plcui.deinit()

含义说明:调用是否成功
数据类型:boolean
取值范围:恒为 true
返回示例:plcui.deinit()

示例

-- 退出界面
plcui.deinit()

6.1.5 plcui.device_bind_touch(tp_device)

功能

把触摸设备绑定给界面引擎,绑定之后按钮点击、输入框聚焦这些触摸操作才会响应。真机上必做,PC 模拟器用鼠标模拟触摸,可以不调用。

参数

tp_device

参数含义:触摸设备句柄
数据类型:userdata
取值范围:tp.init 返回的句柄;传 nil 表示解除绑定
是否必选:必选
注意事项:必须在 plcui.init 之后调用;触摸初始化的完整顺序见 2.4 真机部署与触摸初始化
参数示例:tp_device

返回值

local result = plcui.device_bind_touch(tp_device)

含义说明:绑定是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = plcui.device_bind_touch(tp_device)
注意事项:E_NOT_INITIALIZED 表示没先调用 plcui.init;E_TYPE 表示参数不是 tp.init 返回的句柄;
          E_PLATFORM 表示触摸设备未就绪或触摸分辨率与画布尺寸不一致

示例

-- 真机触摸初始化:先 tp.init,再在 plcui.init 之后绑定
i2c.setup(1, i2c.FAST)
local tp_device = tp.init("gt911", {port = 1, pin_rst = 14, pin_int = 51, w = 1024, h = 600})

plcui.init({width = 1024, height = 600})
local ok, err = plcui.device_bind_touch(tp_device)
if not ok then log.warn("plcui", err.code, err.message) end

6.1.6 plcui.version()

功能

获取 plcui 核心库版本号。

返回值

local version = plcui.version()

含义说明:核心库版本号
数据类型:string
取值范围:例如 "1.0.0"
返回示例:log.info("plcui version", plcui.version())

示例

-- 打印库版本
log.info("plcui version", plcui.version())

6.1.7 plcui.snapshot(path)

功能

把当前界面截屏保存成图片文件,便于留存界面效果或排查显示问题。只有 PC 模拟器能用,真机上调用返回 E_UNSUPPORTED;真机上看界面效果要用外部拍摄或录屏。

参数

path

参数含义:截屏文件的保存路径
数据类型:string
取值范围:本机文件系统路径
是否必选:必选
注意事项:写电脑上的路径,不是设备上的 /luadb、/sd 之类的路径;保存的目录要先建好
参数示例:"D:\\screen.bmp"

返回值

local result = plcui.snapshot(path)

含义说明:保存是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = plcui.snapshot("D:\\screen.bmp")

示例

-- 保存当前界面截图
plcui.snapshot("D:\\screen.bmp")

6.1.8 plcui.status()

功能

查询界面当前运行状态,包括当前页、组件数量、是否正在输入等。

返回值

local status = plcui.status()

含义说明:状态信息表
数据类型:table
取值范围:包含以下字段:
{
    参数含义:核心库版本号
    数据类型:string
    取值范围:例如 "1.0.0"
    注意事项:—
    version = ,

    参数含义:是否已初始化
    数据类型:boolean
    取值范围:true 或 false
    注意事项:—
    initialized = ,

    参数含义:是否开启了自动刷新
    数据类型:boolean
    取值范围:true 或 false
    注意事项:—
    auto_refresh = ,

    参数含义:目标刷新频率
    数据类型:number
    取值范围:恒为 60
    注意事项:是目标值不是实测帧率
    refresh_hz = ,

    参数含义:自动刷新次数
    数据类型:number
    取值范围:大于等于 0
    注意事项:—
    auto_refresh_count = ,

    参数含义:画布宽度和高度
    数据类型:number
    取值范围:1到2048,单位像素
    注意事项:就是 plcui.init 传入的值
    width = ,
    height = ,

    参数含义:运行环境标识
    数据类型:string
    取值范围:模拟器和真机取值不同
    注意事项:同一套代码要区分环境时用它判断
    platform = ,

    参数含义:切图缓存容量
    数据类型:number
    取值范围:大于 0,单位字节
    注意事项:编译时定好的容量,切图放不下时加载会返回 E_RESOURCE
    image_cache_bytes = ,

    参数含义:切图走硬件解码和软件解码的次数
    数据类型:number
    取值范围:大于等于 0
    注意事项:只是计数,不代表帧数或刷新次数;没有统计的环境返回 0
    image_hw_decodes = ,
    image_sw_decodes = ,

    参数含义:界面是否已显示
    数据类型:boolean
    取值范围:true 或 false
    注意事项:—
    active = ,

    参数含义:当前页面 ID
    数据类型:string
    取值范围:布局文件里某个 page 的 id
    注意事项:界面显示之后才有这个字段
    page = ,

    参数含义:组件数、资源数
    数据类型:number
    取值范围:组件不超过 512,资源不超过 256
    注意事项:界面显示之后才有这两个字段,可用来确认没有逼近上限
    component_count = ,
    resource_count = ,

    参数含义:被丢弃的事件数
    数据类型:number
    取值范围:大于等于 0
    注意事项:事件队列最多 64 条,超出会丢弃;界面显示之后才有这个字段
    events_dropped = ,

    参数含义:是否正在输入
    数据类型:boolean
    取值范围:true 或 false
    注意事项:输入框正在编辑时为 true;界面显示之后才有这个字段
    editing = ,
}
返回示例:if plcui.status().page == "main" then update_main() end

示例

-- 判断当前是不是首页
if plcui.status().page == "main" then update_main() end

6.1.9 xml:find(page_id, component_id)

功能

按页面 ID 和组件 ID 查找组件,拿到组件句柄后才能调用组件接口。

参数

page_id

参数含义:页面 ID
数据类型:string
取值范围:布局文件 page 的 id 属性取值
是否必选:必选
注意事项:只能查当前页的组件,查别的页面会返回 E_PAGE_INACTIVE
参数示例:"main"

component_id

参数含义:组件 ID
数据类型:string
取值范围:布局文件组件 id 属性的取值
是否必选:必选
注意事项:切换页面或换布局文件后原来的句柄全部失效,必须重新 find
参数示例:"status"

返回值

local obj = xml:find(page_id, component_id)

含义说明:组件句柄
数据类型:userdata 或 nil, table
取值范围:成功返回组件句柄,失败返回 nil 和错误表
返回示例:local label = xml:find("main", "status")
注意事项:拿到后可以调用该组件的 set 接口,接口清单见 6.2

示例

-- 更新状态栏文字
function update_status(text)
    xml:find("main", "status"):set_text(text)
end

6.1.10 xml:show(page_id)

功能

切换到指定页面。如果在事件处理函数里调用,切页会延后到下一轮执行。

参数

page_id

参数含义:目标页面 ID
数据类型:string
取值范围:布局文件 page 的 id 属性取值
是否必选:必选
注意事项:切页后原有组件句柄全部失效
参数示例:"details"

返回值

local result = xml:show(page_id)

含义说明:调用是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = xml:show("details")
注意事项:在事件里切页时要延时 30 毫秒再去访问新页面的组件

示例

-- 切换到详情页
function on_show_details(event)
    xml:show("details")
    sys.timerStart(update_details, 30)
end

6.1.11 xml:close()

功能

关闭当前界面,之后不再显示。可以重复调用。注意它只关界面,不停止界面刷新:退出界面程序还要再调 plcui.deinit,只调 xml:close 引擎还在跑。

返回值

local result = xml:close()

含义说明:调用是否成功
数据类型:boolean
取值范围:恒为 true
返回示例:xml:close()
注意事项:关闭之后这个界面对象和它 xml:find 查出来的组件句柄都不能再用,要用得重新 plcui.load;
          在事件回调里调用会延后到回调结束之后执行

示例

-- 关闭界面
xml:close()

6.1.12 事件回调 function(event)

功能

布局文件里 on-click、on-change、on-submit、on-cancel 指定的处理函数,事件发生时由 plcui 自动调用。

参数

event

参数含义:事件信息表
数据类型:table
是否必选:必选
取值范围:包含以下字段:
{
    参数含义:事件类型
    数据类型:string
    取值范围:"click"、"change"、"submit"、"cancel"
    注意事项:不带 on- 前缀,取值见 5.1
    type = ,

    参数含义:事件所在页面 ID
    数据类型:string
    取值范围:布局文件里某个 page 的 id
    注意事项:—
    page = ,

    参数含义:触发事件的组件 ID
    数据类型:string
    取值范围:布局文件里某个组件的 id
    注意事项:—
    id = ,

    参数含义:输入框文本
    数据类型:string
    取值范围:任意文本
    注意事项:只有 change、submit、cancel 三种事件有,click 没有;cancel 事件里是还原后的原文本
    text = ,
}
参数示例:function on_setpoint_submit(event)
             update_target(event.text)
         end

返回值

含义说明:无,返回值不参与后续流程
数据类型:—
取值范围:—
返回示例:—
注意事项:处理函数里报错不影响界面继续运行

示例

-- 启动按钮点击事件
function on_start_click(event)
    update_status("已启动")
end

-- 设定值提交事件
function on_setpoint_submit(event)
    update_status("设定值 " .. event.text)
end

使用说明

1、事件处理函数里可以改界面,但不能加载新的布局文件,切页也要延时执行; 2、同一个输入框连续输入时,change 事件会合并,只通知最后一次; 3、事件队列最多 64 条,超出会丢弃,丢弃数量可以查 plcui.status().events_dropped; 4、切换页面会清掉还没来得及处理的事件。

6.2 组件接口

所有组件对象都带有下面 6.2.1 的公共方法,各组件章节不再逐一列出。

6.2.1 公共方法

6.2.1.1 obj:set_hidden(hidden)

功能

显示或隐藏组件,七种组件都支持。

参数

hidden

参数含义:是否隐藏
数据类型:boolean
取值范围:true 隐藏,false 显示
是否必选:必选
注意事项:必须传 true 或 false,传 1、0 或 nil 会直接报错;隐藏正在输入的输入框会取消当前编辑
参数示例:true

返回值

local result = obj:set_hidden(hidden)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:set_hidden(true)

示例

-- 隐藏提示条
xml:find("main", "tip"):set_hidden(true)
6.2.1.2 obj:set_disabled(disabled)

功能

启用或禁用组件,禁用后不可点击、不可输入,并连同子组件一起置灰,七种组件都支持。

参数

disabled

参数含义:是否禁用
数据类型:boolean
取值范围:true 禁用,false 启用
是否必选:必选
注意事项:必须传 true 或 false,传 1、0 或 nil 会直接报错
参数示例:true

返回值

local result = obj:set_disabled(disabled)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:set_disabled(false)

示例

-- 禁用启动按钮
xml:find("main", "start"):set_disabled(true)
6.2.1.3 obj:state()

功能

读取组件当前状态,包括位置尺寸、显示状态,以及各组件的专属信息。

返回值

local state = obj:state()

含义说明:组件状态表
数据类型:table
取值范围:所有组件都有 id、type、x、y、w、h、hidden、disabled、checked、pressed;
          image 另有 src;文字类组件另有 font,用 hzfont 时另有 font_size;
          table 另有 rows、columns;chart 另有 point_count、series_count、y_min、y_max
返回示例:local state = xml:find("main", "level"):state()
注意事项:x、y 是相对页面左上角的坐标,不是布局文件里写的相对父容器内容区的坐标,两者不要混用;
          hidden、disabled 只描述组件自己,父级被隐藏或禁用时它照样不可见、不可点

示例

-- 读取液位组件位置
local state = xml:find("main", "level"):state()
log.info("level pos", state.x, state.y)
6.2.1.4 obj:set_font_size(font_size)

功能

运行时调整文字大小,label、textarea、keyboard、table 四种组件支持。

参数

font_size

参数含义:字号
数据类型:number
取值范围:1到255
是否必选:必选
注意事项:只对 hzfont 字体有效,其他字体会返回 E_TYPE
参数示例:24

返回值

local result = obj:set_font_size(font_size)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:set_font_size(24)

示例

-- 调大状态栏字号
xml:find("main", "status"):set_font_size(24)

6.2.2 图片(image)

图片组件用一张切图显示,靠换切图表达四种状态,是唯一可点击的组件,工控界面上的按钮、指示灯、状态图都用它。

6.2.2.1 创建(XML)

功能

在布局文件里声明图片组件,指定常态切图和各种状态下的切图。

参数

image

参数含义:图片组件的 XML 属性
数据类型:XML 属性
是否必选:是
取值范围:包含以下属性:
{
    参数含义:组件 ID
    数据类型:string
    取值范围:63 字节以内,同一页里不重名
    是否必选:必选
    注意事项:运行时用 xml:find(页面ID, 组件ID) 查找组件
    参数示例:id="start"
    id = ,

    参数含义:组件左上角 X 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:x="20"
    x = ,

    参数含义:组件左上角 Y 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:y="60"
    y = ,

    参数含义:组件宽度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:图片会被拉伸填满这个宽度
    参数示例:w="140"
    w = ,

    参数含义:组件高度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:图片会被拉伸填满这个高度
    参数示例:h="34"
    h = ,

    参数含义:初始是否隐藏
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false
    参数示例:hidden="false"
    hidden = ,

    参数含义:初始是否禁用
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false,禁用后显示 src-disabled 切图且不可点击
    参数示例:disabled="false"
    disabled = ,

    参数含义:常态图资源 ID
    数据类型:string
    取值范围:<resources> 里声明的 image 资源 ID
    是否必选:必选
    注意事项:填资源 ID,不是文件名
    参数示例:src="btn_start"
    src = ,

    参数含义:按下状态图资源 ID
    数据类型:string
    取值范围:<resources> 里声明的 image 资源 ID
    是否必选:可选
    注意事项:不填则按下时仍显示 src
    参数示例:src-pressed="btn_start_pressed"
    src-pressed = ,

    参数含义:选中状态图资源 ID
    数据类型:string
    取值范围:<resources> 里声明的 image 资源 ID
    是否必选:可选
    注意事项:不填则选中时仍显示 src
    参数示例:src-checked="btn_start_checked"
    src-checked = ,

    参数含义:禁用状态图资源 ID
    数据类型:string
    取值范围:<resources> 里声明的 image 资源 ID
    是否必选:可选
    注意事项:不填则禁用时仍显示 src
    参数示例:src-disabled="btn_start_disabled"
    src-disabled = ,

    参数含义:是否可点击
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false;状态图优先级是禁用 > 按下 > 选中 > 常态
    参数示例:clickable="true"
    clickable = ,

    参数含义:点击事件处理函数名
    数据类型:string
    取值范围:plcui.load 的 handlers 里的键名
    是否必选:可选
    注意事项:必须同时写 clickable="true",事件对象里没有 text 字段
    参数示例:on-click="start_click"
    on-click = ,

    参数含义:触摸区左边界内缩
    数据类型:number
    取值范围:0到4096,单位像素
    是否必选:可选
    注意事项:默认 0
    参数示例:hit-x="0"
    hit-x = ,

    参数含义:触摸区上边界内缩
    数据类型:number
    取值范围:0到4096,单位像素
    是否必选:可选
    注意事项:默认 0
    参数示例:hit-y="0"
    hit-y = ,

    参数含义:触摸区宽度
    数据类型:number
    取值范围:0到4096,单位像素
    是否必选:可选
    注意事项:默认等于 w;触摸区必须完整落在组件范围内
    参数示例:hit-w="140"
    hit-w = ,

    参数含义:触摸区高度
    数据类型:number
    取值范围:0到4096,单位像素
    是否必选:可选
    注意事项:默认等于 h;触摸区必须完整落在组件范围内
    参数示例:hit-h="34"
    hit-h = ,
}

示例

<!-- 可点击的启动按钮,常态和按下两张图 -->
<image id="start" x="20" y="60" w="140" h="34"
       src="btn_start" src-pressed="btn_start_pressed"
       clickable="true" on-click="start_click"/>

<!-- 纯状态指示图,不可点击 -->
<image id="pump_status" x="200" y="60" w="34" h="34"
       src="pump_status_off" src-checked="pump_status_on"/>
6.2.2.2 image:set_src(resource_id)

功能

更换图片组件显示的切图。

参数

resource_id

参数含义:切图资源 ID
数据类型:string
取值范围:<resources> 里声明的 image 资源 ID
是否必选:必选
注意事项:填资源 ID,不是文件名
参数示例:"pump_status_checked"

返回值

local result = image:set_src(resource_id)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:set_src("pump_status_checked")

示例

-- 换成运行状态图
xml:find("main", "pump_status"):set_src("pump_status_checked")
6.2.2.3 image:set_checked(checked)

功能

切换图片组件的选中状态,选中后显示 src-checked 那张切图。

参数

checked

参数含义:是否选中
数据类型:boolean
取值范围:true 选中,false 取消选中
是否必选:必选
注意事项:必须传 true 或 false,传 1、0 或 nil 会直接报错
参数示例:true

返回值

local result = image:set_checked(checked)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:set_checked(true)

示例

-- 标记按钮为选中
xml:find("main", "start"):set_checked(true)

6.2.3 标签(label)

标签组件用于显示静态文字:状态栏、标题、单位、报警提示都用它。

6.2.3.1 创建(XML)

功能

在布局文件里声明标签组件,指定显示文字、颜色和对齐方式。

参数

label

参数含义:标签组件的 XML 属性
数据类型:XML 属性
是否必选:是
取值范围:包含以下属性:
{
    参数含义:组件 ID
    数据类型:string
    取值范围:63 字节以内,同一页里不重名
    是否必选:必选
    注意事项:运行时用 xml:find(页面ID, 组件ID) 查找组件
    参数示例:id="status"
    id = ,

    参数含义:组件左上角 X 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:x="20"
    x = ,

    参数含义:组件左上角 Y 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:y="20"
    y = ,

    参数含义:组件宽度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:文字在这个宽度内对齐
    参数示例:w="200"
    w = ,

    参数含义:组件高度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:—
    参数示例:h="26"
    h = ,

    参数含义:初始是否隐藏
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false
    参数示例:hidden="false"
    hidden = ,

    参数含义:初始是否禁用
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false
    参数示例:disabled="false"
    disabled = ,

    参数含义:字体
    数据类型:string
    取值范围:字体资源 ID,或直接写 "hzfont"
    是否必选:可选
    注意事项:默认用系统默认字体;想用 font_size 就要配 hzfont
    参数示例:font="hzfont"
    font = ,

    参数含义:字号
    数据类型:number
    取值范围:1到255
    是否必选:可选
    注意事项:默认 16,只对 hzfont 有效
    参数示例:font_size="20"
    font_size = ,

    参数含义:显示文字
    数据类型:string
    取值范围:UTF-8 编码,511 字节以内
    是否必选:可选
    注意事项:默认空字符串,超出部分不显示
    参数示例:text="就绪"
    text = ,

    参数含义:文字颜色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认 #ffffff
    参数示例:color="#e1fffa"
    color = ,

    参数含义:文字对齐方式
    数据类型:string
    取值范围:"left"、"center"、"right"
    是否必选:可选
    注意事项:默认 left,取值见 5.2
    参数示例:align="left"
    align = ,
}

示例

<label id="status" x="20" y="20" w="200" h="26" text="就绪"
       font="hzfont" font_size="20" color="#e1fffa" align="left"/>
6.2.3.2 label:set_text(text)

功能

修改标签显示的文字。

参数

text

参数含义:要显示的文字
数据类型:string
取值范围:UTF-8 编码,511 字节以内,可以是空字符串
是否必选:必选
注意事项:超出部分不显示
参数示例:"已启动"

返回值

local result = label:set_text(text)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:set_text("已启动")

示例

-- 更新状态栏文字
xml:find("main", "status"):set_text("已启动")
6.2.3.3 label:get_text()

功能

读取标签当前显示的文字。

返回值

local text = label:get_text()

含义说明:标签文字
数据类型:string 或 nil, table
取值范围:成功返回文字,失败返回 nil 和错误表
返回示例:local text = xml:find("main", "status"):get_text()

示例

-- 读取当前状态文字
local text = xml:find("main", "status"):get_text()

6.2.4 输入框(textarea)

输入框组件用于参数录入,配上键盘组件即可完成数值和文本输入,自带确认、取消、字符过滤和长度限制。

6.2.4.1 创建(XML)

功能

在布局文件里声明输入框组件,指定初始文本、字符限制和关联键盘。

参数

textarea

参数含义:输入框组件的 XML 属性
数据类型:XML 属性
是否必选:是
取值范围:包含以下属性:
{
    参数含义:组件 ID
    数据类型:string
    取值范围:63 字节以内,同一页里不重名
    是否必选:必选
    注意事项:运行时用 xml:find(页面ID, 组件ID) 查找组件
    参数示例:id="setpoint"
    id = ,

    参数含义:组件左上角 X 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:x="20"
    x = ,

    参数含义:组件左上角 Y 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:y="110"
    y = ,

    参数含义:组件宽度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:—
    参数示例:w="120"
    w = ,

    参数含义:组件高度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:多行模式下文字可以滚动
    参数示例:h="26"
    h = ,

    参数含义:初始是否隐藏
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false,隐藏正在编辑的输入框会取消当前编辑
    参数示例:hidden="false"
    hidden = ,

    参数含义:初始是否禁用
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false,禁用后不可输入
    参数示例:disabled="false"
    disabled = ,

    参数含义:字体
    数据类型:string
    取值范围:字体资源 ID,或直接写 "hzfont"
    是否必选:可选
    注意事项:默认用系统默认字体;想用 font_size 就要配 hzfont
    参数示例:font="hzfont"
    font = ,

    参数含义:字号
    数据类型:number
    取值范围:1到255
    是否必选:可选
    注意事项:默认 16,只对 hzfont 有效
    参数示例:font_size="20"
    font_size = ,

    参数含义:初始文本
    数据类型:string
    取值范围:UTF-8 编码,511 字节以内
    是否必选:可选
    注意事项:默认空字符串
    参数示例:text="75.0"
    text = ,

    参数含义:文字颜色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认 #ffffff
    参数示例:color="#edf7fb"
    color = ,

    参数含义:文字对齐方式
    数据类型:string
    取值范围:"left"、"center"、"right"
    是否必选:可选
    注意事项:默认 left,取值见 5.2
    参数示例:align="left"
    align = ,

    参数含义:最大输入长度
    数据类型:number
    取值范围:1到511
    是否必选:可选
    注意事项:默认 511,按字符个数算,不是字节数
    参数示例:max-length="5"
    max-length = ,

    参数含义:允许输入的字符集合
    数据类型:string
    取值范围:127 字节以内
    是否必选:可选
    注意事项:默认不限制,只允许集合里出现的字符
    参数示例:accepted-chars="0123456789."
    accepted-chars = ,

    参数含义:配套键盘的组件 ID
    数据类型:string
    取值范围:本页 keyboard 组件的 id,可以写在 textarea 的后面,但不能跨页
    是否必选:可选
    注意事项:不填则点击输入框不会弹键盘
    参数示例:keyboard="num_kb"
    keyboard = ,

    参数含义:是否单行
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 true,设为 false 时允许多行并可滚动
    参数示例:one-line="true"
    one-line = ,

    参数含义:文本变化事件
    数据类型:string
    取值范围:plcui.load 的 handlers 里的键名
    是否必选:可选
    注意事项:程序里调用 textarea:set_text() 不触发;连续输入会合并,只通知最后一次
    参数示例:on-change="setpoint_change"
    on-change = ,

    参数含义:确认提交事件
    数据类型:string
    取值范围:plcui.load 的 handlers 里的键名
    是否必选:可选
    注意事项:由键盘的确认键触发,事件对象的 text 是刚提交的文本
    参数示例:on-submit="setpoint_submit"
    on-submit = ,

    参数含义:取消事件
    数据类型:string
    取值范围:plcui.load 的 handlers 里的键名
    是否必选:可选
    注意事项:键盘取消键或点击输入框和键盘以外的区域触发,文本自动还原
    参数示例:on-cancel="setpoint_cancel"
    on-cancel = ,
}

示例

<!-- 只能输数字和小数点的设定值输入框 -->
<textarea id="setpoint" x="20" y="110" w="120" h="26" text="75.0"
          font="hzfont" font_size="20" color="#edf7fb"
          one-line="true" max-length="5" accepted-chars="0123456789."
          keyboard="num_kb" on-submit="setpoint_submit" on-cancel="setpoint_cancel"/>

<!-- 多行备注输入框,不配键盘,用程序写入 -->
<textarea id="remark" x="20" y="150" w="300" h="80"
          font="hzfont" font_size="20" one-line="false" max-length="200"/>
6.2.4.2 textarea:set_text(text)

功能

修改输入框的内容。

参数

text

参数含义:要写入的文本
数据类型:string
取值范围:511 字节以内,且符合输入框自身的 max-length、accepted-chars、one-line 限制
是否必选:必选
注意事项:通过本接口写入不触发 on-change 事件
参数示例:"75.0"

返回值

local result = textarea:set_text(text)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:set_text("75.0")

示例

-- 写入设定值
xml:find("main", "setpoint"):set_text("75.0")
6.2.4.3 textarea:get_text()

功能

读取输入框当前内容,正在编辑时读到的是尚未确认的草稿。

返回值

local text = textarea:get_text()

含义说明:输入框文本
数据类型:string 或 nil, table
取值范围:成功返回文本,失败返回 nil 和错误表
返回示例:local text = xml:find("main", "setpoint"):get_text()

示例

-- 读取设定值
local text = xml:find("main", "setpoint"):get_text()
6.2.4.4 textarea:focus()

功能

让输入框进入编辑状态并弹出键盘。

返回值

local result = textarea:focus()

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:focus()
注意事项:输入框隐藏或禁用时会失败

示例

-- 弹出键盘编辑设定值
xml:find("main", "setpoint"):focus()

6.2.5 键盘(keyboard)

键盘组件提供数字键盘和英文键盘,由输入框调起,确认或取消后自动收起,平时不需要管它的显示和隐藏。

6.2.5.1 创建(XML)

功能

在布局文件里声明键盘组件,供同页的输入框调用。

参数

keyboard

参数含义:键盘组件的 XML 属性
数据类型:XML 属性
是否必选:是
取值范围:包含以下属性:
{
    参数含义:组件 ID
    数据类型:string
    取值范围:63 字节以内,同一页里不重名
    是否必选:必选
    注意事项:输入框的 keyboard 属性填的就是这个 ID
    参数示例:id="num_kb"
    id = ,

    参数含义:组件左上角 X 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:x="20"
    x = ,

    参数含义:组件左上角 Y 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:y="150"
    y = ,

    参数含义:组件宽度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:—
    参数示例:w="300"
    w = ,

    参数含义:组件高度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:—
    参数示例:h="150"
    h = ,

    参数含义:初始是否隐藏
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认隐藏,点击输入框时自动弹出,不需要自己管显示和隐藏
    参数示例:hidden="true"
    hidden = ,

    参数含义:初始是否禁用
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false
    参数示例:disabled="false"
    disabled = ,

    参数含义:字体
    数据类型:string
    取值范围:字体资源 ID,或直接写 "hzfont"
    是否必选:可选
    注意事项:默认 montserrat_20;想用 font_size 就要配 hzfont
    参数示例:font="hzfont"
    font = ,

    参数含义:字号
    数据类型:number
    取值范围:1到255
    是否必选:可选
    注意事项:默认 16,只对 hzfont 有效
    参数示例:font_size="20"
    font_size = ,

    参数含义:键盘布局
    数据类型:string
    取值范围:"number" 数字键盘、"text" 英文键盘
    是否必选:可选
    注意事项:默认 number,取值见 5.3
    参数示例:layout="number"
    layout = ,
}

示例

<keyboard id="num_kb" x="20" y="150" w="300" h="150" layout="number"
          font="hzfont" font_size="20"/>

6.2.6 面板(container)

面板组件把一组组件框起来统一摆放,可以配背景、边框和圆角,适合分区、分组、做卡片效果。整页纯色背景也可以直接铺一个纯色面板或用 page 的 background 属性,不必出背景切图,运行更快。

6.2.6.1 创建(XML)

功能

在布局文件里声明面板组件,把若干组件包在里面统一摆放。

参数

container

参数含义:面板组件的 XML 属性
数据类型:XML 属性
是否必选:是
取值范围:包含以下属性:
{
    参数含义:组件 ID
    数据类型:string
    取值范围:63 字节以内,同一页里不重名
    是否必选:必选
    注意事项:运行时用 xml:find(页面ID, 组件ID) 查找组件
    参数示例:id="alarm_panel"
    id = ,

    参数含义:组件左上角 X 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:x="10"
    x = ,

    参数含义:组件左上角 Y 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:y="10"
    y = ,

    参数含义:组件宽度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:—
    参数示例:w="460"
    w = ,

    参数含义:组件高度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:—
    参数示例:h="200"
    h = ,

    参数含义:初始是否隐藏
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false,隐藏会连同子组件一起
    参数示例:hidden="false"
    hidden = ,

    参数含义:初始是否禁用
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false,禁用会连同子组件一起置灰
    参数示例:disabled="false"
    disabled = ,

    参数含义:背景色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认无背景
    参数示例:background="#153c56"
    background = ,

    参数含义:背景不透明度
    数据类型:number
    取值范围:0到255
    是否必选:可选
    注意事项:默认 255,只在写了 background 时有意义
    参数示例:bg-opacity="255"
    bg-opacity = ,

    参数含义:边框颜色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认 #000000
    参数示例:border-color="#24465c"
    border-color = ,

    参数含义:边框宽度
    数据类型:number
    取值范围:0到4096,单位像素
    是否必选:可选
    注意事项:默认 0;会平移所有子组件的坐标起点
    参数示例:border-width="1"
    border-width = ,

    参数含义:圆角大小
    数据类型:number
    取值范围:0到4096,单位像素
    是否必选:可选
    注意事项:默认 0
    参数示例:radius="8"
    radius = ,

    参数含义:四边留白
    数据类型:number
    取值范围:0到4096,单位像素
    是否必选:可选
    注意事项:默认 0;子组件的 (0,0) 落在四周各内缩 padding + border-width 之后的位置
    参数示例:padding="4"
    padding = ,
}

说明

子组件的坐标是相对面板内容区的,内容区 = 面板范围四周各内缩 padding + border-width。例如 padding="4" border-width="1" 的面板里,子组件写 (0,0) 实际显示在面板内缩 5 像素处。

示例

<container id="alarm_panel" x="10" y="10" w="460" h="200"
           background="#153c56" radius="8" padding="4" border-width="1">
  <label id="alarm_title" x="0" y="0" w="200" h="26" text="报警记录"
         font="hzfont" font_size="20" color="#e1fffa"/>
  <table id="alarm_list" x="0" y="30" w="440" h="160" rows="3" columns="2"/>
</container>

6.2.7 表格(table)

表格组件用于行列数据展示:报警记录、参数列表、日志明细都用它,支持表头、隔行变色、按列对齐,数据长了可以拖动查看。

6.2.7.1 创建(XML)

功能

在布局文件里声明表格组件,指定行列数和配色,用 <cell> 填初始内容。

参数

table

参数含义:表格组件的 XML 属性
数据类型:XML 属性
是否必选:是
取值范围:包含以下属性:
{
    参数含义:组件 ID
    数据类型:string
    取值范围:63 字节以内,同一页里不重名
    是否必选:必选
    注意事项:运行时用 xml:find(页面ID, 组件ID) 查找组件
    参数示例:id="alarm"
    id = ,

    参数含义:组件左上角 X 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:x="10"
    x = ,

    参数含义:组件左上角 Y 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:y="10"
    y = ,

    参数含义:组件宽度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:—
    参数示例:w="460"
    w = ,

    参数含义:组件高度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:行数乘以最小行高不能超过这个高度,超出部分可以拖动查看
    参数示例:h="180"
    h = ,

    参数含义:初始是否隐藏
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false
    参数示例:hidden="false"
    hidden = ,

    参数含义:初始是否禁用
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false
    参数示例:disabled="false"
    disabled = ,

    参数含义:字体
    数据类型:string
    取值范围:字体资源 ID,或直接写 "hzfont"
    是否必选:可选
    注意事项:默认 hzfont
    参数示例:font="hzfont"
    font = ,

    参数含义:字号
    数据类型:number
    取值范围:1到255
    是否必选:可选
    注意事项:默认 16,只对 hzfont 有效
    参数示例:font_size="20"
    font_size = ,

    参数含义:行数
    数据类型:number
    取值范围:1到128,行数乘以列数不超过 1024
    是否必选:必选
    注意事项:包含表头,第 1 行就是表头
    参数示例:rows="3"
    rows = ,

    参数含义:列数
    数据类型:number
    取值范围:1到16,行数乘以列数不超过 1024
    是否必选:必选
    注意事项:col-widths、col-aligns 的个数必须等于这个值
    参数示例:columns="2"
    columns = ,

    参数含义:是否把第 1 行当表头
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 true
    参数示例:header="true"
    header = ,

    参数含义:最小行高
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:可选
    注意事项:默认自适应;行数乘以行高不能超过组件高度
    参数示例:row-height="28"
    row-height = ,

    参数含义:单元格上下留白
    数据类型:number
    取值范围:0到64,单位像素
    是否必选:可选
    注意事项:默认 8
    参数示例:cell-padding="8"
    cell-padding = ,

    参数含义:是否画全网格
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false,只画行下边线;设为 true 画全网格
    参数示例:grid="true"
    grid = ,

    参数含义:各列宽度
    数据类型:string
    取值范围:逗号分隔的整数,个数必须等于 columns
    是否必选:可选
    注意事项:默认平均分配
    参数示例:col-widths="120,340"
    col-widths = ,

    参数含义:全部列的默认对齐方式
    数据类型:string
    取值范围:"left"、"center"、"right"
    是否必选:可选
    注意事项:默认 left,取值见 5.2
    参数示例:align="left"
    align = ,

    参数含义:各列对齐方式
    数据类型:string
    取值范围:逗号分隔的 left/center/right,个数必须等于 columns
    是否必选:可选
    注意事项:默认等于 align
    参数示例:col-aligns="left,right"
    col-aligns = ,

    参数含义:表头对齐方式
    数据类型:string
    取值范围:"left"、"center"、"right"
    是否必选:可选
    注意事项:默认等于 align
    参数示例:header-align="left"
    header-align = ,

    参数含义:表头行底色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认 #153c56
    参数示例:header-color="#153c56"
    header-color = ,

    参数含义:间隔行底色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认 #10283b
    参数示例:alternate-color="#10283b"
    alternate-color = ,

    参数含义:单元格文字颜色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认 #ffffff
    参数示例:color="#ffffff"
    color = ,

    参数含义:表体底色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认 #0c2133
    参数示例:background="#0c2133"
    background = ,

    参数含义:边框颜色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认 #24465c
    参数示例:border-color="#24465c"
    border-color = ,

    参数含义:边框宽度
    数据类型:number
    取值范围:0到16,单位像素
    是否必选:可选
    注意事项:默认 1
    参数示例:border-width="1"
    border-width = ,
}

参数

cell

参数含义:表格单元格,写在 table 下,行列都从 1 开始
数据类型:XML 元素
是否必选:可选
取值范围:包含以下属性:
{
    参数含义:行号
    数据类型:number
    取值范围:1 到表格 rows,第 1 行是表头
    是否必选:必选
    注意事项:—
    参数示例:row="1"
    row = ,

    参数含义:列号
    数据类型:number
    取值范围:1 到表格 columns
    是否必选:必选
    注意事项:—
    参数示例:column="1"
    column = ,

    参数含义:单元格文字
    数据类型:string
    取值范围:511 字节以内
    是否必选:可选
    注意事项:默认空,没写内容的单元格显示为空
    参数示例:text="时间"
    text = ,
}

示例

<table id="alarm" x="10" y="10" w="460" h="180" rows="3" columns="2" grid="true">
  <cell row="1" column="1" text="时间"/>
  <cell row="1" column="2" text="内容"/>
</table>
6.2.7.2 table:set_cell(row, column, text)

功能

写入表格某个单元格的文字。

参数

row

参数含义:行号
数据类型:number
取值范围:1 到表格 rows,第 1 行是表头
是否必选:必选
参数示例:2

column

参数含义:列号
数据类型:number
取值范围:1 到表格 columns
是否必选:必选
参数示例:1

text

参数含义:单元格文字
数据类型:string
取值范围:511 字节以内
是否必选:必选
参数示例:"2026-11-10"

返回值

local result = table:set_cell(row, column, text)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:set_cell(2, 1, "2026-11-10")

示例

-- 写入第二行的两列
local alarm = xml:find("main", "alarm")
alarm:set_cell(2, 1, "2026-11-10")
alarm:set_cell(2, 2, "泵体超温")
6.2.7.3 table:get_cell(row, column)

功能

读取表格某个单元格的文字。

参数

row

参数含义:行号
数据类型:number
取值范围:1 到表格 rows
是否必选:必选
参数示例:2

column

参数含义:列号
数据类型:number
取值范围:1 到表格 columns
是否必选:必选
参数示例:1

返回值

local text = table:get_cell(row, column)

含义说明:单元格文字
数据类型:string 或 nil, table
取值范围:成功返回文字,失败返回 nil 和错误表
返回示例:local text = obj:get_cell(2, 1)
注意事项:没写过的单元格返回空字符串

示例

-- 读取第二行第一列
local text = xml:find("main", "alarm"):get_cell(2, 1)
6.2.7.4 table:set_column_width(column, width)

功能

调整表格某一列的宽度。

参数

column

参数含义:列号
数据类型:number
取值范围:1 到表格 columns
是否必选:必选
参数示例:1

width

参数含义:列宽
数据类型:number
取值范围:1到4096,单位像素
是否必选:必选
参数示例:120

返回值

local result = table:set_column_width(column, width)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:set_column_width(1, 120)

示例

-- 把时间列加宽
xml:find("main", "alarm"):set_column_width(1, 120)
6.2.7.5 table:get_size()

功能

读取表格的行数和列数。

返回值

local rows, columns = table:get_size()

含义说明:表格行数和列数,一次返回两个值
数据类型:number, number
取值范围:行数包含表头行
返回示例:local rows, columns = obj:get_size()
注意事项:成功返回两个数值,失败只返回 nil 和错误表,两种返回形式不同

示例

-- 读取表格行列数
local rows, columns = xml:find("main", "alarm"):get_size()

6.2.8 图表(chart)

图表组件用于趋势展示:温度曲线、压力曲线、能耗柱状图都用它,支持多条曲线同屏对比,可以隐藏单条曲线。

6.2.8.1 创建(XML)

功能

在布局文件里声明图表组件,指定类型、纵轴范围和网格,用 <series> 定义曲线。

参数

chart

参数含义:图表组件的 XML 属性
数据类型:XML 属性
是否必选:是
取值范围:包含以下属性:
{
    参数含义:组件 ID
    数据类型:string
    取值范围:63 字节以内,同一页里不重名
    是否必选:必选
    注意事项:运行时用 xml:find(页面ID, 组件ID) 查找组件
    参数示例:id="trend"
    id = ,

    参数含义:组件左上角 X 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:x="10"
    x = ,

    参数含义:组件左上角 Y 坐标
    数据类型:number
    取值范围:-4096到4096,单位像素
    是否必选:可选
    注意事项:默认 0,相对父容器内容区左上角
    参数示例:y="10"
    y = ,

    参数含义:组件宽度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:—
    参数示例:w="300"
    w = ,

    参数含义:组件高度
    数据类型:number
    取值范围:1到4096,单位像素
    是否必选:必选
    注意事项:—
    参数示例:h="160"
    h = ,

    参数含义:初始是否隐藏
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false
    参数示例:hidden="false"
    hidden = ,

    参数含义:初始是否禁用
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false
    参数示例:disabled="false"
    disabled = ,

    参数含义:图表类型
    数据类型:string
    取值范围:"line" 曲线图、"bar" 柱状图
    是否必选:可选
    注意事项:默认 line,取值见 5.4
    参数示例:type="line"
    type = ,

    参数含义:每条曲线的点数
    数据类型:number
    取值范围:2到1024
    是否必选:可选
    注意事项:默认 60;series 的 values 个数必须精确等于这个值
    参数示例:point-count="60"
    point-count = ,

    参数含义:纵轴最小值
    数据类型:number
    取值范围:-1000.00 到 1000.00,最多两位小数,且小于 y-max
    是否必选:可选
    注意事项:默认 0;数据超过这个范围要先在业务里换算成合适的单位
    参数示例:y-min="0"
    y-min = ,

    参数含义:纵轴最大值
    数据类型:number
    取值范围:-1000.00 到 1000.00,最多两位小数,且大于 y-min
    是否必选:可选
    注意事项:默认 100
    参数示例:y-max="100"
    y-max = ,

    参数含义:横向分隔线数量
    数据类型:number
    取值范围:0到32
    是否必选:可选
    注意事项:默认 5
    参数示例:hdiv="5"
    hdiv = ,

    参数含义:纵向分隔线数量
    数据类型:number
    取值范围:0到32
    是否必选:可选
    注意事项:默认 6
    参数示例:vdiv="6"
    vdiv = ,

    参数含义:曲线粗细
    数据类型:number
    取值范围:1到16,单位像素
    是否必选:可选
    注意事项:默认 2
    参数示例:line-width="2"
    line-width = ,

    参数含义:网格线颜色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认 #34566b
    参数示例:grid-color="#34566b"
    grid-color = ,

    参数含义:背景色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认 #000000,要配 bg-opacity 才看得到
    参数示例:background="#0c2133"
    background = ,

    参数含义:背景不透明度
    数据类型:number
    取值范围:0到255
    是否必选:可选
    注意事项:默认 0,完全透明
    参数示例:bg-opacity="255"
    bg-opacity = ,

    参数含义:边框颜色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认 #000000
    参数示例:border-color="#24465c"
    border-color = ,

    参数含义:边框宽度
    数据类型:number
    取值范围:0到16,单位像素
    是否必选:可选
    注意事项:默认 0
    参数示例:border-width="1"
    border-width = ,
}

参数

series

参数含义:图表曲线,写在 chart 下,每张图 1 到 8 个
数据类型:XML 元素
是否必选:必选,至少一个
取值范围:包含以下属性:
{
    参数含义:曲线 ID
    数据类型:string
    取值范围:63 字节以内,同一张图内不重名
    是否必选:必选
    注意事项:set_values、add_point、get_values、set_series_hidden 都要用它
    参数示例:id="supply"
    id = ,

    参数含义:曲线颜色
    数据类型:string
    取值范围:#RRGGBB 格式
    是否必选:可选
    注意事项:默认 #38d6ce;chart 本身没有 color 属性,颜色只能写在这里
    参数示例:color="#44dcf6"
    color = ,

    参数含义:初始是否隐藏
    数据类型:boolean
    取值范围:只能写 true 或 false
    是否必选:可选
    注意事项:默认 false
    参数示例:hidden="false"
    hidden = ,

    参数含义:初始数据点
    数据类型:string
    取值范围:逗号分隔的小数,个数必须精确等于 point-count,每个值 -1000.00 到 1000.00
    是否必选:可选
    注意事项:默认全部取 y-min
    参数示例:values="10,20,30"
    values = ,
}

示例

<chart id="trend" x="10" y="10" w="300" h="160" type="line" point-count="60"
       y-min="0" y-max="100">
  <series id="supply" color="#44dcf6"/>
  <series id="return" color="#f6b244"/>
</chart>
6.2.8.2 chart:set_values(series_id, values)

功能

一次性写入一条曲线的全部数据点,适合周期性刷新趋势曲线。

参数

series_id

参数含义:曲线 ID
数据类型:string
取值范围:series 的 id 属性取值
是否必选:必选
参数示例:"supply"

values

参数含义:数据点数组
数据类型:table
取值范围:每个元素是 -1000.00 到 1000.00 的数值,最多两位小数,数组长度必须精确等于 point-count
是否必选:必选
注意事项:整条曲线一起更新,只要有一个数据不合法,整条都不更新;
          超出纵轴范围的点按轴边界画出来,get_values 读回来的还是原始值
参数示例:{10, 20, 30}

返回值

local result = chart:set_values(series_id, values)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:set_values("supply", values)

示例

-- 刷新供水温度曲线
xml:find("main", "trend"):set_values("supply", supply_values)
6.2.8.3 chart:add_point(series_id, value)

功能

往曲线末尾追加一个数据点,最早的数据点自动挤掉。

参数

series_id

参数含义:曲线 ID
数据类型:string
取值范围:series 的 id 属性取值
是否必选:必选
参数示例:"supply"

value

参数含义:数据点数值
数据类型:number
取值范围:-1000.00 到 1000.00,最多两位小数
是否必选:必选
参数示例:68.5

返回值

local result = chart:add_point(series_id, value)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:add_point("supply", 68.5)

示例

-- 追加一个温度点
xml:find("main", "trend"):add_point("supply", 68.5)
6.2.8.4 chart:get_values(series_id)

功能

读取一条曲线的全部数据点。

参数

series_id

参数含义:曲线 ID
数据类型:string
取值范围:series 的 id 属性取值
是否必选:必选
参数示例:"supply"

返回值

local values = chart:get_values(series_id)

含义说明:数据点数组,从左到右排列
数据类型:table 或 nil, table
取值范围:成功返回数值数组,失败返回 nil 和错误表
返回示例:local values = obj:get_values("supply")

示例

-- 读取供水温度曲线数据
local values = xml:find("main", "trend"):get_values("supply")
6.2.8.5 chart:set_range(minimum, maximum)

功能

调整图表纵轴的显示范围,作用于整张图的所有曲线。

参数

minimum

参数含义:纵轴最小值
数据类型:number
取值范围:-1000.00 到 1000.00,且小于 maximum
是否必选:必选
参数示例:0

maximum

参数含义:纵轴最大值
数据类型:number
取值范围:-1000.00 到 1000.00,且大于 minimum
是否必选:必选
参数示例:120

返回值

local result = chart:set_range(minimum, maximum)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:set_range(0, 120)
注意事项:图表只画曲线和网格,不画刻度和坐标数值;要显示数值另放 label,或烘进背景切图

示例

-- 把纵轴改成 0~120
xml:find("main", "trend"):set_range(0, 120)
6.2.8.6 chart:set_series_hidden(series_id, hidden)

功能

显示或隐藏图表中的某条曲线。

参数

series_id

参数含义:曲线 ID
数据类型:string
取值范围:series 的 id 属性取值
是否必选:必选
参数示例:"return"

hidden

参数含义:是否隐藏
数据类型:boolean
取值范围:true 隐藏,false 显示
是否必选:必选
注意事项:必须传 true 或 false,传 1、0 或 nil 会直接报错
参数示例:true

返回值

local result = chart:set_series_hidden(series_id, hidden)

含义说明:设置是否成功
数据类型:boolean 或 nil, table
取值范围:成功返回 true,失败返回 nil 和错误表
返回示例:local result, err = obj:set_series_hidden("return", true)

示例

-- 隐藏回水温度曲线
xml:find("main", "trend"):set_series_hidden("return", true)

七、开发规范说明

开发流程的五个环节见 一、概述 中的开发流程。下面把每个环节写成一份契约:输入 是上一环节交出来的东西,产出物 是本环节要交的东西,通过判据 是可以逐条对照的检查项,不通过怎么办 交代退回到哪一环。AI 照着做时,每完成一个环节就按判据自检,全过再进下一环节,不要跳步。

7.1 HTML 交互原型(对应环节①)

这个环节定什么:布局和交互逻辑都在这里定稿,后面四环只是搬运。原型里点不通的交互,搬到 PLCUI 一样点不通,所以不要想着「先出个静态稿,交互后面再说」。

输入

需求描述:有几块画面、每块画面上要显示什么数据、要做什么操作。

产出物

1、design/spec.md:色板(#RRGGBB 列表,正文、标题、背景、报警各定一到两个颜色)、字号阶梯(例如 16、20、24、32)、间距网格(基本单位,例如 4 像素); 2、design/index.html:可交互原型,布局和交互逻辑都在里面跑通,素材直接嵌进来预览,所见即所得。

通过判据

1、界面上出现的每一个颜色值,都能在色板里找到; 2、字号只用了字号阶梯里的值,font_size 取值 1~255 且只对 hzfont 有效; 3、所有坐标、宽高、间距都是间距网格基本单位的整数倍; 4、没有控件重叠、没有文字被截断、主次层级一眼能分清; 5、每个交互都点过一遍:按钮的常态、按下、选中、禁用,输入框的确认和取消,页面之间的切换,行为都符合预期; 6、用到的元素都是 PLCUI 七种组件表达得了的(image、label、textarea、keyboard、container、table、chart),没有渐变、悬停、阴影、动画、滚动列表这类网页才有的效果; 7、每个画面都对着截图自查过 2~3 轮。

不通过怎么办

只改 design/index.html,改完重新自检,不要把视觉和交互问题带到后面环节。如果是规范本身不合适(例如字号阶梯定得太密),先改 design/spec.md 再改原型。判据 6 不满足时,改的是原型而不是 PLCUI 侧——PLCUI 能做什么、不能做什么,六、函数详解 里写清楚了,超出的交互换成分页或切图来表达。

7.2 素材流水线(对应环节②)

输入

design/index.html 与 design/spec.md。

产出物

1、assets/:切图目录; 2、assets/manifest.txt:资源 ID 与文件名的对照表,供布局导出使用。

通过判据

1、要表达状态的图元四张齐全:常态、按下、选中、禁用,尺寸一致,同名加 _pressed、_checked、_disabled 后缀; 2、背景是「隐藏动态层后整页烘成的一张图」,动态层只留状态图、文字、控件;如果整页背景是纯色,不出背景切图,导出时改用 page 的 background 属性或纯色 container 铺底,省掉一张大图的存储、解码和刷新开销,运行更快; 3、单个文件不超过 16MB,解码后 宽×高×4 不超过 8MB; 4、格式是 Baseline JPG 或 PNG,不是渐进式 JPG; 5、文件名不含中文和空格;同名文件不能放在不同目录,打包后目录会摊平成文件名,重名会冲突。

不通过怎么办

重出素材。如果根子在原型里的图元画得不对,回到环节①改原型,不要直接在素材上手改,否则下一版又对不上。

7.3 原型导出 XML(对应环节③)

输入

design/index.html 与 assets/manifest.txt。

产出物

1、main.xml:布局文件; 2、事件清单:on-* 取值与处理函数名的对照表,供 Lua 侧照抄。

通过判据

1、<ui version="0.1">,width、height 等于画布尺寸,entry 指向首屏页面; 2、导出的组件树、坐标、尺寸与 design/index.html 一致,可以逐个比对; 3、必填属性无遗漏,逐条对照 4.5 书写规则; 4、ID 不重名:资源 ID 和页面 ID 在整个文件里不重名,组件 ID 在同一页里不重名; 5、布局文件里写的每个 on-*,都能在事件清单里找到同名函数,事件名和原型里的交互一一对应; 6、on-click 同时写了 clickable="true",font_size 只出现在 hzfont 上; 7、数值约束都满足:y-min 小于 y-max、<series values> 的个数等于 point-count、col-widths 和 col-aligns 的个数等于 columns; 8、plcui.load 一次通过,返回的 err 为空。

不通过怎么办

按 err.code、err.line、err.column 定位改 main.xml。如果是切图尺寸对不上,退回环节②;如果是版式放不下、太挤,退回环节①。如果原型里的布局用 XML 表达不出来(例如靠元素相互压叠堆出来的效果),同样退回环节①调整原型,不要手工改 XML 绕过去,改了就和原型脱钩,下次导出又会覆盖掉。

7.4 交互逻辑搬成 Lua(对应环节④)

输入

main.xml、事件清单,以及作为唯一事实来源的 design/index.html:坐标、尺寸、颜色、交互行为都对着原型取,不要凭印象重新设计。

产出物

1、main.lua:入口,只做初始化和页面路由; 2、每个画面一个 Lua 文件,公共逻辑单独一个文件。

通过判据

1、原型里的每个交互行为,在 Lua 侧都有对应的处理函数,和事件清单逐条对得上,行为结果也一致:按钮按下后界面怎么变、输入框取消后还原成什么、页面怎么跳,都和原型里点出来的一样; 2、入口只有 main.lua,require 只写文件名、不带路径和后缀,整个工程不出现重名文件; 3、脚本里不写 require("sys")、require("plcui"),这两个都是内建核心库,直接调用即可; 4、函数一律具名,不用闭包和匿名函数,注释只写功能,跨文件共用的变量和函数不加 local; 5、切页之后延时 30 毫秒再 xml:find; 6、set_hidden、set_disabled、image:set_checked、chart:set_series_hidden 只传 true、false,不传 1、0、nil; 7、图表数值先换算到 -1000.00~1000.00 且最多两位小数,面板标题标注真实单位; 8、所有 Lua 文件语法检查通过,跑起来没有报错。

不通过怎么办

改 Lua。如果是布局里缺组件、事件名对不上,退回环节③;如果是素材没到位,退回环节②。如果某个交互在 Lua 里根本实现不了,先回环节①确认原型是不是用上了 PLCUI 表达不了的效果,不要在 Lua 里绕着实现。

原型写法与 PLCUI 写法的对照

照着这张对应关系搬,一行对一行,不需要重新设计:

原型(HTML)                        PLCUI
元素的 left/top/width/height   →  组件的 x / y / w / h
点击 onclick                    →  image 的 clickable="true" + on-click="函数名",Lua 里写同名函数
文本变化 / 确认 / 取消           →  textarea 的 on-change / on-submit / on-cancel
改文字                          →  label:set_text()、textarea:set_text()
改图片、切换状态                →  image:set_src()、image:set_checked()
显示隐藏                        →  obj:set_hidden()
禁用启用                        →  obj:set_disabled()
字号调整                        →  obj:set_font_size()
页面跳转                        →  xml:show(页面ID)
输入框聚焦、弹出键盘            →  textarea:focus()
数据刷新                        →  chart:set_values()、chart:add_point()、table:set_cell()
状态查询                        →  plcui.status()、obj:state()

7.5 验证闭环(对应环节⑤)

输入

可以跑起来的工程。

产出物

1、screen/:用 plcui.snapshot 截出的界面图; 2、自检记录:判据逐条的结论。

通过判据

1、截图与原型并排比对,没有明显偏差; 2、交互行为逐个对过原型:点按钮、输参数、取消编辑、切换页面,结果和原型里点出来的一致; 3、plcui.status() 三项指标正常:component_count 不超过 512、resource_count 不超过 256、events_dropped 为 0; 4、每个事件都实际触发过:click、change、submit、cancel 各至少一次,处理函数里没有报错; 5、页面来回切换两轮,没有句柄失效的报错; 6、下载到 Air1603(104 号固件)跑通,触摸响应正常。

不通过怎么办

先定位问题出在哪一环再退回:视觉偏差回环节①,切图效果回环节②,坐标、ID、事件名回环节③,数据和交互回环节④。不要在验证环节直接改源文件,改了下一版就和原型对不上了。

7.6 交付前自检清单

下面每一条都可以逐项打勾,全勾完再交付:

  • □ 界面上的色值、字号、间距都落在设计规范里
  • □ 每个画面都和原型对过截图
  • □ 原型里的每个交互在设备上都点过一遍,行为一致
  • □ 坐标、颜色、交互行为都是对着原型取的,没有凭印象重画
  • □ 原型用到的效果 PLCUI 都表达得了
  • □ 四态图元齐全,非纯色背景是整页烘出来的图
  • □ 纯色背景没出切图,用的是 page 的 background 或纯色 container
  • □ plcui.load 一次通过
  • □ 每个 on-* 都有同名处理函数
  • □ ID 无重名,必填属性无遗漏
  • □ 切页后重新 xml:find,并且延时 30 毫秒
  • □ 严格 boolean 的四个接口只传 true、false
  • □ plcui.status() 的组件数、资源数、丢事件数都正常
  • □ 真机在 plcui.init 之后调用了 plcui.device_bind_touch,按钮点得动
  • □ 打包后所有文件平铺进 /luadb,文件名少于 23 字节且无重名
  • □ Air1603(104 号固件)真机跑通

八、产品支持说明

支持的产品

Air1603(104 号固件)。

软件要求

1、Air1603 的 104 号固件; 2、布局文件版本必须是 0.1,库版本 1.0.0; 3、切图使用 Baseline JPG 或 PNG,配合 hzfont 矢量字体可显示中文。

兼容性说明

1、plcui.init 之后界面刷新自动运行,不需要额外启动;真机触摸响应还要调用 plcui.device_bind_touch 绑定触摸设备,见 2.4 真机部署与触摸初始化; 2、同名核心库、扩展库一起使用时,注意界面刷新频率对业务任务的影响,业务定时任务建议不低于 100 毫秒; 3、布局文件、切图和脚本一起打包下载,打包后所有文件平铺进 /luadb(目录层级不保留),文件名少于 23 字节且不重名,切图与布局文件放在同一目录下; 4、多个 Lua 文件要注意文件名在整个工程里不能重名,多个布局文件要各自准备切图资源; 5、AirUI 和 plcui 不能同时用,换用前先结束另一个,同时初始化会返回 E_DISPLAY_BUSY; 6、真机上画布不超过 1024 × 600,显示方向固定,不能旋转。

九、常见问题与排查

按症状查原因和解决办法,err.code 的完整含义见 5.5 错误码一节。

真机界面显示正常,按钮点不动、输入框没反应

原因:没有把触摸设备绑定给界面引擎。解决:按 i2c.setup → tp.init → plcui.init → plcui.device_bind_touch 的顺序补上绑定,写法见 2.4 真机部署与触摸初始化。

plcui.device_bind_touch 返回 E_NOT_INITIALIZED

原因:调用顺序错了,plcui.init 还没执行。解决:绑定必须放在 plcui.init 成功之后。

plcui.device_bind_touch 返回 E_PLATFORM

原因:触摸设备没就绪,或者 tp.init 填的 w、h 与 plcui.init 的 width、height 不一致。解决:确认 tp.init 成功返回句柄,触摸分辨率与画布尺寸填一样的值。

tp.init 返回 nil,日志打印 tp has been initialized

原因:tp.init 一次上电只接受一次,重复调用直接失败。解决:保存第一次返回的句柄复用;触摸不响应时只重新执行 i2c.setup 补回总线配置,不要重调 tp.init。

plcui.load 返回 E_VERSION

原因:<ui version> 不是 "0.1"。解决:布局文件版本必须精确写 version="0.1",注意这和库版本 1.0.0 不是同一个值。

plcui.load 返回 E_RESOURCE

原因:切图缺失或不合规。解决:src 只写切图文件名(相对布局文件所在目录,不支持子目录和绝对路径);切图与布局文件放同一目录(luadb 里都平铺在 /luadb);用 Baseline JPG 或 PNG,渐进式 JPG 不支持。

plcui.load 返回 E_HANDLER

原因:handlers 里缺少布局文件声明的事件函数。解决:布局里每个 on-* 的取值,都要在 handlers 里给出同名函数,缺一个都加载失败。

plcui.init 返回 E_DISPLAY_BUSY

原因:三种情况都报这个错——重复初始化、AirUI 还在用、别的显示界面还开着。解决:整个工程只初始化一次,需要重来先调 plcui.deinit;AirUI 和 plcui 不能同时用,换用 plcui 前先结束 AirUI。

退出界面程序后,再进界面报 E_DISPLAY_BUSY 或界面不刷新

原因:只调了 xml:close 没调 plcui.deinit。解决:xml:close 只关当前界面,界面刷新还在跑,退出界面程序必须再调 plcui.deinit。

真机上调用 plcui.snapshot 返回 E_UNSUPPORTED

原因:截屏只在 PC 模拟器支持。解决:对照原型截图在模拟器上做,真机看效果用外部拍摄或录屏。

事件回调里 plcui.load 返回 E_BUSY

原因:回调执行期间不允许换整份界面。解决:切页用 xml:show,要换另一份布局文件就延后到回调之外再 plcui.load。

切页后 xml:find 报 E_NOT_FOUND,或组件句柄报 E_OBJECT_CLOSED

原因:切页后旧页面的组件句柄全部失效。解决:切页后延时 30 毫秒再重新 xml:find,不要继续用旧句柄。

image:set_src 报错或换图不生效

原因:填了文件名。解决:set_src 填 <resources> 里声明的资源 ID,不是切图文件名。

require 报找不到文件,或下载后提示文件缺失

原因:require 写法不对,或打包后文件重名被覆盖。解决:require 只写文件名、不带路径和 .lua 后缀;打包后目录摊平,不同目录也不能重名;文件名少于 23 字节,不用中文和空格。

加载布局报 Unknown attribute

原因:属性名写错。解决:属性名只有 font_size 用下划线,其余一律连字符(max-length、on-click、point-count)。

界面启动慢、刷新卡、闪存占用大

原因:背景用了多张大切图。解决:多个背景共用一张背景图,jpg格式优先,png格式次选。能用纯色背景就不出切图,直接用 page 的 background 属性或纯色 container 铺底,渲染更快;只有带纹理、渐变、照片的背景才需要整页烘图。

按钮、输入框里的文字不居中,偏上

原因:文字在控件里默认贴顶,align 只管水平方向,没有垂直对齐属性。解决:控件 h 设为一行文字的高度 font_size + 3,y 按 目标 y + (目标 h - 字号 - 3) / 2 摆到垂直中心,完整写法见 5.2 文字对齐 align 的「文字上下居中」。

搜索