11 KiB
Go Gio Windows 桌面适配指南
目录
- 版本与适用边界
- 即时模式架构与状态
- Windows 视觉与布局
- 核心交互模式映射
- 文件、异步与长任务
- 键盘、焦点与无障碍
- 构建、测试与交付
- 常见反模式
- 官方资料
1. 版本与适用边界
生成代码前读取 go.mod、go.sum、入口文件和实际使用的 gioui.org 子包,确认 Go、Gio 与扩展包的准确版本。Gio 模块仍是 v1 之前版本,次版本可能包含不兼容变化;应固定已验证版本,并以该版本的包文档和示例为准。
Gio 适合需要 Go 单一技术栈、自绘界面、跨平台渲染和高度定制交互的桌面应用。它不会自动提供完整的 Windows Fluent 控件集,也没有与 WinUI DataGrid、TabView、ContentDialog 或 Mica 一一对应的内置组件。选择 Gio 意味着项目需要明确承担复合控件的状态、输入、焦点、语义、主题和测试成本。
2. 即时模式架构与状态
Gio 使用即时模式:应用读取窗口事件、更新持久状态,再在每个 FrameEvent 中布局并绘制当前帧。不能依赖上一帧留下的控件对象树。
widget.Clickable、widget.Editor、列表位置、标签选择、筛选值和弹层状态等必须由页面或组件结构体长期持有,不能在每一帧的布局函数中重新创建。- 领域数据与 UI 交互状态分离。记录以稳定 ID 识别,不以可见标题、排序后的行号或循环索引作为业务身份。
- 布局函数应接近纯函数:读取状态、消费本帧输入、产生操作;文件、网络、数据库和昂贵计算放在布局循环之外。
- 将导入、刷新、生成、更新、提交等动作建模为集中命令,统一管理启用条件、执行中、防重复、取消、结果和错误,不要只散落在
Clicked()分支中。 - 通过显式页面状态描述空闲、加载、内容、空、筛选后为空、错误、离线/过期、权限拒绝和部分结果。
窗口事件循环必须处理 FrameEvent 与 DestroyEvent,并保证每个帧事件都完成提交。后台结果到达时更新线程安全的应用状态并调用 Window.Invalidate() 请求重绘;不要用忙循环持续刷新。
3. Windows 视觉与布局
widget/material 提供 Material Design 主题和样式,它不是 Windows Fluent 设计系统。Windows 项目应在 Gio 上建立一层薄的设计适配:集中定义语义颜色、字体、字号、间距、圆角、描边、阴影、状态层、图标和动效,再用这些 Token 组合组件。不要在各页面直接散布颜色和尺寸常量。
- Windows 字体优先使用系统可用的 Segoe UI Variable/Segoe UI,并准备缺失时的回退;验证中文字体与混排。
- 保留系统装饰窗口和标准关闭、最大化、最小化、调整大小行为。不要仅为“像 WinUI”而自制整套非客户区。
- 不宣称 Gio 内置原生 Mica/Acrylic。只有在隔离的 Windows 原生适配经过版本、性能、高对比度和失效测试后才启用,并始终提供纯色主题表面回退。
- 以
layout.Context的Constraints决定紧凑、中等和宽布局,用layout.Flex、layout.Stack、layout.Inset和layout.List组合,不按全屏分辨率或固定像素坐标布局。 - 在断点切换时保留输入、焦点、选择、勾选、筛选、滚动、展开、任务和未保存状态。
主题至少覆盖浅色、深色、高对比度或可访问的强对比回退,并验证系统强调色不可用、透明效果关闭和远程桌面等场景。颜色不能成为错误、选择或状态的唯一线索。
4. 核心交互模式映射
导航与标签
Gio 没有内置完整 NavigationView/TabView 体验。用稳定页面 ID 维护顶级导航,用稳定标签 ID 维护文档工作区,并自行定义:键盘切换、焦点返回、关闭、重排、溢出、未保存确认、任务归属和状态恢复。标签标题只是显示文本,不能作为身份。
数据表格
大型表格使用 layout.List 或经过测量的自定义虚拟化行布局,只绘制可见区域,避免为每个单元格长期持有昂贵对象。列宽、表头、滚动、固定列和编辑行为需形成单独组件契约。
必须分别建模:
- 当前行/单元格;
- 键盘焦点;
- 选择集;
- 勾选集。
指针命中后先解析稳定记录 ID,再处理单击选择、双击默认操作、右键上下文目标、复选框和行内按钮。双击不能额外执行一次单击的破坏性动作;复选框或行内按钮消费事件后不能继续触发行默认操作。Ctrl/Shift 多选、Space 切换、Enter 调用、上下文菜单键或 Shift+F10 都要有明确规则。排序和筛选后按 ID 恢复有效状态,并向用户说明被筛选隐藏的勾选项。
表单
用持久的 widget.Editor、布尔/枚举选择状态和应用层字段模型构造表单。每个字段定义标签、说明、必填、格式、领域规则、验证时机、错误位置和提交模式。提交失败保留输入并将焦点移到首个错误;异步验证以请求 ID 或取消机制忽略旧结果。占位文本不能代替字段标签。
对话框、反馈与任务
Gio 中的对话框、浮层、信息条通常需要应用自行组合。模态层必须限制背景交互、建立正确绘制层级、将焦点移入、支持 Escape/关闭规则,并在关闭后将焦点返回触发点。只有不可逆、高代价或没有安全默认值的决定才阻塞确认。
普通成功使用内联状态或非阻塞通知;可恢复错误保留上下文并提供重试;长任务在页面内显示阶段、进度、取消和结果。不要为每次生成、更新或提交统一弹出成功对话框。
多 Tab 数据工作台
所有工作标签复用同一页面骨架:标题与说明、导入/配置/刷新等顶部前置命令、来源与校验状态、可伸展表格主区、选择摘要,以及生成/更新/提交等底部完成命令。每个命令显式绑定作用域、前置条件、风险、可逆性、确认策略和完成反馈。
5. 文件、异步与长任务
文件选择可使用 gioui.org/x/explorer,但必须遵循其窗口生命周期:每个窗口创建对应 Explorer,将窗口事件传给 ListenEvents,并从独立 goroutine 调用阻塞的 ChooseFile、ChooseFiles 或 CreateFile。返回句柄按 io.Reader、io.Writer 或相关接口使用,不要假设一定是 *os.File 或具有普通文件路径。
- goroutine 不直接无同步地修改布局正在读取的复合状态;通过 channel、互斥保护的结果或项目统一状态容器交接。
- I/O、网络、数据库与计算任务不得阻塞窗口事件循环。
- 长任务使用
context.Context或等价取消协议,携带任务 ID,并在窗口关闭、标签关闭或新请求替代旧请求后丢弃过期结果。 - 任务提交阶段要定义幂等性;不能幂等时在进行中禁用重复命令并清楚显示原因。
- 后台状态改变后调用
Window.Invalidate(),不要依赖用户移动鼠标才看到结果。
6. 键盘、焦点与无障碍
用 key.Filter 声明需要接收的键事件,用 key.FocusCmd 请求焦点,并处理焦点进入、离开和方向移动。快捷键的作用域必须清晰;重复操作提供 Windows 常用快捷键或可发现的替代路径。所有仅右键、悬停、拖拽或触控可达的功能都需要键盘路径。
使用 io/semantic 为自定义组件公开合适的类别、标签、说明、启用、选择及其他状态。复合表格、标签、对话框和自绘输入承担比标准控件更高的语义责任;代码中存在语义操作不代表屏幕阅读器体验已正确,必须用 Windows Narrator 和无障碍检查工具执行真实任务测试。
焦点指示必须在浅色、深色与强对比背景上清晰;弹层打开和关闭、标签切换、删除当前记录、筛选后当前项消失时都要定义焦点落点。状态变化与错误需要可感知的文本和语义反馈,不能只改变颜色或播放动画。
7. 构建、测试与交付
Windows GUI 发布构建可按项目需要使用 -ldflags="-H windowsgui" 隐藏控制台,但开发构建应保留可诊断日志。锁定 Go/Gio/扩展模块版本,保存可复现构建命令,并在干净 Windows 环境验证产物、资源、字体、图标与文件选择器。
最低测试矩阵包括:最低支持 Windows 与当前 Windows 11;紧凑/中等/宽窗口、贴靠、最大化和多显示器;100%/125%/150%/200% 缩放;浅色、深色、强对比回退;键盘、鼠标及声明支持的触控;Narrator;空、真实、超大、错误、权限拒绝和慢任务数据;窗口/标签关闭、取消、重试和恢复。
交付前确认:
- 所有持久 Widget 状态未在帧布局中意外重建。
- 页面、标签和记录使用稳定 ID,而非索引或标题。
- 大型列表/表格只布局可见内容,并以真实数据测量。
- 后台任务不阻塞事件循环,状态同步无数据竞争,结果到达会触发重绘。
- 键盘、焦点、语义、对话框和上下文菜单已用真实辅助技术验证。
- Windows 视觉层使用集中 Token,且有纯色和强对比回退。
- 依赖版本、构建命令和自定义复合控件责任已记录。
8. 常见反模式
- 每帧重新创建
widget.Clickable、widget.Editor或列表状态,导致输入和焦点丢失。 - 把
widget/material的默认外观直接称为 Windows Fluent。 - 在事件循环中执行阻塞 I/O,或用持续重绘掩盖状态同步问题。
- 用排序后行号保存勾选或选择,导致筛选后作用于错误记录。
- 自绘表格、标签或弹层,却没有键盘、焦点、语义和屏幕阅读器测试。
- 假设
x/explorer返回本地路径或*os.File,或者在帧处理 goroutine 中调用其阻塞选择方法。 - 为视觉效果移除系统窗口能力,或把未经验证的 Windows 私有 API 变成必需依赖。