ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

深入解析 JDK jpackage Windows MSI 安装器的 ControlEvents 事件表:以 --win-dir-chooser 安装目录对话框为例

2026/9/13 15:35:31 拓冰建站 浏览量
深入解析 JDK jpackage Windows MSI 安装器的 ControlEvents 事件表:以 --win-dir-chooser 安装目录对话框为例 深入解析 JDK jpackage Windows MSI 安装器的 ControlEvents 事件表以 --win-dir-chooser 安装目录对话框为例【免费下载链接】jdkJDK main-line development https://openjdk.org/projects/jdk项目地址: https://gitcode.com/GitHub_Trending/jd/jdk导读JDK 的 jpackage 工具在打包 Windows MSI 安装器时会基于 WiX 工具集生成安装界面并通过改写 MSI 数据库中的ControlEvents控件事件与InstallUISequenceUI 动作序列两张表来定制向导流程。本文以仓库中的黄金参考文件 ControlEvents.md 为骨架逐列、逐行拆解--win-dir-chooser场景下安装目录对话框的事件流并结合 InstallDirNotEmptyDlg.wxs、WinInstallerUiTest.java 等源码说明 jpackage 如何用事件订阅把自定义对话框缝合进 WiX 标准安装向导。读完本文你将理解 MSIControlEvents表的六列语义、jpackage 自定义 UI 的编排机制以及如何阅读和复现这套测试黄金文件。一、背景--win-dir-chooser与 jpackage 的 MSI UI 定制机制1.1 选项定义与作用jpackage 的--win-dir-chooser是一个仅在 Windows 平台生效的打包选项官方手册 jpackage.md 的描述是Adds a dialog to enable the user to choose a directory in which the product is installed.即添加一个对话框允许用户选择产品的安装目录。中文资源文件 HelpResources_zh_CN.properties 中的描述为添加一个对话框以允许用户选择产品的安装目录。在命令行解析层面该选项在 StandardOption.java 中定义public static final OptionValueBoolean WIN_INSTALLDIR_CHOOSER booleanOption(win-dir-chooser).scope(nativeBundling()).create();注意它被限定在nativeBundling()作用域内即只对 MSI/EXE 这类原生打包流程有意义与--type app-image等不产生安装器的打包方式无关。1.2 UI 片段的生成链路在 MSI 打包器 WinMsiPackager.java 的类注释中明确写道ui.wxf文件是基于--license-file、--win-shortcut-prompt、--win-dir-chooser三个命令行选项生成的 WiX UI 片段。实际的片段装配发生在 WixUiFragmentBuilder.javauiConfig UIConfig.build() .withLicenseDlg(pkg.licenseFile().isPresent()) .withInstallDirChooserDlg(pkg.withInstallDirChooser()) .withShortcutPromptDlg(!shortcutFolders.isEmpty() pkg.withShortcutPrompt()) .create(); if (!uiConfig.equals(UIConfig.build().create()) || pkg.withUI()) { uiSpec Optional.of(UISpec.create(uiConfig)); } else { uiSpec Optional.empty(); }也就是说只要指定了目录选择、许可协议或快捷方式提示中的任意一项或显式指定--win-with-uijpackage 就会生成一个基于 WiX 标准InstallDir风格 UI 的ui.wxf片段否则该片段为空安装器使用 WiX 默认界面。最终这些 UI 设定会编译进 MSI 数据库的两张表——InstallUISequence安装 UI 序列与ControlEvents控件事件。而本文主角 ControlEvents.md正是仓库测试中对启用--win-dir-chooser且不带其他 UI 选项这一场景下ControlEvents表改写的期望快照黄金文件。二、ControlEvents.md 是什么测试黄金参考文件2.1 文件定位该文件位于 test/jdk/tools/jpackage/resources/WinInstallerUiTest/dir_chooser/ 目录下与 InstallUISequence.md 成对出现。InstallUISequence.md的内容同样极简但语义明确| Action | Condition | | --- | --- | | WelcomeDlg | NOT Installed OR PATCH |它表示在全新安装NOT Installed或打补丁PATCH时安装向导的 UI 序列从WelcomeDlg开始——这是标准的 WiX 入口对话框动作。在resources/WinInstallerUiTest/目录下一共存在 8 个场景子目录每个都包含ControlEvents.md与InstallUISequence.mddir_chooserdir_chooserlicensedir_chooserlicenseshortcut_promptdir_choosershortcut_promptlicenselicenseshortcut_promptshortcut_promptui目录名正是测试用例 TestSpec.toString() 生成的特性组合标签把withDirChooser、withShortcutPrompt、withLicense、withUi四个布尔开关命名的 token 排序后用连接dir_chooser来自withDirChoosershortcut_prompt来自withShortcutPromptlicense来自withLicenseui来自withUi。2.2 为什么只有 8 个目录测试用例的生成逻辑在 WinInstallerUiTest.java对withDirChooser / withLicense / withShortcutPrompt做 2×2×2 全组合剔除三者全为 false 的用例避免与SimplePackageTest重复得到 7 个不带 UI 的组合再追加一个纯ui用例共 8 个基础场景。而test()方法还会把这 7 个组合各复制一份强制开启--win-with-ui但 expectedFilesDir() 会把带 UI 且含自定义对话框的用例重定向到其不带 UI 版本的目录——因为--win-with-ui不改变这些对话框的事件表内容只是让安装器额外显示 EULA 等标准页面。这正是黄金文件目录只有 8 个而非 15 个的原因。三、ControlEvents 表结构与列语义ControlEvents.md 全文是一张 Markdown 表格与 MSI 数据库ControlEvents表一一对应列对应 MSI 字段含义DialogDialog_事件源对话框当前所在页面ControlControl_触发事件的控件按钮等EventEvent发布的事件类型如NewDialog跳转页面ArgumentArgument事件参数如NewDialog的目标对话框 IDConditionCondition事件发布的条件表达式1恒真NOT Installed表示未安装OrderingOrdering同一控件上多个事件的处理顺序数值越小越先执行在测试代码中这一结构被建模为 MsiDatabase.ControlEvent 记录public record ControlEvent( String dialog, String control, String event, String argument, String condition, int ordering) { ... }3.1 一个重要的前提黄金文件是过滤后的子集需要特别说明ControlEvents.md并非 MSI 数据库中ControlEvents表的全量转储而是测试工具按规则筛选出的jpackage 对 UI 流程的关键改动。筛选逻辑位于 MsiDatabase.uiAlterations()对 jpackage 自有的自定义对话框InstallDirNotEmptyDlg、ShortcutPromptDlg无条件收录全部事件对 WiX 标准对话框仅收录Next/Back两个控件上Ordering 6的事件——测试注释给出的依据是jpackage 假定标准 WiX UI 不会为它改写的对话框序列定义高于 6 的顺序号结果按dialog → control → event → argument → condition → ordering六键排序后输出。因此阅读这些黄金文件时应将其理解为jpackage 改写 MSI UI 的关键事件清单而非完整表内容。例如InstallDirDlg上触发目录校验的DoAction事件Ordering3、5就不会出现在文件中详见第四节。四、dir_chooser 场景4 条事件逐行解读dir_chooser场景的完整期望表如下原文照录| Dialog | Control | Event | Argument | Condition | Ordering | | --- | --- | --- | --- | --- | --- | | InstallDirDlg | Back | NewDialog | WelcomeDlg | NOT Installed | 6 | | InstallDirNotEmptyDlg | No | NewDialog | InstallDirDlg | 1 | 1 | | InstallDirNotEmptyDlg | Yes | NewDialog | VerifyReadyDlg | 1 | 1 | | WelcomeDlg | Next | NewDialog | InstallDirDlg | NOT Installed | 6 |这 4 条事件刻画了启用--win-dir-chooser后安装向导的核心导航图。逐条拆解4.1 入口WelcomeDlg | Next | NewDialog | InstallDirDlg | NOT Installed | 6全新安装时用户在欢迎页点击Next事件发布NewDialog目标为InstallDirDlg安装目录选择页。条件NOT Installed保证升级/修复等场景不会从欢迎页重复进入目录页Ordering6表明这是标准 WiX UI 在该控件上的既有事件被测试筛选规则原样收录。4.2 返回InstallDirDlg | Back | NewDialog | WelcomeDlg | NOT Installed | 6与上一条配对在目录选择页点击Back回到欢迎页。同样只在NOT Installed时生效Ordering6 属于 WiX 标准事件。4.3 自定义对话框InstallDirNotEmptyDlg | No | NewDialog | InstallDirDlg | 1 | 1InstallDirNotEmptyDlg是 jpackage 为--win-dir-chooser注入的自定义对话框WiX 源码见 InstallDirNotEmptyDlg.wxs。它的职责是当用户选择的安装目录已存在且非空时弹出确认提示。若用户点击No拒绝在非空目录安装则NewDialog跳回InstallDirDlg让用户重新选择目录。条件为恒真1Ordering1。对应的 WiX 声明InstallDirNotEmptyDlg.wxsCustomAction IdJpCheckInstallDir BinaryKeyJpCaDll DllEntryCheckInstallDir / UI Dialog IdInstallDirNotEmptyDlg Width300 Height85 Title!(loc.InstallDirNotEmptyDlg_Title) Control IdYes TypePushButton X100 Y55 Width50 Height15 Defaultno Cancelno Text!(loc.WixUIYes) Publish EventNewDialog Value$(var.JpAfterInstallDirDlg)1/Publish /Control Control IdNo TypePushButton X150 Y55 Width50 Height15 Defaultyes Cancelyes Text!(loc.WixUINo) Publish EventNewDialog ValueInstallDirDlg1/Publish /Control ... /Dialog /UI可以看到确认/取消按钮的文字来自本地化资源MsiInstallerStrings_*.wxl仓库中提供en、de、ja、zh_CN等语言版本而目录非空的判断由自定义动作JpCheckInstallDirDLL 入口CheckInstallDir在用户点击InstallDirDlg的Next时执行。4.4 确认InstallDirNotEmptyDlg | Yes | NewDialog | VerifyReadyDlg | 1 | 1用户点击Yes接受在非空目录安装NewDialog跳转到VerifyReadyDlg确认安装页。注意这里的跳转目标并不是硬编码的——WiX 片段中写的是$(var.JpAfterInstallDirDlg)这是一个由 UISpec.createCustom() 动态计算的 WiX 变量指向安装目录页之后的下一个对话框var it dialogs.iterator(); do { if (it.next().equals(InstallDirDlg)) { uiSpec.setWixVariable(JpAfterInstallDirDlg, it.next().id()); } } while (it.hasNext());在纯dir_chooser场景下InstallDirDlg之后紧跟VerifyReadyDlg因此JpAfterInstallDirDlg解析为VerifyReadyDlg与黄金文件一致若同时启用--win-shortcut-prompt则该变量解析为ShortcutPromptDlg见第五节对比表。4.5 被过滤掉的幕后事件细心的读者会发现InstallDirNotEmptyDlg.wxs中InstallDirDlg的Next按钮其实发布了 3 个事件Publish DialogInstallDirDlg ControlNext EventDoAction ValueJpCheckInstallDir Order31/Publish Publish DialogInstallDirDlg ControlNext EventNewDialog ValueInstallDirNotEmptyDlg Order5INSTALLDIR_VALID0/Publish Publish DialogInstallDirDlg ControlNext EventNewDialog Value$(var.JpAfterInstallDirDlg) Order5INSTALLDIR_VALID1/Publish即先执行JpCheckInstallDir动作校验目录Order3再按校验结果属性INSTALLDIR_VALID分流——值为0目录非空/无效进入InstallDirNotEmptyDlg值为1则直接进入下一个对话框。但由于这三个事件挂在 WiX 标准对话框InstallDirDlg上且Ordering 6按 MsiDatabase.uiAlterations() 的过滤规则它们不会出现在黄金文件中。这再次印证黄金文件聚焦于jpackage 引入的自定义流程节点与被改写的标准导航事件。五、与其他选项组合时的差异5.1 叠加--win-shortcut-promptdir_choosershortcut_prompt场景的 ControlEvents.md 扩充到了 8 条事件| Dialog | Control | Event | Argument | Condition | Ordering | | --- | --- | --- | --- | --- | --- | | InstallDirDlg | Back | NewDialog | WelcomeDlg | NOT Installed | 6 | | InstallDirNotEmptyDlg | No | NewDialog | InstallDirDlg | 1 | 1 | | InstallDirNotEmptyDlg | Yes | NewDialog | ShortcutPromptDlg | 1 | 1 | | ShortcutPromptDlg | Back | NewDialog | InstallDirDlg | 1 | 1 | | ShortcutPromptDlg | Cancel | SpawnDialog | CancelDlg | 1 | 1 | | ShortcutPromptDlg | Next | NewDialog | VerifyReadyDlg | 1 | 1 | | VerifyReadyDlg | Back | NewDialog | ShortcutPromptDlg | NOT Installed | 6 | | WelcomeDlg | Next | NewDialog | InstallDirDlg | NOT Installed | 6 |对比纯dir_chooser场景变化一目了然InstallDirNotEmptyDlg | Yes的跳转目标从VerifyReadyDlg变为ShortcutPromptDlg——这正是JpAfterInstallDirDlg动态计算的结果新增ShortcutPromptDlg快捷方式提示页的 4 条事件Back回InstallDirDlg、Cancel通过SpawnDialog弹出标准的CancelDlg取消确认框、Next前进到VerifyReadyDlgVerifyReadyDlg | Back的目标改为ShortcutPromptDlg形成完整的回退链。测试代码中--win-shortcut-prompt不是单独使用的它必须与--win-menu、--win-shortcut搭配见 WinInstallerUiTest.java因为提示框本身是让用户选择是否创建开始菜单/桌面快捷方式。5.2 叠加--license-filedir_chooserlicense场景的 ControlEvents.md 反而精简到只有 2 条| Dialog | Control | Event | Argument | Condition | Ordering | | --- | --- | --- | --- | --- | --- | | InstallDirNotEmptyDlg | No | NewDialog | InstallDirDlg | 1 | 1 | | InstallDirNotEmptyDlg | Yes | NewDialog | VerifyReadyDlg | 1 | 1 |其 InstallUISequence.md 仍为WelcomeDlg | NOT Installed OR PATCH。可以看到加入许可协议对话框WelcomeEulaDlg/LicenseAgreementDlg后标准对话框WelcomeDlg、InstallDirDlg、VerifyReadyDlg上的Ordering 6导航事件不再出现在清单中仅保留 jpackage 自定义对话框的 2 条事件。这从侧面说明许可对话框的引入改变了向导的序列结构标准导航事件或被替换、或不再满足筛选条件——这正是此类黄金文件的价值任何对 UI 流程的改动都会直观地反映在快照差异中。三层组合dir_chooserlicenseshortcut_prompt的 ControlEvents.md 则进一步印证了这一点它保留InstallDirNotEmptyDlg两条事件与ShortcutPromptDlg的 4 条事件其中Yes的目标为ShortcutPromptDlg、ShortcutPromptDlg | Back目标为InstallDirDlg。5.3 纯--win-with-ui纯ui场景不带目录选择、许可、快捷方式提示的 ControlEvents.md 是空表仅有表头而其 InstallUISequence.md 为| Action | Condition | | --- | --- | | WelcomeDlg | Installed AND PATCH | | WelcomeEulaDlg | 0 |这表示--win-with-ui单独使用时jpackage 不注入任何自定义控件事件仅通过InstallUISequence调整对话框的显示条件例如在补丁场景显示欢迎页、引入 EULA 页WelcomeEulaDlg。对比 dir_chooser 场景的WelcomeDlg | NOT Installed OR PATCH可以清晰看到同一张表在不同选项组合下被改写的具体差异。六、测试验证机制从 MSI 二进制到 Markdown 快照这套黄金文件并非手写而是由测试框架从真实生成的 MSI 安装包中提取、序列化而来并在每次测试运行时逐行比对。6.1 运行时的比对流程WinInstallerUiTest.run() 的核心逻辑createTest(false).forTypes(PackageType.WIN_MSI).addBundleVerifier(cmd - { var expectedFilesDir expectedFilesDir(); var expectedInstallUISequence Files.readAllLines(expectedFilesDir.resolve(INSTALL_UI_SEQUENCE_FILE)); var expectedControlEvents Files.readAllLines(expectedFilesDir.resolve(CONTROL_EVENTS_FILE)); var uiAlterations WindowsHelper.getUIAlterations(cmd); var actualInstallUISequence actionSequenceToMarkdownTable(uiAlterations.installUISequence()); var actualControlEvents controlEventsToMarkdownTable(uiAlterations.controlEvents()); TKit.assertStringListEquals(expectedInstallUISequence, actualInstallUISequence, ...); TKit.assertStringListEquals(expectedControlEvents, actualControlEvents, ...); }).run();流程为先用jpackage测试中通过 JPackageCommand 调用生成 MSI → 通过WindowsHelper.getUIAlterations(cmd)读取 MSI 数据库 → 用 toMarkdownTable() 把InstallUISequence与ControlEvents两张表序列化为与黄金文件格式完全一致的 Markdown表头、---分隔行、单元格内的|会被转义为#124;→ 与磁盘上的期望文件逐行断言相等。任何一行差异都会导致测试失败并给出精确到文件与行的提示信息。6.2 黄金文件的再生成当 jpackage 的 UI 逻辑发生有意变更时可以调用 updateExpectedMsiTables() 重新生成全部 8 个场景的快照public static void updateExpectedMsiTables() { for (var spec : testCases()) { spec.createTest(true).addBundleVerifier(cmd - { spec.save(WindowsHelper.getUIAlterations(cmd)); }).run(Action.CREATE); } }它遍历所有用例重新构建 MSI 并调用spec.save(...)把提取结果写回对应的ControlEvents.md/InstallUISequence.md。这套生成 → 固化 → 比对的黄金文件机制是 jpackage 保证 Windows 安装器 UI 行为不回归的关键手段。6.3 运行前提与方式从测试头部注释WinInstallerUiTest.java可知通过requires (os.family windows)限定仅在 Windows 上运行依赖 MSI/WiX 工具链通过run main/othervm/timeout720 -Xmx512m jdk.jpackage.test.Main --jpt-runWinInstallerUiTest驱动SQESpecial Quality Engineering环境下会额外排除部分用例使用 JDK 构建产物中的 JTReg 测试入口即可执行例如make test TESTtest/jdk/tools/jpackage/windows/WinInstallerUiTest.java或直接使用jtreg配合-jdk:build-jdk运行该文件。测试参数组合覆盖了--win-dir-chooser、--win-shortcut-prompt、--win-with-ui、--license-file的 15 个规格含带 UI 变体并验证生成的 MSI 包类型限定为WIN_MSIrun() 中forTypes(PackageType.WIN_MSI)。七、实战复现与观察事件表7.1 生成带目录选择对话框的 MSI在 Windows 上使用 JDK 自带的jpackage命令jpackage --type msi \ --win-dir-chooser \ --name DemoApp \ --input input-dir \ --main-jar demo.jar \ --main-class com.example.Demo \ --dest out安装时向导会依次出现欢迎页 → 安装目录选择页若用户选中的目录已存在且非空则弹出InstallDirNotEmptyDlg确认框。如需叠加快捷方式提示追加jpackage --type msi \ --win-dir-chooser \ --win-shortcut-prompt \ --win-menu \ --win-shortcut \ --name DemoApp \ --input input-dir \ --main-jar demo.jar \ --main-class com.example.Demo \ --dest out--win-shortcut-prompt需与--win-menu/--win-shortcut配合使用这与 WinInstallerUiTest.java 中的测试初始化逻辑一致。7.2 用仓库测试观察事件流若想亲自验证事件表内容最直接的方式是在 Windows 上运行上述WinInstallerUiTest或在修改 jpackage UI 逻辑后调用updateExpectedMsiTables()观察resources/WinInstallerUiTest/下各场景文件的 diff——这正是仓库作者维护这套黄金文件的日常工作流。对于静态阅读可以对照以下文件形成完整证据链期望值dir_chooser/ControlEvents.md 与 dir_chooser/InstallUISequence.md对话框实现InstallDirNotEmptyDlg.wxs序列编排UISpec.java测试与提取逻辑WinInstallerUiTest.java 与 MsiDatabase.java。结语ControlEvents.md虽然只是一张 4 行的 Markdown 表格但它精确编码了 jpackage 在--win-dir-chooser场景下对 MSIControlEvents表的全部关键改写标准 WiX 导航事件WelcomeDlg↔InstallDirDlgOrdering6与自定义InstallDirNotEmptyDlg分流事件Yes/NoOrdering1共同构成完整向导流程而目标对话框JpAfterInstallDirDlg的动态计算机制使其可以无缝兼容--win-shortcut-prompt、--license-file等组合。理解这张表就等于掌握了 jpackage Windows 安装器 UI 定制的神经系统——无论是阅读、调试还是扩展安装流程它都是最值得优先查阅的入口。【免费下载链接】jdkJDK main-line development https://openjdk.org/projects/jdk项目地址: https://gitcode.com/GitHub_Trending/jd/jdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考