wpf-offscreen-render · diff
git:20261024.538af72 to git:20261024.2bed752
42 added, 28 removed. Audit A to A.
---
name: wpf-offscreen-render
- description: 在独立临时进程中把真实 WPF UserControl、Page、Window 或松散 XAML 离屏渲染成 PNG,同时输出绑定错误、布局问题、实际尺寸、DPI 等结构化摘要,用于修改 WPF 界面后验证布局、主题、数据绑定和视觉回归;不启动业务程序、不抢焦点,支持 .NET Framework 4.7.2–4.8.1 与 .NET 5–10。用户要截图、预览界面、看一眼 XAML、离屏截图、查看 WPF 界面效果、检查绑定错误,或对比改动前后界面时使用(render, screenshot or preview a WPF view or XAML offscreen; RenderTargetBitmap; WPF visual regression)。不适用于 WinUI、UWP、Avalonia、MAUI、WinForms。
+ description: 在独立临时进程中把真实 WPF UserControl、Page、Window 或松散 XAML 离屏渲染成 PNG,同时返回绑定错误、布局问题、实际尺寸与 DPI 摘要,用于界面预览、布局 / 主题 / 数据绑定检查和视觉回归。用户要渲染、截图、预览 WPF / XAML 界面,或对比改动前后效果时使用(render, screenshot, preview, WPF visual regression)。不适用于 WinUI、UWP、Avalonia、MAUI、WinForms。
---
# WPF 离屏渲染
- 在独立临时进程中创建 WPF Visual,通过 `RenderTargetBitmap` 输出 PNG;使用真实控件、真实样式和确定的样例数据,不依赖 Gallery 或生产程序的启动入口。渲染需要 Windows;云端 Linux 容器中的会话无法使用。
+ 在独立临时进程中创建 WPF Visual,通过 `RenderTargetBitmap` 输出 PNG;使用真实控件、真实样式和确定的样例数据,不启动业务程序。渲染需要 Windows;`windowHost` 还需要交互式桌面会话。
## 仓库根
- 后续「仓库根」指 **wpf-offscreen-render 工具仓库根目录**,必须**同时**存在 `scripts/render.ps1` 与 `mcp/start.ps1`。脚本、文档、MCP `--root` 都用这个目录的**绝对路径**。
+ 「仓库根」是 **wpf-offscreen-render 工具仓库根目录**,必须同时存在:
- **禁止**按「此 SKILL.md 上两级」推断。skill 装在插件内时碰巧是上两级;复制到 `.agents/skills/` 后上两级是工作区,不是本工具。
+ - `scripts/render.ps1`
+ - `mcp/start.ps1`
+ - `references/cli.md`、`references/integration.md`、`references/steps.md`、`references/verification.md`
- 按顺序取第一个通过校验的路径,未通过则试下一个:
+ 按顺序取第一个通过全部校验的路径:
- 1. 环境变量 `WPF_RENDER_ROOT`(MCP 启动器也会设 `--root`,与此同源)。
- 2. 下一行写死的绝对路径(复制或安装 skill 时填写;未填则跳过):
- `仓库根:C:\Users\yu\codes\tooling\wpf-offscreen-render`
- 3. 当前工作区里名为 `wpf-offscreen-render` 且含上述两个文件的目录(monorepo 常见于 `tooling/wpf-offscreen-render`)。
- 4. 仅当本 `SKILL.md` 仍位于工具仓库的 `skills/wpf-offscreen-render/` 下时,用其祖先目录,**必须校验那两个文件**,不得按固定级数。
+ 1. 环境变量 `WPF_RENDER_ROOT`。
+ 2. 若本 `SKILL.md` 位于工具仓库的 `skills/wpf-offscreen-render/` 下,沿其祖先目录向上查找。
- 都找不到则停止并告诉用户,不要猜路径、不要调用脚本。文档在 `{仓库根}/references/`(`cli.md`、`integration.md`、`steps.md`、`verification.md`),不要用相对于本 SKILL.md 的 `../../references/`。
+ Skill 被单独复制到其他目录时必须设置 `WPF_RENDER_ROOT`;不要按固定层数、当前工作区名称或模糊搜索猜路径。找不到时停止并告诉用户,不要调用脚本。MCP 启动器会通过 `--root` 把同一仓库根传给服务器。
- **路径一律传绝对路径**:Codex 等客户端以插件目录为工作目录启动 MCP 服务器,相对路径会被拒绝;`preset` 需要同时传 `config`(业务项目根目录的绝对路径)。此 skill 不授予启动生产程序、连接设备或 Git 操作的权限。
+ 脚本、文档和 MCP 参数一律使用仓库根的绝对路径;`preset` 还需要业务项目根目录的绝对 `config`。
- ## 决策(10 秒)
+ ## 工具路由
- 按顺序回答,选第一条成立的路。非 Windows / 找不到仓库根:停止。
+ | 需求 | MCP 工具 | 脚本 / 限制 |
+ | --- | --- | --- |
+ | 读取业务预设 | `list_views` | 需要业务根的绝对 `config` |
+ | 按预设或单次渲染 | `render_view` | 支持 `config`、`preset`、`factoryFile`、`steps`;不传视图参数会渲染内置样例 |
+ | 直接程序集的多主题 / 尺寸 / DPI | `render_matrix` | 不支持 `config`、`preset`、`factoryFile`、`steps`;预设多组合使用 `render_view` 或 `render.ps1` |
+ | 前后对比 | `compare_snapshots` | 脚本为 `compare-snapshots.ps1` |
+ | 查询布局树 | `get_layout` | 读取某次渲染的 `layout.json` |
+ | 环境自检 | `self_test` | 或运行 `{仓库根}\tests\run-tests.ps1 -Quick` |
- 1. **通道**:工具列表里有 `render_view` → MCP。没有 → `{仓库根}\scripts\render.ps1` 等脚本(参数见 `{仓库根}/references/cli.md`)。禁止假装调用 MCP。
- 2. **入口**:业务根有 `wpf-render.json` → `config`(该目录绝对路径)+ `preset`(改了业务代码加 `build: true`)。没有 → 先构建业务项目,再 `assemblyPaths`(优先应用 exe 输出目录)+ `viewType` 或 `xaml`。不传视图参数会渲内置样例,**不得当业务验收**。
- 3. **模式**:默认 `layout-only`。仅当依赖 Loaded / PresentationSource / 窗口祖先 / 实际承载尺寸时用 `windowHost`。卡顿、耗时、阻塞不是本工具能证明的,另测。
- 4. **DataContext**:无参预览 VM → `dataContextType`;要构造参数或 stub → `dataContextFactory`;还要资源 / 异步就绪 / 选中态 / 清理 → `factoryFile`。字段见 `{仓库根}/references/integration.md`。
+ ## 决策
- `self_test` 只在本机没跑过、或 `failed:start` / 怀疑缺 SDK 时用(通过输出 `2 通过,0 失败`),不要每次改 UI 都跑。
+ 1. 工具列表有 `render_view` 时优先使用 MCP;没有时使用 `{仓库根}\scripts\render.ps1` 等脚本,不要假装调用 MCP。
+ 2. 业务根有 `wpf-render.json` 时先 `list_views`,再用 `render_view(config, preset)`;修改业务代码时加 `build: true`。脚本模式使用 `render.ps1 -Config <业务根> -Preset <名称>`。
+ 3. 没有预设时先构建业务项目,再传 `assemblyPaths`(优先应用 exe 输出目录)和 `viewType` 或 `xaml`。不传视图参数得到的是内置样例,不可作为业务验收。
+ 4. 默认使用 `layout-only`。只有依赖 `Loaded`、`PresentationSource`、窗口祖先、实际承载尺寸,或工厂返回 `Window` 时才用 `windowHost`;无交互桌面会话中不要选它。
+ 5. DataContext 无参预览 VM 用 `dataContextType`;需要构造参数或 stub 用 `dataContextFactory`;需要资源、异步就绪、选中态或清理用 `factoryFile`。字段见 `{仓库根}\references\integration.md`。
- MCP 工具:`list_views`、`render_view`、`render_matrix`、`compare_snapshots`、`get_layout`、`self_test`。脚本对应 `{仓库根}\scripts\render.ps1`、`compare-snapshots.ps1`、`{仓库根}\tests\run-tests.ps1 -Quick`。
+ `self_test` 用于首次安装、工具或 Windows Desktop Runtime 变化、`failed:start`,或怀疑环境异常时;不要每次改 UI 都运行。
## 最短路径
- 1. 按上面调一次渲染(MCP:`list_views` 再 `render_view`;脚本:`render.ps1 -Config <业务根> -Preset <名>`)。多主题/尺寸/DPI → `render_matrix`(或脚本一次传多个 `-Theme` / `-Size` / `-Dpi`)。只关心某控件 → `element`(`x:Name`)。
- 2. **先读 JSON 再看图**。
+ 1. 渲染一次:MCP 按上面的路由调用;脚本用 `render.ps1`。多主题 / 尺寸 / DPI 的预设用 `render_view` 或脚本预设,多组合直传参数才用 `render_matrix`。
+ 2. **先读 JSON,再检查 PNG。** 不把摘要、退出码或 manifest 当成视觉验收的替代品。
- `status`:`ok` / `binding-issues` / `failed:<阶段>`(`build` / `locate` / `start` / `render` / `timeout` / `step`)。`bindingTraceVerified` 为 false 则绑定统计不可信。`bindingIssues` 逐条定位路径。`layoutIssues`(`text-truncated` / `clipped` / `overlap` / `zero-size` / `out-of-viewport`)是线索,不影响 status,对照截图确认;坐标用 `get_layout`。失败看 `buildErrors` / `error` 与 `outDir` 下日志。排障:`{仓库根}/references/verification.md`。
- 3. 向用户报告:真实页面还是样例;模式 / 主题 / 尺寸 / DPI;`status` 与 `bindingTraceVerified`;绑定与已确认的布局问题;未覆盖(Popup / HWND / 默认主题悬停触发器 / 未 wait 的异步);`outDir` 绝对路径。临时产物不进仓库。
- 4. **强制清理**:汇报后若无需保留,立即删除本次 `outDir`(`render-*` / `matrix-*`)和一次性 harness。禁止靠 7 天 Temp 清理。用户要留图则先移到稳定目录再删其余。
+ `status`:`ok` / `binding-issues` / `failed:<阶段>`(`build` / `locate` / `start` / `render` / `timeout` / `step`)。退出码 `0` 表示通过,`2` 表示绑定问题,`1` 或 `3` 表示失败。`bindingTraceVerified` 为 false 时绑定统计不可信。`layoutIssues`(`text-truncated` / `clipped` / `overlap` / `zero-size` / `out-of-viewport`)是线索,不影响状态;对照截图确认,坐标用 `get_layout`。失败看 `buildErrors` / `error` 和 `outDir` 下日志。排障见 `{仓库根}\references\verification.md`。
+ 报告真实页面还是样例、模式、主题、尺寸、DPI、状态、绑定监听可信度、绑定与已确认的布局问题,以及未覆盖的 Popup / HWND / 默认主题悬停触发器 / 未等待的异步。
+
## 需要时再走
- - **交互**(展开、输入、点击、Tab、动画):`steps`;不能与多主题/尺寸/DPI 同时用。用 `waitFor`,不要只靠 `waitMs`。默认主题 `IsMouseOver` / `IsPressed` 离屏截不到,`state` 会报错。只打样例 VM / stub。写法:`{仓库根}/references/steps.md`。一次性探查也可用 `{仓库根}\scripts\new-wpf-harness.ps1` + `invoke-wpf-render.ps1 -HarnessDir`。异步在 `ViewFactory.WaitUntilReadyAsync` 等明确完成信号。
- - **改动对比**:改前渲一次当基线,改后 `compare_snapshots`(两次 `outDir`)。看 `changedBoundsDip` 是否只在预期区域、`environmentDifferences` 是否为空。用户要留基线则另选稳定目录,先留旧再写新。
- - **坐标**:`layout.json`、`elementRectDip`、`compare_snapshots` 都是截图 DIP。`background` 默认 `auto`(衬底,不代表业务背景)。超时只作用于本工具拉起的渲染进程(默认 120 秒)。
+
+ - **交互**:用 `steps` 验证展开、输入、点击、Tab、VisualState 和动画。步骤不能与多个主题 / 尺寸 / DPI 同时使用;用 `waitFor` 等明确完成信号,不要只靠 `waitMs`。默认主题 `IsMouseOver` / `IsPressed` 触发器离屏截不到,`state` 会明确报错。动作会运行真实业务代码,只对样例 VM / stub 服务执行。写法见 `{仓库根}\references\steps.md`。
+ - **改动对比**:改前后保持机器、字体、资源、样例数据、尺寸、主题、DPI 和动画状态一致,再用 `compare_snapshots`。查看 `changedBoundsDip`、`environmentDifferences` 和差异图。
+ - **一次性探查**:用 `{仓库根}\scripts\new-wpf-harness.ps1` 生成工程,再用 `invoke-wpf-render.ps1 -HarnessDir` 渲染;异步页面在 `ViewFactory.WaitUntilReadyAsync` 等明确完成信号。
+
+ ## 产物生命周期
+
+ 1. 先读取 JSON、日志并实际查看 PNG。
+ 2. 用户要保留时,先把快照或基线移动到稳定目录,再清理其余临时文件并报告稳定路径。
+ 3. 用户不需要时,在最终回复前删除本次 `render-*` / `matrix-*` `outDir` 和一次性 harness;不要把已删除的临时路径当作交付物。