能力: 需授权开通(内部位 component.usercompose;票内须显式开通,* 不能覆盖)。
用设计器里的现有控件拼成可复用块,另存为工程内的 .yuc 定义后,从工具箱拖到任意窗体当「普通控件」用。对外只暴露你勾选的导出名(属性 / 事件),不必在业务代码里写内部子控件名。
这不是新的内置控件类型,也不改运行时
YsCtrlKind:宿主是分组框,用旁路戳yuc:定义名记住属于哪个用户组件。
1. 心智模型(先读这段)
| 概念 | 含义 |
|---|---|
| 定义 | 工程 组件/<名>.yuc:子控件树 + 导出表 + 可选悬停显隐 |
| 实例 / 宿主 | 窗体上的整组外壳(分组框),名如 搜索框1、名片行1 |
| 子控件 | 定义展开后的真实控件;运行时名 = 宿主名__定义内名(如 搜索框1__按钮2) |
| 导出名 | 另存时勾选的对外名字,如 搜索按钮、姓名;代码优先写这个 |
| 导出事件 | 把「子控件某事件」映射成 宿主.对外事件名 |
推荐写法:
搜索框1.搜索按钮 = "搜索" // 导出名 → 已映射的默认属性(按钮=标题,文本框=文本)
名片行1.姓名 = "小明"
名片行1.姓名颜色 = "#FFFFFF" // 导出的样式属性
也可以(进阶):
搜索框1.搜索按钮.标题 = "搜索" // 导出名当子控件别名,再点属性/方法
搜索框1.按钮2.可见 = 假 // 定义内子名(未导出也可)
搜索框1__按钮2.标题 = "搜索" // 内部全名(一般不必手写)
2. 从零做一块(设计器)
- 在窗体上用基础控件拼好一块(标签、文本框、按钮、图片框……)。
- 多选这些控件 → 右键 另存为用户组件…。
- 对话框里:
- 起名(会变成组件/<名>.yuc的文件名 /id;勿用+等非法标识符字符)
- 勾选要导出的属性(每个子控件可填对外名;空=不导出)
- 可选:文字色 等样式导出(如姓名颜色)
- 可选:悬停显示(平时隐藏、鼠标进入整组才显示的子控件名,逗号分隔) - 确定后:
- 工程树出现 📂 组件(可双击打开.yucJSON)
- 工具箱出现分组 用户组件 - 从工具箱再拖一个实例到窗体;子控件自动改名为
实例__定义内名。
整组操作: 点选宿主或任意下属 → 整组选中(尺寸手柄只在外层宿主)。移动 / 复制 / 粘贴 / 删除按整组。整组已选时再点子控件 → 单独选中该子项(可改子属性)。粘贴后自动续号(名片行2…)。
样式:在编辑定义前或展开后点子控件,用属性面板 / YSS 设字体、颜色(与普通控件相同)。实例侧主要改「显示什么」;导出了样式属性后也可在代码里改。
2.1 拼接时就排好内部布局(推荐)
可以在另存之前用水平/垂直布局 + 停靠把内部排好,定义会写进 .yuc,实例宿主被拉大时内部会按布局重排。
典型做法:
- 在窗体空白处放一个 垂直布局(或水平布局),设停靠=填充(或稍后挂到分组框内再填满)。
- 把标签、按钮、弹簧等放进该布局(所属容器=布局名),需要伸缩的设拉伸/弹簧。
- 多选「布局 + 其下全部子控件」(只选布局也会自动带上直接下属)→ 另存为用户组件。
- 拖出的实例:外层仍可再进窗体级布局;内部按你另存时的停靠与布局占比伸缩。
要点:
- 内部根布局建议 停靠=填充,否则宿主变大时布局块不会跟着铺满。
- 子控件之间用水平/垂直布局 + 弹簧,比绝对坐标更耐缩放。
- 旧版
.yuc若没有dock/layoutStretch字段,需重新另存一次才会带上布局信息。
3. 属性怎么写
3.1 三种等价路径
| 写法 | 何时用 |
|---|---|
实例.导出名 = 值 |
推荐:导出名已映射到默认属性 |
实例.导出名.属性 / 实例.导出名.方法() |
要对「导出所指向的那个子控件」访问其它属性/方法 |
实例__子名.属性 |
调试或未导出的内部控件 |
文本框类导出的默认属性是 「文本」(不是「标题」)。若写 搜索框1.搜索框.标题 会报错;应写 搜索框1.搜索框 或 搜索框1.搜索框.文本。
3.2 补全
在 实例. 后,补全会列出该定义的 exports 与 eventExports。再写一层 . 时,若前一段是导出名或定义内子名,会按该子控件类型补属性/方法。
3.3 运行时按名赋值(动态实例)
预放在窗体上的实例可直接 实例.导出名 = …。
克隆用户组件 得到的动态名请用:
文本 n = 克隆用户组件(联系人项1, 12, 200)
置用户组件属性(n, "标题", "新会话")
文本 t = 取用户组件属性(n, "标题")
| 命令 | 说明 |
|---|---|
克隆用户组件(模板实例, 左边, 顶边) |
整组复制,自动续号;失败返回空文本 |
克隆用户组件到(模板, 新名, 左, 顶) |
指定新宿主名;已存在则失败 |
置用户组件属性(实例名, 导出名, 值) |
按导出名写;成功返回真 |
取用户组件属性(实例名, 导出名) |
按导出名读,返回文本 |
上限约 256 个动态实例;动态实例的导出事件不会自动挂接——要写事件请继续用设计器预放槽位。
4. 事件怎么写
事件函数写在窗体对应的 .yc(主窗一般是 源代码/main.yc;次级窗是 窗口/窗名/窗名.yc)。
4.1 三种挂法
| 写法 | 含义 |
|---|---|
函数 实例.导出事件名 { … } |
另存时勾了 eventExports;编译期改写到真实子控件事件 |
函数 实例.导出名.子事件 { … } |
如 搜索框1.搜索按钮.单击 → 改写为 搜索框1__按钮2.单击 |
函数 实例__子名.子事件 { … } |
直接挂内部子控件(未导出也行) |
示例:
' 导出事件(.yuc 里 eventExports 有 单击 → 某子按钮.单击)
函数 搜索框1.单击 {
标签状态.标题 = "点了搜索:" + 搜索框1.搜索框
}
' 导出名.事件
函数 搜索框1.搜索按钮.单击 {
…
}
' 内部名(调试)
函数 搜索框1__按钮2.鼠标进入 {
标签状态.标题 = "进入搜索按钮"
}
4.2 形参:设计器完整 / 手写可省略
- 设计器右键控件 → 事件:始终生成完整带参桩,例如
函数 按钮1.单击(整数 X, 整数 Y) { … } - 手写:可以无参或少参,运行时照样触发:
函数 按钮1.单击 { … } - 光标在「函数 控件.事件」行按 Tab:可补齐标准形参。
测悬停时不要用信息框(会抢焦点导致立刻离开);改写标签 / 状态栏即可。
4.3 运行时命中规则(拼接组件)
- 单击等:若点在子控件上、宿主自己没写该事件,会回退到宿主上的同名处理(便于
搜索框1.单击覆盖整组)。 - 鼠标进入 / 离开:发给真实命中的子控件;进出整组边界时,宿主也可收到一份(便于整组悬停逻辑)。
- 悬停显隐自动桩(见下节)按「整组成员」合并,组内移动不会误触发离开。
4.4 事件代码放哪
| 窗 | 文件 | 工程树 |
|---|---|---|
| 主窗口 | 源代码/main.yc |
源代码 → main.yc |
| 其它窗 | 窗口/窗名/窗名.yc |
窗口 → 窗名 → 窗名.yc |
悬停显隐自动段(usercompose-behavior)也写在上表对应文件。主窗不要再维护 窗口/主窗口/主窗口.yc,否则会与 main.yc 重复定义。
5. 跨工程复用(拷贝 .yuc)
不必每个工程重新拼接。 .yuc 是自包含定义文件。
- 把源工程
组件/某某.yuc复制到目标工程根下的组件/(没有就新建该目录)。 - 目标工程须开通
component.usercompose。 - 重新打开工程或刷新后,工具箱「用户组件」出现同名项,直接拖用。
- 导出名 / 事件名与原定义一致,代码照旧写。
IDE 只扫描当前工程 组件/*.yuc,不会自动链接其它工程目录。若子树里用了旁路插件控件,目标环境也要有对应插件。
单文件样例说明见 examples/usercompose。
6. .yuc 文件格式(实现要点)
UTF-8 JSON。工程打开时 UserComposeStore 扫描 组件/*.yuc 载入;另存 / 保存定义时写回。
6.1 顶层字段
| 字段 | 类型 | 说明 |
|---|---|---|
id |
文本 | 与文件名(无扩展名)一致,如 名片行 |
title |
文本 | 工具箱显示名 |
width / height |
整数 | 宿主默认尺寸 |
allowNest |
逻辑 | 是否允许定义内再嵌套用户组件宿主 |
children |
数组 | 相对宿主 (0,0) 的子控件快照 |
exports |
数组 | 属性导出 |
eventExports |
数组 | 事件导出 |
hoverReveal |
文本数组 | 悬停才显示的子控件名 |
6.2 exports[]
{ "name": "姓名", "child": "姓名", "prop": "标题" }
{ "name": "姓名颜色", "child": "姓名", "prop": "文字色" }
{ "name": "搜索框", "child": "编辑框1", "prop": "文本" }
| 键 | 含义 |
|---|---|
name |
对外导出名 |
child |
定义内子控件名 |
prop |
映射到的属性面板标题(标题 / 文本 / 图片 / 值 / 文字色 …) |
6.3 eventExports[]
{ "name": "单击", "child": "按钮2", "event": "单击" }
{ "name": "值改变", "child": "滑块1", "event": "值改变" }
编译 / 解析时:函数 宿主.单击 → 函数 宿主__按钮2.单击。
6.4 窗体 XML 侧
实例展开后,宿主控件带旁路标记(设计器内部为 editorControl = "yuc:定义名" 一类戳,不增加 DesignControl 字段布局)。子控件以真实控件序列化进窗体 XML;若 XML 残缺,编译烘焙阶段可按 .yuc 补齐缺失子项 / 修复被压扁的几何(虚拟列表原型等场景)。
6.5 嵌套
.yuc 的 children 里也可以再带用户组件宿主(含 userCompose / yuc: 戳)。工具箱可拖已有用户组件再另存成更大块。另存时若选中容器,会自动带上其下属子控件。
7. 悬停显示与播放器音量条
7.1 原理
另存对话框 「悬停显示」 填子控件名(逗号分隔,对应定义内 name)。写入 .yuc 的 hoverReveal 后,保存窗体时 IDE 会向对应 .yc 自动生成 鼠标进入 / 鼠标离开 段(标记 usercompose-behavior):
- 鼠标进入整组任一成员 → 这些子控件
可见 = 真 - 鼠标离开整组 →
可见 = 假 - 组内在成员之间移动不会误触发离开(运行时按
yuc:单元合并)
平时请把要弹出的控件在定义里设成 可见=假(另存时勾了悬停显示的一般会处理好)。
7.2 横向:图标旁弹出滑块(演示工程)
现成样例:examples/用户组件演示/组件/音量条.yuc。
结构:底条 + 「音量」图标 + 横向滑块(默认隐藏)+ 数值标签。
hoverReveal: ["滑块"],并导出 值 / 数值文本、事件 值改变。
函数 音量条1.值改变(整数 当前值) {
音量条1.数值文本 = 到文本(当前值)
}
' 悬停显隐由 usercompose-behavior 自动写,一般不必手写
7.3 纵向:播放器那种「悬停出竖条音量」
目标效果:平时只看到喇叭图标;鼠标移上去,在图标上方弹出一根竖向滑块。
步骤:
- 设计器摆控件(建议先在空白处拼,再另存):
- 图标:标签或图片框(如「🔊」),靠下。
- 滑块:放在图标上方;设 宽度约 24、高度约 100~120(高度 > 宽度 才会自动纵向)。
- 滑块属性:最小值 0、最大值 100、值 50;平时 可见 = 假。 - 多选图标 + 滑块(可再加底框)→ 右键 另存为用户组件…
- 名称如竖向音量
- 导出:滑块 → 对外名值(属性「值」)
- 导出事件:可选「值改变」
- 悬停显示 填:滑块(定义里滑块的名字) - 宿主高度要够装下「竖滑块 + 图标」。例如宽 40、高 140;图标在底部,滑块在上方。
- 工具箱拖实例后写:
函数 竖向音量1.值改变(整数 当前值) {
' 接到播放器音量等
置状态("音量 " + 到文本(当前值))
}
纵向规则: 滑块控件 高度大于宽度 时按竖条绘制与拖动(底端=最小值,顶端=最大值)。宽高对调即回横向。
布局示意(定义内相对坐标):
┌ 宿主 40×140 ──┐
│ 滑块 24×110 │ ← hoverReveal,平时隐藏;高>宽=纵向
│ (居中偏上) │
│ 图标 36×28 │ ← 始终可见
└────────────────┘
7.4 单根滑块「悬停展开」(不拼用户组件)
只想一根轨道平时收成细线、悬停才展开圆点:选中滑块 → 属性 悬停展开 = 真。
这是单控件行为,不写 .yuc;适合内嵌在进度条旁的细音量条。要「图标 + 弹出整根竖条」请用 §7.3。
7.5 与滑块文档
属性 / 事件表见 yss/slider.md。完整花样见 用户组件演示。
8. 实例上用 YSS
实例展开后子控件是真实控件,用名称选择器即可:
label#名片行1__姓名 {
color: #FFFFFF;
font-size: 14px;
}
picturebox#名片行1__头像 {
border-radius: 20px;
}
导出「姓名颜色」等后,也可:名片行1.姓名颜色 = "#FFCC00"。
9. 列表场景
9.1 推荐:虚拟列表(旁路,component.itemlist)
数据可上千行,屏幕上只回收少量行模板;行长相仍用本页的 .yuc。支持纵向 / 横向 / 网格 / 可变项高。
列表绑定原型(会话列表1, 会话行原型)
列表清空(会话列表1)
整数 i = 列表添加项(会话列表1)
列表置项属性(会话列表1, i, "标题", "新会话")
见 虚拟列表演示。
9.2 旧做法
- 预放槽位:多个同定义实例,按行赋值;多余槽隐藏。
- 克隆用户组件:控件数随行数涨(见 §3.3);导出事件不自动挂。
10. 授权与门控
- 设计器另存、工具箱拖入、工程含用户组件宿主时,均需票内
component.usercompose。 *通配不能覆盖该位。- 虚拟列表另需
component.itemlist。
11. 易错对照
| 现象 / 写法 | 正确做法 |
|---|---|
组件名带 +(如 Y++搜索) |
另存名只用中文/字母/数字/_;+ 会被当成运算符拆开 |
搜索框1.搜索框.标题 |
文本框用 .文本,或只写导出名赋值 |
| 只改了运行时库、事件仍无效 | 用户程序是静态链接:须重新编译你的工程 |
| 信息框测「鼠标进入」立刻离开 | 改用标签/状态栏显示 |
主窗事件写在 窗口/主窗口/主窗口.yc |
写到 源代码/main.yc |
拷了 .yuc 工具箱没有 |
确认路径是工程根 组件/,已开通能力,并重新打开工程 |
动态克隆实例写 函数 xxx.单击 |
导出事件不自动挂;用预放槽位写事件 |
以为用户组件是新 DesignControlType |
宿主是分组框 + yuc: 旁路;见 §1 |
12. 示例与相关
| 资源 | 内容 |
|---|---|
| 用户组件入门 | 名片行 + 指标卡,导出名写测试数据(推荐先看) |
| 用户组件演示 | 悬停显隐、设置行、克隆 API、SQLite 槽位 |
| 联系人项.yuc 单文件 | 拷到任意工程 组件/ 即用 |
| 虚拟列表演示 | .yuc 行模板 + 回收池 |
相关文档:工程结构 · 窗口 XML · YSS · 常见任务写法 · 语言基础 · 自定义类(代码向 OOP,与可视化拼接不同)