YStudio Y++

用户组件(可视化拼接)

Y++ / YStudio 开发文档 · 一点滴

能力: 需授权开通(内部位 component.usercompose;票内须显式开通,* 不能覆盖)。

用设计器里的现有控件拼成可复用块,另存为工程内的 .yuc 定义后,从工具箱拖到任意窗体当「普通控件」用。对外只暴露你勾选的导出名(属性 / 事件),不必在业务代码里写内部子控件名。

这不是新的内置控件类型,也不改运行时 YsCtrlKind:宿主是分组框,用旁路戳 yuc:定义名 记住属于哪个用户组件。


1. 心智模型(先读这段)

概念 含义
定义 工程 组件/<名>.yuc:子控件树 + 导出表 + 可选悬停显隐
实例 / 宿主 窗体上的整组外壳(分组框),名如 搜索框1、名片行1
子控件 定义展开后的真实控件;运行时名 = 宿主名__定义内名(如 搜索框1__按钮2)
导出名 另存时勾选的对外名字,如 搜索按钮、姓名;代码优先写这个
导出事件 把「子控件某事件」映射成 宿主.对外事件名

推荐写法:

搜索框1.搜索按钮 = "搜索"           // 导出名 → 已映射的默认属性(按钮=标题,文本框=文本)
名片行1.姓名 = "小明"
名片行1.姓名颜色 = "#FFFFFF"        // 导出的样式属性

也可以(进阶):

搜索框1.搜索按钮.标题 = "搜索"      // 导出名当子控件别名,再点属性/方法
搜索框1.按钮2.可见 = 假             // 定义内子名(未导出也可)
搜索框1__按钮2.标题 = "搜索"        // 内部全名(一般不必手写)

2. 从零做一块(设计器)

  1. 在窗体上用基础控件拼好一块(标签、文本框、按钮、图片框……)。
  2. 多选这些控件 → 右键 另存为用户组件…。
  3. 对话框里:
    - 起名(会变成 组件/<名>.yuc 的文件名 / id;勿用 + 等非法标识符字符)
    - 勾选要导出的属性(每个子控件可填对外名;空=不导出)
    - 可选:文字色 等样式导出(如 姓名颜色)
    - 可选:悬停显示(平时隐藏、鼠标进入整组才显示的子控件名,逗号分隔)
  4. 确定后:
    - 工程树出现 📂 组件(可双击打开 .yuc JSON)
    - 工具箱出现分组 用户组件
  5. 从工具箱再拖一个实例到窗体;子控件自动改名为 实例__定义内名。

整组操作: 点选宿主或任意下属 → 整组选中(尺寸手柄只在外层宿主)。移动 / 复制 / 粘贴 / 删除按整组。整组已选时再点子控件 → 单独选中该子项(可改子属性)。粘贴后自动续号(名片行2…)。

样式:在编辑定义前或展开后点子控件,用属性面板 / YSS 设字体、颜色(与普通控件相同)。实例侧主要改「显示什么」;导出了样式属性后也可在代码里改。

2.1 拼接时就排好内部布局(推荐)

可以在另存之前用水平/垂直布局 + 停靠把内部排好,定义会写进 .yuc,实例宿主被拉大时内部会按布局重排。

典型做法:

  1. 在窗体空白处放一个 垂直布局(或水平布局),设停靠=填充(或稍后挂到分组框内再填满)。
  2. 把标签、按钮、弹簧等放进该布局(所属容器=布局名),需要伸缩的设拉伸/弹簧。
  3. 多选「布局 + 其下全部子控件」(只选布局也会自动带上直接下属)→ 另存为用户组件。
  4. 拖出的实例:外层仍可再进窗体级布局;内部按你另存时的停靠与布局占比伸缩。

要点:

  • 内部根布局建议 停靠=填充,否则宿主变大时布局块不会跟着铺满。
  • 子控件之间用水平/垂直布局 + 弹簧,比绝对坐标更耐缩放。
  • 旧版 .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 是自包含定义文件。

  1. 把源工程 组件/某某.yuc 复制到目标工程根下的 组件/(没有就新建该目录)。
  2. 目标工程须开通 component.usercompose。
  3. 重新打开工程或刷新后,工具箱「用户组件」出现同名项,直接拖用。
  4. 导出名 / 事件名与原定义一致,代码照旧写。

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 纵向:播放器那种「悬停出竖条音量」

目标效果:平时只看到喇叭图标;鼠标移上去,在图标上方弹出一根竖向滑块。

步骤:

  1. 设计器摆控件(建议先在空白处拼,再另存):
    - 图标:标签或图片框(如「🔊」),靠下。
    - 滑块:放在图标上方;设 宽度约 24、高度约 100~120(高度 > 宽度 才会自动纵向)。
    - 滑块属性:最小值 0、最大值 100、值 50;平时 可见 = 假。
  2. 多选图标 + 滑块(可再加底框)→ 右键 另存为用户组件…
    - 名称如 竖向音量
    - 导出:滑块 → 对外名 值(属性「值」)
    - 导出事件:可选「值改变」
    - 悬停显示 填:滑块(定义里滑块的名字)
  3. 宿主高度要够装下「竖滑块 + 图标」。例如宽 40、高 140;图标在底部,滑块在上方。
  4. 工具箱拖实例后写:
函数 竖向音量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 旧做法

  1. 预放槽位:多个同定义实例,按行赋值;多余槽隐藏。
  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,与可视化拼接不同)