YStudio Y++

常见任务写法

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

按任务抄完整可运行片段;签名细节以各专题文档为准。入门先看 最小窗口程序、语言基础。

硬约束

  1. 控件名来自窗口 XML 的 名称;事件形参以该控件文档为准。
  2. 属性用 =,方法必须 ();逻辑用 真 / 假。
  3. 数组写 整数 arr[] / 文本 list[];循环计数变量须外部先声明。
  4. 类名写全:HTTP类、JSON类、绘制类、TCP类、WebSocket类、SQLite类、Excel类、Word类、打印类。
  5. 多窗口:载入(窗名) / 关闭(窗名) 无引号。
  6. 网络回调用 &函数名 注册;形参以该类「事件」表为准,禁止自行删改。
  7. 窗口随大小变化:用停靠或水平/垂直布局,见 窗口布局.md;不要用 YSS 改坐标。

1. 读控件、写控件

假定 XML 中有 标签1、文本框1、按钮1、组合框1、复选框1。

函数 主窗口.创建完毕() {
    标签1.标题 = "就绪"
    文本框1.文本 = ""
    组合框1.清空()
    组合框1.加入项目("选项A")
    组合框1.加入项目("选项B")
    组合框1.置当前索引(0)
    复选框1.选中 = 假
}

函数 按钮1.单击(整数 X, 整数 Y) {
    文本 t = 文本框1.文本
    整数 i = 组合框1.取当前索引()
    文本 项 = 组合框1.取项目文本(i)
    如果 (复选框1.选中) {
        标签1.标题 = t + " / " + 项
    } 否则 {
        标签1.标题 = "未勾选"
    }
}

对照:label · textbox · button · combobox · checkbox


2. 分支、循环、遍历数组

整数 i = 0
循环 (5, i) {
    输出(i)                    // 0…4
}

整数 n = 0
循环 (1, 10, 2, n) {
    输出(n)                    // 1,3,5,7,9
}

整数 k = 0
当 (k < 3) {
    k = k + 1
    输出(k)
}

整数 arr[] = [10, 20, 30]
整数 项 = 0
遍历 (项, arr) {
    输出(项)
}

文本 list[] = ["甲", "乙"]
文本 行 = ""
遍历 (行, list) {
    输出(行)
}

// 字典:默认保序(写入顺序);可选 正序 / 倒序 按键排序
通用 配置 = 字典_批量置(["端口", "8080", "名称", "服务"])
通用 值 = 0
文本 键 = ""
遍历 (值, 配置) {              // 保序:端口 → 名称
    输出(值)
}
遍历 (键, 值, 配置, 正序) {    // 按键升序
    输出(键, "=", 值)
}

对照:basic-flow · basic-dict · basic-array · language §4.1

接收「整数数组 / 文本数组」返回值(左侧元素类型须与右侧一致;函数形参见 language.md §5.1):

整数 索引[] = 列表框1.取选中索引()     // 元素为整数的数组
文本 名列表[] = 取进程列表()           // 以各命令签名为准
列表框1.清空()                         // 列表框 / 组合框均有 清空()

3. 读写文本文件

函数 按钮保存.单击(整数 X, 整数 Y) {
    如果 (写文件("note.txt", 文本框1.文本)) {
        信息框("已保存")
    } 否则 {
        信息框("保存失败")
    }
}

函数 按钮打开.单击(整数 X, 整数 Y) {
    如果 (文件存在("note.txt")) {
        文本框1.文本 = 读文件("note.txt")
    } 否则 {
        信息框("文件不存在")
    }
}

对照:basic-file。二进制用 读入文件 / 写出文件 等字节集命令。


4. HTTP + JSON

函数 按钮拉取.单击(整数 X, 整数 Y) {
    HTTP类 http
    http.置超时(15000)          // 毫秒
    文本 正文 = http.提交("GET", "https://example.com/api/info")
    如果 (http.取状态码() < 200 || http.取状态码() >= 300) {
        标签1.标题 = "请求失败:" + http.取错误()
        返回()
    }
    JSON类 j
    如果 (j.解析(正文) == 假) {
        标签1.标题 = "JSON 失败:" + j.取错误()
        返回()
    }
    标签1.标题 = j.取文本("message", "")
}

POST 带正文:

HTTP类 http
http.置超时(15000)
文本 体 = "{\"name\":\"Y++\"}"
文本 回 = http.提交("POST", "https://example.com/api", 体)
如果 (回 == "") {
    标签1.标题 = http.取错误()
}

对照:http · json

超时、失败重试、且不卡住窗口:见 examples/http-retry。

HTTP类 只做客户端(请求别人)。本机 HTTP 听端口请用 HTTP服务端类;原始字节流 / WebSocket 仍用下面的 TCP / WebSocket。


5. 多窗口

函数 按钮打开工作台.单击(整数 X, 整数 Y) {
    载入(工作台)                 // 窗口名无引号
}

函数 按钮关闭登录.单击(整数 X, 整数 Y) {
    关闭(登录窗)
}

// 在「工作台」窗口的代码里访问本窗控件可直接写名称;
// 访问其它窗:工作台.标签1.标题 = "来自别处"

对照:多窗口设计


6. 列表框 / 菜单

函数 主窗口.创建完毕() {
    列表框1.清空()
    列表框1.加入项目("一行")
    列表框1.加入项目("二行")
    列表框1.置当前索引(0)

    菜单栏1.清空()
    菜单栏1.加入主菜单("文件")
    菜单栏1.加入子菜单(0, "打开")
    菜单栏1.加入分割线(0)
}

函数 列表框1.项被单击(整数 当前索引, 文本 项目) {
    标签1.标题 = 项目
}

函数 菜单栏1.项被单击(整数 索引, 文本 项目文本) {
    标签1.标题 = 项目文本
}

对照:listbox · menubar


7. 定时器

定时器类 t

函数 滴答() {
    输出("tick")
}

函数 主窗口.创建完毕() {
    整数 id = t.启动(1000, &滴答)   // 毫秒;返回定时器 ID
}

函数 按钮停.单击(整数 X, 整数 Y) {
    t.停止()                        // 省略 ID=停本对象全部;详见 timer-obj
}

对照:timer-obj。子程序指针写法见 callback。


8. 绘制类叠画(任意控件上)

绘制类 g

函数 按钮1.单击(整数 X, 整数 Y) {
    g.绑组件(按钮1)              // 组件名无引号
    g.置填充色("#332D7EF7")
    g.画矩形(4, 4, 40, 20)
    按钮1.刷新()
}

对照:painter。画布场景编辑见 canvas / yss/canvas。


9. 自定义类型(.yt)

源代码/点.yt:

点 {
    整数 x = 0,
    整数 y = 0
}

main.yc:

函数 主窗口.创建完毕() {
    点 p
    p.x = 10
    p.y = 20
    点 arr[] = []
    // 按 usertype 文档追加元素或字面量初始化
    标签1.标题 = 到文本(p.x) + "," + 到文本(p.y)
}

对照:usertype · examples/usertype


10. 给窗口换肤(YSS)

  1. 读 窗口/主窗口/主窗口.xml → 记下 名称、typeId(如 button、textbox)。
  2. 在 资源/主窗口.yss 写规则(属性键必须英文):
form {
    background-color: #1E1E1E;
}
button#确定 {
    background-color: #007ACC;
    color: #FFFFFF;
    border-radius: 4;
}
button#确定:hover {
    background-color: #1C97EA;
}
textbox {
    background-color: #252526;
    color: #E0E0E0;
    border-color: #3F3F46;
}
  1. F5:资源复制到 exe 旁,启动自动加载 主窗口.yss,一般不用手写 加载YSS()。
  2. 换肤 / 子目录主题:
函数 主窗口.创建完毕() {
    加载YSS(":/yss/主题.yss")
}
  1. 查键:总览 YSS.md → 该控件「YSS 样式」分文档(如 button)。
  2. 不要用 YSS 改布局坐标;随窗拉伸用 窗口布局。表格格子配色用代码 API(见 datagrid / tablewidget)。

10.5 HTTP 服务端 Webhook(本机联调)

HTTP服务端类 srv

函数 收到请求(整数 请求ID, 文本 请求方法, 文本 路径, 文本 查询串,
            文本 请求头, 文本 正文, 文本 IP, 整数 端口) {
    标签1.标题 = 请求方法 + " " + 路径
    如果 (路径 == "/hook") {
        srv.应答JSON(请求ID, "{\"ok\":true}")
        返回()
    }
    srv.置状态码(请求ID, 404)
    srv.应答(请求ID, "not found")
}

函数 主窗口.创建完毕() {
    srv.请求到达(&收到请求)
    如果 (!srv.启动(18080, "127.0.0.1")) {
        标签1.标题 = "HTTP 启动失败:" + srv.取错误()
    }
}

// HTTPS:启动前 置TLS + 置TLS证书(PFX路径) 或 置PEM证书(证书路径, 私钥路径)
// 参数是文件路径(相对/绝对均可),不是证书名称;不必用「合并路径」
// 客户端访问自签证书时须 忽略证书(真)
函数 按钮HTTPS请求.单击(整数 X, 整数 Y) {
    HTTP类 http
    http.忽略证书(真)
    文本 回 = http.提交("GET", "https://127.0.0.1:18443/ping")
    标签1.标题 = 回
}

对照:httpserver。须在授权中开通 HTTP服务端类。证书支持 PFX/P12、PEM,以及 申请证书(Let’s Encrypt)。本机可用 PowerShell 自签 PFX(见该文档)。


11. TCP 服务端回显(本机联调)

假定窗体有 标签1。TCP 不是控件事件:先写回调函数,再用 &函数名 注册,最后 启动/连接。

回调形参必须与文档一致(勿自行删参数)。

TCP类 srv
TCP类 cli

函数 服务进入(整数 连接ID, 文本 IP, 整数 端口, 文本 IP端口) {
    标签1.标题 = "接入 " + IP端口
}

函数 服务数据(整数 连接ID, 文本 IP, 整数 端口, 文本 IP端口, 文本 数据) {
    // 服务端回发必须带 连接ID
    srv.发送(连接ID, "回:" + 数据)
}

函数 服务离开(整数 连接ID, 文本 IP, 整数 端口, 文本 IP端口) {
    标签1.标题 = "离开 id=" + 到文本(连接ID)
}

函数 主窗口.创建完毕() {
    srv.客户进入(&服务进入)
    srv.数据到达(&服务数据)
    srv.客户离开(&服务离开)
    srv.置分包模式(0)                 // 0=原始流;教学明文可先用 0
    如果 (srv.启动(19000, "127.0.0.1") == 假) {
        标签1.标题 = "监听失败:" + srv.取错误()
    }
}

函数 按钮连.单击(整数 X, 整数 Y) {
    如果 (cli.连接_同步("127.0.0.1", 19000) == 假) {
        标签1.标题 = "连接失败:" + cli.取错误()
        返回()
    }
    如果 (cli.发送("你好") <= 0) {
        标签1.标题 = "发送失败"
        返回()
    }
    文本 r = cli.接收(256)
    标签1.标题 = "收到:" + r
    cli.断开()
}

分包(推荐消息协议):双方 置分包模式(1),用 发送分包 / 事件里已是整帧。完整写法见 tcp。

11.1 大响应 / 自定义头 + 文件体

  • 发送字节集 返回成功后即可 断开(优雅半关闭,一般不必块间延时)。
  • 异步 发送文件 返回只表示已启动;立刻 断开 会取消并可能截断。要发完再断:用 发送文件同步,或等 发送完毕。
  • HTTP 静态文件(自定义头 + 文件体)同连接:
函数 应答文件(整数 连接ID, 文本 路径, 整数 长度) {
    文本 头 = "HTTP/1.1 200 OK\r\nContent-Length: " + 到文本(长度)
        + "\r\nConnection: close\r\n\r\n"
    srv.发送字节集(连接ID, 到字节集(头))
    srv.发送文件同步(连接ID, 路径)
    srv.断开(连接ID)
}

对照:tcp · callback(&函数名)


12. WebSocket 服务端回显

WebSocket类 srv
WebSocket类 cli

函数 服务进入(整数 连接ID, 文本 IP, 整数 端口, 文本 IP端口) {
    标签1.标题 = "WS 接入 " + IP端口
}

函数 服务收到(整数 连接ID, 文本 IP, 整数 端口, 文本 IP端口, 文本 数据) {
    srv.发送(连接ID, "echo:" + 数据)   // 返回逻辑
}

函数 服务离开(整数 连接ID, 文本 IP, 整数 端口, 文本 IP端口, 整数 状态码, 文本 原因) {
    标签1.标题 = "WS 离开 code=" + 到文本(状态码)
}

函数 主窗口.创建完毕() {
    srv.置路径("/chat")
    srv.置允许来源("*")
    srv.客户进入(&服务进入)
    srv.数据到达(&服务收到)
    srv.客户离开(&服务离开)
    如果 (srv.启动(18765, "127.0.0.1") == 假) {
        标签1.标题 = "WS 启动失败:" + srv.取错误()
    }
}

函数 按钮连.单击(整数 X, 整数 Y) {
    cli.置超时(3000)
    如果 (cli.连接_同步("ws://127.0.0.1:18765/chat") == 假) {
        标签1.标题 = "WS 连接失败:" + cli.取错误()
        返回()
    }
    如果 (cli.发送("ping") == 假) {
        标签1.标题 = "发送失败"
        返回()
    }
    文本 r = cli.接收()
    标签1.标题 = r
    cli.关闭()
}

对照:websocket。UDP 监听见 udp;本机进程间通信见 pipe(创建服务端 / 连接,短名建议 ASCII,如 YppDesk)。


13. SQLite 建表、插入、查询

函数 主窗口.创建完毕() {
    SQLite类 db
    如果 (db.打开("data/demo.db") == 假) {
        标签1.标题 = db.取错误()
        返回()
    }
    // 旧版列定义文本亦可用(见 sqlite 文档)
    如果 (db.表是否存在("用户") == 假) {
        db.建表("用户", "id:主键,姓名:文本,年龄:整数")
    }
    db.插入("用户", "姓名,年龄", "张三,20")
    如果 (db.查询("用户", "", "姓名,年龄", "年龄 DESC", 10)) {
        当 (db.取下一行()) {
            文本 名 = db.取文本("姓名")
            文本 龄 = db.取文本("年龄")
            标签1.标题 = 名 + " / " + 龄
        }
    }
    db.关闭()
}

对照:sqlite · MySQL 见 mysql


13.5 Excel 按表头读写

第一行当字段名,相对工程根的 data\\名单.xlsx。完整按钮代码见 examples/excel-header-map。

Excel类 book
如果 (book.打开("data\\名单.xlsx")) {
    整数 列 = 0
    整数 c
    循环 (book.取列数(), c) {
        如果 (book.取单元格(1, c + 1) == "姓名") {
            列 = c + 1
        }
    }
    标签1.标题 = book.取单元格(2, 列)
    book.关闭()
}

对照:excel


13.5a 打印:画布预览 + 实打

无内置预览窗。先 打开画布(dpi 填 0 跟机)画同一套输出,取画布图片 放到图片框;再对同一打印机 打开 实打。完整 API 与自定义设置见 print.md(旁路包 supportlib.print,需授权)。示例工程:examples/打印预览。

打印类 打
打.置字体("微软雅黑", 10)
打.置页边距(1000, 1000, 1000, 1000)

函数 画一页() {
    打.开始页()
    打.输出文本行("对账单", 14)
    打.输出表格(0, 打.取游标Y(), "品名" + 字符(9) + "数量" + 字符(10) + "苹果" + 字符(9) + "2", 2500)
    打.结束页()
}

如果 (打.打开画布("", 0)) {
    画一页()
    图片框1.图片 = 打.取画布图片()
    打.关闭()
}
如果 (打.打开()) {
    画一页()
    打.关闭()
}

对照:print


13.5b Word 套打 / 加密文档

模板占位符用智能替换(可跨 Word 拆开的 run)。加密 docx 与 Excel 相同,传密码即可。完整 API 见 word.md(旁路包 supportlib.word,免费)。

Word类 doc
如果 (!doc.打开(合并路径(取程序目录(), "合同模板.docx"))) {
    信息框(doc.取错误(), 0)
    返回
}
doc.批量替换("{{姓名}}=张三" + 字符(10) + "{{金额}}=12800")
doc.另存为(合并路径(取程序目录(), "合同_张三.docx"))
doc.关闭()

' 加密文档
Word类 sec
如果 (sec.打开("密件.docx", "我的密码")) {
    sec.替换("{{姓名}}", "张三")
    sec.另存为("密件_out.docx", "我的密码")
    sec.关闭()
}

对照:word


13.5b 滚动区域(长设置页)

需授权开通。完整说明见 yss/scrollarea。

设计器:工具箱「布局 → 滚动区域」→ 子控件「所属容器」填滚动区域名。属性面板选滚动条策略(自动/始终/隐藏)、底部留白、平滑滚动。

函数 主窗口.创建完毕() {
    滚动区域1.垂直滚动条 = 0
    滚动区域1.水平滚动条 = 0
    滚动区域1.底部留白 = 1
    滚动区域1.平滑滚动 = 真
    滚动区域1.刷新()
    滚动区域1.滚到(0, 0)
}

函数 滚动区域1.滚动(整数 当前值) {
    输出("垂直=" + 到文本(当前值))
}

函数 按钮到底.单击(整数 X, 整数 Y) {
    整数 h = 滚动区域1.内容高度 - 滚动区域1.可视高度
    如果 (h < 0) {
        h = 0
    }
    滚动区域1.置滚动位置(h)
}

13.5c 用户组件(可视化拼接)

需授权开通。完整说明见 usercompose。

做定义: 多选控件 → 右键「另存为用户组件」→ 组件/<名>.yuc → 工具箱「用户组件」拖入。
跨工程: 拷贝 .yuc 到目标工程 组件/ 即可,不必重新拼接。
属性: 优先 实例.导出名 = 值;也可 实例.导出名.属性 / 实例__子名.属性。
事件: 函数 实例.导出事件 / 实例.导出名.单击 / 实例__子.单击;设计器进入生成完整带参桩,手写可无参。

' 假定窗体上有用户组件实例「联系人项1」,已导出 标题/预览/时间
函数 主窗口.创建完毕() {
    联系人项1.标题 = "Y++ YStudio学习交流"
    联系人项1.预览 = "一点滴:[动画表情]"
    联系人项1.时间 = "08:28"
}

函数 联系人项1.单击 {
    输出("点了整组")
}

' 动态实例(导出事件不自动挂)
文本 n = 克隆用户组件(联系人项1, 12, 200)
置用户组件属性(n, "标题", "新会话")

悬停出音量条: 另存时「悬停显示」填滑块名;竖条把滑块做成高>宽放图标上方。详见 usercompose §7。
内部布局: 另存前用水平/垂直布局+停靠排好(根布局建议停靠=填充),会写入 .yuc;宿主拉大时内部跟着排。见 usercompose §2.1。

样例:用户组件入门 · 用户组件演示·音量条 · 单文件联系人项


13.6 进程类:中文不乱码

管道按 UTF-8 读。完整对照见 examples/process-encoding。

进程类 p
如果 (p.启动("cmd.exe", "/c chcp 65001>nul & echo 你好")) {
    文本框1.内容 = p.读全部(8000)
}

13.7 TLS 证书开关

自签优先 置TLS根证书;置TLS验证(假) 只给本机联调。见 examples/tls-verify。

TCP类 cli
cli.置TLS(真)
cli.置TLS根证书("ca.cer")
cli.置TLS主机名("api.internal.local")
cli.连接_同步("192.168.1.10", 443)

13.8 表格(控件名 表格1)

设计器里的 表格,不是「数据表格」。名单、订单这类一行一条记录用它。批量填充用 导入文件 / 导入行数据 / 虚拟加载(数据表格没有这些方法)。

函数 主窗口.创建完毕() {
    表格1.添加列("姓名", 120)
    表格1.添加列("手机", 140)
    表格1.置单元格(0, 0, "张三")
    表格1.置单元格(0, 1, "13800000000")
}

函数 按钮导入.单击(整数 X, 整数 Y) {
    表格1.导入文件("data.txt", "----")
}

函数 表格1.单元格被单击(整数 行, 整数 列, 文本 区域) {
    标签1.标题 = 区域 + " = " + 表格1.取单元格(行, 列)
}

对照:表格


13.9 数据表格(控件名 数据表格1)

设计器里的 数据表格。没有 导入文件 / 虚拟加载。少格用 置单元格;一块区域用 置区域文本;整行用 置行数据。要列字母 A/B/C 与行号时再 置工作表模式(真)。

函数 主窗口.创建完毕() {
    数据表格1.置列数(3)
    数据表格1.置行数(10)
    数据表格1.置列头(0, "姓名")
    数据表格1.置列头(1, "分数")
    数据表格1.置单元格(0, 0, "张三")
    数据表格1.置单元格(0, 1, "95")
    数据表格1.置单元格(1, 0, "李四")
    数据表格1.置单元格(1, 1, "88")
}

函数 数据表格1.单元格被单击(整数 行, 整数 列, 文本 区域) {
    标签1.标题 = 区域 + " = " + 数据表格1.取单元格(行, 列)
}

对照:数据表格


13.10 选择颜色对话框

返回整数;取消是 -1。颜色选择器用 置颜色_eEx;其它控件 背景色 用 颜色值_到文本。

整数 色 = 选择颜色对话框()
如果 (色 != -1) {
    颜色选择器1.置颜色_eEx(色)
    按钮1.背景色 = 颜色值_到文本(色)
}

对照:对话框 · 颜色选择器


14. 查文档的固定顺序

  1. 打开本窗 XML → 记下控件 名称、typeId。
  2. 打开对应 yss/*.md → 抄属性 / 方法 / 事件;要换肤再看该页「YSS 样式」与 YSS.md。
  3. 用到支持库 → 打开该库文档的方法/事件表;回调形参以事件说明为准。
  4. 语法拿不准 → language / basic-flow。
  5. 禁止在签名表外发明方法名或改事件形参。

易错对照

错误 正确
文本框1.内容 多数工程用 文本框1.文本(以 textbox 属性表为准)
按钮1.被单击()(旧名) 按钮1.单击 { } 或设计器生成的 按钮1.单击(整数 X, 整数 Y)
HTTP http HTTP类 http
载入("工作台") 载入(工作台)
整数[] a 整数 a[]
遍历(整数 项, arr) 先 整数 项 = 0,再 遍历(项, arr)
字典遍历按键字母序(默认) 默认保序;字母序写 遍历(..., 正序) / 倒序
YSS 写 背景色: #fff background-color: #FFFFFF;(英文键 + #RRGGBB)
button#确定:悬停 button#确定:hover
用 YSS 精细画数据表格每一格 外壳用 YSS;格子用 置区域背景色 等
数据表格1.导入文件(...) 导入文件 只属于 表格;数据表格用 置单元格 / 置行数据 / 置区域文本
按钮1.背景色 = 选择颜色对话框() 按钮1.背景色 = 颜色值_到文本(色)(先判 != -1)
把 表格1 写成 数据表格1(或反过来) XML「名称」是哪一种控件就写哪一个;方法表不要混抄
http.置超时(15) 当秒 置超时 单位是毫秒:15000
函数 srv.数据到达(...) 当控件事件 TCP/WS:srv.数据到达(&回调),回调形参按文档
服务端 发送("hi") 不带连接ID 发送(连接ID, "hi")
TCP / WebSocket TCP类 / WebSocket类

相关

最小窗口程序 · YSS 样式 · 语言基础 · 流程控制 · TCP类 · WebSocket类 · Word类 · 打印类 · 用户组件 · 工程结构 · 窗口 XML