G
YouTube TikTok Games
Skip to content

feat: 插件可为「打开」对话框注册文件格式 (#748) - #776

Open
JSR-Bot wants to merge 1 commit into
InkCanvasForClass:net10from
JSR-Bot:bot/issue-6
Open

JSR-Bot wants to merge 1 commit into
InkCanvasForClass:net10from
JSR-Bot:bot/issue-6

Conversation

@JSR-Bot

@JSR-Bot JSR-Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #748

插件此前无法向宿主工具栏「打开」按钮的文件对话框注册自己的可打开格式,插件想打开自有格式只能另弹自己的对话框(issue 里说的「劫持对话框」),多个插件之间还会互相冲突。

根因

SymbolIconOpenStrokes_MouseUp(Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs:1362)的筛选器直接取写死的资源字符串 Main_Strokes_OpenFileDialogFilter(仅 .icstk/.xml/.zip/.uink),没有任何插件扩展点;打开后又只按扩展名分派宿主内置格式,其余一律落到 OpenSingleStrokeFile。同时未设置 FilterIndex,而「所有支持的文件」排在最后一项,默认筛选项是 *.icstk,与「默认显示全部支持格式」的预期也不一致。

改动说明

  • SDK 新增 IOpenFileFormatService(InkCanvas.PluginSdk/IOpenFileFormatService.cs):RegisterFormat(string groupName, IEnumerable<string> extensions, Func<string, bool> handler),一个插件可多次调用注册多组格式;须在 Initialize 阶段调用(与现有 RegisterService{T} 约束一致,用 _currentLoadingPlugin 识别调用方)。
  • HostApiRequirement.CurrentApiVersion 1.13.0 → 1.14.0,插件载入时按版本校验。
  • 宿主实现:
    • Ink Canvas/Plugins/PluginManager.cs:注册表 + GetOpenFormats() + RegisterOpenFormat() + TryOpenWithPluginFormat();注册通过 TrackUndo 挂到插件的 UnloadPlugin 上,卸载时自动注销;handler 抛异常或返回 false 时写日志并提示打开失败。
    • Ink Canvas/Plugins/Services/OpenFileFormatService.cs:宿主服务,仅转发到 PluginManager;App.xaml.cs 里 DI 注册。
    • MW_Save&OpenStrokes.cs:新增 BuildOpenStrokesFilter() 动态拼接筛选器(第一项「所有支持的文件」= 宿主 + 全部插件扩展名,其后为宿主各组、再后为插件各组),并设 FilterIndex = 1;打开时宿主内置扩展名(HostOpenExtensions)优先走原分支,其余扩展名先尝试插件派发,无人认领时才回落到原 OpenSingleStrokeFile。
  • 冲突规则:宿主内置扩展名(.icstk/.xml/.zip/.uink)不允许插件覆盖;多个插件注册同一扩展名时先注册者生效,后者的该扩展名被忽略并写 WARN;扩展名非法或全部被拒绝时注册返回 false 并写 WARN。
  • i18n:MainWindowStrings 三套语言资源(resx / zh-ME / en-US)新增「所有支持的文件」与「墨迹打开失败」两条,Designer.cs 同步。
  • 文档:rules/plugin_sdk.md 补充 IOpenFileFormatService 用法与冲突规则。

测试

构建(worktree 内,Debug x64):

dotnet restore "Ink Canvas.sln" --locked-mode
MSBuild.exe /p:platform=x64 /p:configuration=Debug "Ink Canvas\InkCanvasForClass.csproj" /m /verbosity:minimal /p:RunAnalyzers=false

结果:还原与构建均成功,退出码 0,无编译错误(工作树干净,无未提交改动)。

运行验证(Debug 产物直接运行,测试插件 com.jsrbot.testopenformat + 另外两个测试插件 A/B 提供 .docx/.pdf、.enb/.enbx、.failx、.abc):

验证项 结果
打开对话框筛选项 / 默认项(UIA 实读) 默认选中「所有支持的文件 (.icstk;.xml;.zip;.uink;.docx;.pdf;.enb;.enbx;.failx;.abc)」;其后依次为宿主 4 项、测试文档 (*.docx;*.pdf)、测试课件 (*.enb;*.enbx)、失败测试 (*.failx)、测试格式B (*.abc);.icstk 未被插件重复列出
.docx / .enb 打开 命中注册的 handler,插件日志分别记录 doc / course
.abc(另一插件) 派发到插件 B 自己的窗口,多插件按扩展名派发正常
handler 返回 false(.failx) 插件日志 refusing,宿主日志 WARN「插件 … 未能打开文件」,界面顶部 toast「墨迹打开失败」
冲突:插件注册 .icstk RegisterFormat 返回 false 并写 WARN 拒绝
冲突:两插件注册 .enb/.enbx/.pdf 先注册者生效,后注册者该扩展名被忽略并写 WARN
非法/空扩展名 写 WARN,注册失败

回归检查(宿主原生格式路径):

回归项 结果
原生 .icstk 打开(批注模式) PASS,2 条笔画正常渲染
原生 .icstk 保存 PASS,保存文件与源文件字节完全一致(71 bytes,2 strokes)
原生 .icstk 打开(白板模式) PASS
.xml/.zip/.uink 代码路径未改动:命中 HostOpenExtensions 时完全跳过插件派发,后续分支未修改;未单独准备样例手动打开
未注册的未知扩展名 仍回落到原 OpenSingleStrokeFile,行为不变

截图

筛选器(默认「所有支持的文件」+ 插件分组):

打开对话框筛选器

.abc 派发到插件自己的窗口:

插件窗口

handler 返回 false 时的提示:

打开失败提示

回归:原生 .icstk 在批注模式打开:

回归-批注

回归:原生 .icstk 在白板模式打开:

回归-白板

需要确认的两点

  1. 插件格式打开后,宿主不再执行 TryBlockFrozenPageMutation 冻结页检查与 inkCanvas 可见性切换(由插件自行决定界面状态)。如果希望宿主统一处理,请告知期望行为。
  2. 第一项「所有支持的文件」由宿主自动生成,插件只提供组名与扩展名,不接受自定义筛选项文案。

🤖 本 PR 由 SuperDevBot(AI)生成,已按 CONTRIBUTING 完成构建与运行测试。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

希望插件可以注册新支持打开格式

2 participants