尧图精选

Inno Setup自定义安装界面:从Pascal脚本到动态布局完全指南

🕒 发布时间:2026/9/14 14:05:56 📁 来源:尧图网络
简介面向使用 Inno Setup 封装 Windows 程序的开发者这份资源提供了一套完整的自定义安装界面方案内置 .NET Framework 4 依赖封装支持可摆脱默认向导的单调样式生成品牌统一的专业安装包。压缩包共 136 个文件约 105.4MB其中 64 个 isl 脚本片段与 2 个 iss 主脚本控制安装流程23 张 png 界面图和 19 个 psd 设计源文件提供界面素材与可编辑模板11 个 dll 扩展库支撑增强版封装功能另有 exe 工具、ico 图标和 license 许可证文件等辅助内容。目前已有 1741 人学习下载方案成熟度较有保障。使用上Code.iss 是核心界面代码version.h 为配置文件可集中修改应用名称、公司、版本号读者只需替换 app 目录下的程序文件并更换 temp 目录下的界面图片再用 Inno Setup 增强版重新编译即可生成与品牌风格统一的专业安装程序。psd 源文件还方便进一步调整视觉细节整体目录结构清晰适合需要快速产出安装包的团队或个人直接套用。1. Inno Setup 自定义安装界面不是换图是改流程接企业内部分发任务时客户往往不看功能列表先双击安装包。Inno Setup 的默认向导干净但干净过头WelcomeLabel 上那行默认字体一眼就能认出是标准模板。真正的自定义安装界面不是把 WizardImage.bmp 换成产品图而是在 Inno Setup 的 Pascal 脚本里接管向导页面的布局与流程把固定模板改成符合自己产品结构的页面。它覆盖页面创建、显示、跳转、数据校验这一整条生命周期。适合要把序列号输入、组件选择、环境检测塞进安装向导的开发者也适合用 Inno Setup Compiler 把脚本编译成量产安装包的发布工程师。下文会直接给出可跑的脚本命令以及编译参数和排错路径。2. 自定义安装界面的底层逻辑TForm、事件钩子与页面对象2.1 安装向导是 TForm 实例不是模板Inno Setup 的安装向导在核心代码里就是一个基于 VCL 的窗体脚本中称为 WizardForm。它的类型是 TSetupForm继承自 TForm。这意味着你在脚本里操作的不是抽象模板而是真实的控件树。凡是默认页面上能看到的标签、输入框、按钮都能通过 WizardForm 的字段访问。比如欢迎页的主标题是 WizardForm.WelcomeLabel1跳转按钮是 WizardForm.NextButton。把握住这一点你就知道为什么一个界面改造不需要重新设计皮肤也不需要嵌入 HTML。值得注意的是[Setup]段的配置项只是构造页面时的初始参数。页面创建完毕是在 InitializeWizard 事件触发前完成的。因此我对界面改动大多数都放在 InitializeWizard 里执行让它一次性把标题、位图、控件的初始值改好。如果有些状态必须在页面显示时才更新那要放到 CurPageChanged 里等页码匹配时再处理。最佳实践是凡是“页面出现之前该定下来的”放 InitializeWizard凡是“每次显示都要变的”放 CurPageChanged。顺序错了效果就是设置被覆盖或控件不存在导致运行时异常。2.2 事件钩子哪些事件跟自定义安装界面相关四个事件几乎支撑了所有自定义安装界面工作。InitializeWizard 在向导窗口构造完成后调用适合新增页面和修改默认控件的初始状态。CurPageChanged 在当前页面已经显示出来后调用适合做页面切换后的提示更新。NextButtonClick 在用户点击“下一步”时触发可以阻止跳转并弹窗校验。BackButtonClick 则在用户按“上一步”时触发处理需要回退清理的状态。下表是这些事件的参数和使用场景事件触发时机适合作什么InitializeWizard向导窗口构造完成新增自定义页面、修改默认控件初始属性CurPageChanged(PageID)页面显示完成后更新提示文本、预填输入框、改变按钮状态NextButtonClick(PageID)点击“下一步”时校验页面数据写入全局变量BackButtonClick(PageID)点击“上一步”时回退时清理临时文件或状态这里面最关键的是 PageID。标准页面有 wpWelcome、wpLicense、wpSelectDir 等常量直接在脚本中使用即可。自定义页面的 ID 是创建时动态分配的。要把页面对象保存为全局变量然后拿 CurPageID 和 Page.ID 做比较。很多人自定义页面不生效原因就是只调用了 CreateCustomPage没有保存返回的 TWizardPage 引用后面想校验数据时找不到入口。下面这个例子显示了页面对象保存与事件处理的配合[Code] var ExtraPage: TWizardPage; procedure InitializeWizard; begin ExtraPage : CreateCustomPage( wpSelectDir, 附加配置, 选择你要绑定的服务); end; procedure CurPageChanged(CurPageID: Integer); begin if CurPageID ExtraPage.ID then WizardForm.NextButton.Caption : 开始部署; end;代码里 CreateCustomPage 的第一个参数 wpSelectDir 表示这个自定义页面插到“选择目录”页之后第二个和第三个参数是页面标题和说明。ExtraPage.ID 是从那个对象的 ID 属性读取动态分配的页码。CurPageChanged 里比较通过后修改的 NextButton.Caption 只在本页面显示不会污染其他页面的按钮文案。2.3 三类页面对象快速构建输入和选择页如果每一个自定义安装界面都从零摆控件工作量会指数增长。Inno Setup 预置了三个可复用的页面类TInputQueryWizardPage、TInputOptionWizardPage、TInputFileWizardPage。分别对应输入框页面、选项页面、文件选择页面。典型使用是序列号输入、安装方式选择、数据目录选择。这三个类的共同特点是页面容器已经搭好你只需要 Add 字段或选项然后通过数组属性取值。例如 TInputQueryWizardPage 的 Values[0] 表示第一个输入框的内容。它们也正确处理了 Tab 键顺序和默认字体。对于更复杂的布局我一般会在这些页面上再添加 TNewStaticText 或 TNewButton而不是放弃它们重造一个裸页面。只有在需要完全自由排布时才使用 CreateCustomPage 返回的 TWizardPage然后手工把控件挂到 Surface 上。这些页面对象在 Inno Setup 脚本中的实际创建函数是 CreateInputQueryPage、CreateInputOptionPage、CreateInputFilePage。它们有一个共同点调用时需要指定前置页面 ID、标题和说明文字。返回对象之后再调用 Add 方法来增加输入项或选项。这比直接操作默认页面更加直观也是自定义安装界面中最常用、最不容易出错的路径。接下来的章节里我会用一个输入页面实例带出完整的编译和校验流程。3. 用 Inno Setup Compiler 跑通第一个自定义安装页面3.1 最小脚本骨架我把自定义安装页面的最小骨架固定为四个部分[Setup]基本信息、[Files]文件清单、[Code]页面对象和方法。.iss 文件就是一个纯文本约定通常用 UTF-8 保存。因为[Code]使用 Pascal 方言如果脚本里出现中文字符串请确保文件编码正确。编译器对缺分号、括号不匹配非常敏感报错行号往往能直接定位问题。先看一个包含序列号输入页的完整脚本。不包含实际文件也能验证页面逻辑[Setup] AppNameDemoApp AppVersion1.0 DefaultDirName{autopf}\DemoApp OutputDiruserdocs:Output Compressionlzma2 SolidCompressionyes AlwaysShowDirOnReadyPageyes [Code] var SerialPage: TInputQueryWizardPage; procedure InitializeWizard; begin SerialPage : CreateInputQueryPage( wpWelcome, 产品序列号, 请输入随采购单发放的序列号, 序列号会写入注册表安装后不可更改。 ); SerialPage.Add(Serial Number:, False); end; function NextButtonClick(CurPageID: Integer): Boolean; begin Result : True; if CurPageID SerialPage.ID then begin if Trim(SerialPage.Values[0]) then begin MsgBox(序列号不能为空, mbError, MB_OK); Result : False; end; end; end; procedure CurPageChanged(CurPageID: Integer); begin if CurPageID SerialPage.ID then WizardForm.NextButton.Caption : 继续; end;这段脚本中CreateInputQueryPage 的四个参数依次是前置页面 wpWelcome、页面标题、说明文字、底部提示。Add 方法返回输入框选项第一个参数是输入框左侧显示标签第二个参数是屏蔽密码输入false 表示可见明文。Values[0] 取得输入框内容Trim 去掉两端空格。NextButtonClick 返回 False 会让界面停留在当前页所以在这里做序列号必填校验非常自然。CurPageChanged 里改的是按钮文案属于界面反馈而不是校验。3.2 编译命令行 ISCC.exe 的参数怎么设有了脚本就可以用 Inno Setup Compiler 编译。安装 Inno Setup 后IDE 位于安装目录命令行工具通常叫 ISCC.exe。它支持在持续集成里直接调用。下面是一条常用命令C:\Program Files (x86)\Inno Setup 6\ISCC.exe my-installer.iss /OD:\Build /FDemoApp-Setup /Q这条命令把 my-installer.iss 编译成安装程序输出到 D:\Build文件名前缀改为 DemoApp-Setup/Q 表示静默编译不弹窗口。表里列出几个我经常在自定义安装界面脚本里用到的参数参数作用注意点/O路径指定输出目录路径带空格要加双引号/F名称指定输出文件名前缀结果是 名称.exe/Q静默编译出错时返回非零退出码/D名称值传入预处理变量配合 ISPP 按环境切换配置/S描述命令附加签名命令签名命令中的空格要转义编译阶段最容易踩的坑并不是 Pascal 语法错误而是[Setup]段使用了不存在的常量。例如{autopf}是 Inno Setup 6 推荐的 Program Files 常量5.x 里的{pf}虽然在兼容层中还可用但新脚本建议用前者。另外中文脚本保存为带 BOM 的 UTF-8 时有时第一行注释会带走 BOM编译器会误报第一个分句错误。所以在用编辑器保存 .iss 时我通常选择 UTF-8 无 BOM。3.3 运行安装包观察页面跳转编译完成后的 exe 默认以向导模式运行。为了确认自定义安装界面的跳转是否符合预期可以在目标机器上以/LOG参数运行安装包把安装过程日志写到文件。日志不会记录你在界面上点了几下但会记录每次页面切换。如果自定义页面在 Next 后没有进入下一步十有八九是 NextButtonClick 返回 False日志里会重复出现当前页点击记录。DemoApp-Setup.exe /LOGC:\setup-debug.log运行期间观察两点。第一序列号页面是否出现在欢迎页之后第二不输入序列号直接点下一步是否弹错并留在原页。确认这两点说明你已经掌握了自定义安装界面的主流程。之后再往这个页面里加入更多输入项、显示逻辑和控件都不会偏离这个框架。4. 自定义安装界面的版式改造欢迎页、许可页、安装路径页4.1 默认页面控件的改名映射与其完全新建页面有时更需要修改现有默认页面的内容。Inno Setup 给每个标准页面的主要控件起了固定名字直接可在[Code]中引用。表里列出最常见的几个控件路径所属页面主要作用WizardForm.WelcomeLabel1欢迎页主标题文本WizardForm.WelcomeLabel2欢迎页副标题/操作说明WizardForm.WizardBitmapImage欢迎页右侧大图WizardForm.LicenseMemo许可页许可协议全文WizardForm.SelectDirLabel安装路径页页面顶部提示WizardForm.DirEdit安装路径页目标目录输入框WizardForm.NextButton所有页面下一步按钮这些控件的属性设置和普通 VCL 控件完全一致。修改 Caption 能改文字修改 Font 能改字体修改 Left、Top、Width、Height 能调整位置。但要注意InitializeWizard 执行时这些控件已经存在所以不会报“控件不存在”的错误。如果你在 CurPageChanged 中改 Caption那么每次进入该页面都会覆盖当前显示值适合动态文案如果你只改一次就放 InitializeWizard。4.2 欢迎页只改文字和位图不算完成很多自定义安装界面项目只把欢迎页大图换掉就宣布完成。实际交付场景里客户往往还要求删掉原来那行“欢迎使用安装向导”的默认字眼改成自己的部署说明。下面的代码展示了如何一次性改掉欢迎页的三个外壳元素procedure InitializeWizard; begin WizardForm.WelcomeLabel1.Caption : MyApp 企业版部署向导; WizardForm.WelcomeLabel2.Caption : 本程序将在你指定的目录安装 MyApp 服务端。 #13#10 安装过程中要求输入管理员批准的序列号。; WizardForm.WizardBitmapImage.Bitmap.LoadFromFile( ExpandConstant({src}\welcome.bmp)); end;这里 WelcomeLabel2 用 #13#10 连接两行文本表示换行。WizardBitmapImage.Bitmap.LoadFromFile 从安装包同级目录加载 welcome.bmp。文件必须真实存在于{src}目录否则运行到这一行会抛异常。稳妥做法是把这个位图放入所写的 .iss 所在目录并用Source: welcome.bmp; Flags: dontcopy放进[Files]段再用ExpandConstant({tmp}\welcome.bmp)访问。使用{src}只有在未安装时能找到源文件安装后源文件不一定还在。4.3 许可页控制 Memo 和显示条件许可页的默认样子是灰色文本框里面放一长串协议文字。你可以修改 LicenseMemo 的字号和背景色但更重要的是控制这个页面是否展示。Inno Setup 默认只要[Setup]段有 LicenseFile就会自动显示许可页。如果想在某些条件下不显示可以结合 ShouldSkipPage 事件。比如内部开发版安装包不展示正式协议[Setup] LicenseFilelicense.txt [Code] function ShouldSkipPage(PageID: Integer): Boolean; begin Result : False; if PageID wpLicense then Result : IsDebugBuild; end;这里 IsDebugBuild 对应一个自定义的预处理变量或全局布尔值。ShouldSkipPage 在每次页面切换前执行返回 True 会直接跳过该页。需要说明的是LicenseMemo 的修改放在 InitializeWizard 会早于页面显示所以可以在进入许可页之前调整字号。下面是一个调整字体大小的片段WizardForm.LicenseMemo.Font.Size : 11; WizardForm.LicenseMemo.Color : clWindow;Font.Size 修改为 11 磅让长文本更容易阅读。Color 设置为 clWindow 后和系统窗口背景一致避免深色背景里出现一条亮灰色。如果你要改成深色主题还需要同步改字体的 Color否则文字颜色与背景撞色无法阅读。4.4 安装路径页改提示语和预设路径安装路径页最容易做的自定义安装界面修改是把顶部说明文字改成你自己的语境并在初始化时预填一个非默认的安装目录。注意不要在 CurPageChanged 中无条件覆写 DirEdit.Text因为那会让用户在页面上做的修改一进入下一页就丢失。正确做法是把预设逻辑放在 InitializeWizardprocedure InitializeWizard; begin WizardForm.SelectDirLabel.Caption : 选择 MyApp 的部署目录 #13#10 建议保持默认不要安装在 C 盘根目录。; WizardForm.DirEdit.Text : D:\Applications\MyApp; end;DirEdit.Text 只是显示值最终安装时真正生效的值是[Setup]段的 DefaultDirName以及用户在界面上修改后的结果。如果你确实需要在初始化时强制使用自定义目录推荐直接用DefaultDirName{code:MyDefaultDir}通过一个函数返回动态路径。这个函数会在页面构造时被调用效果比在 InitializeWizard 里改 Text 更符合 Inno Setup 的取值逻辑。5. 在自定义安装界面中手工创建控件并做动态布局5.1 不用预置页面在 TWizardPage 上手工摆控件当序列号、选项、文件选择页面都无法满足布局要求时只能回到 CreateCustomPage 返回的 TWizardPage自己把控件挂到页面上。典型场景是服务安装包中要在同一页输入服务地址、端口、超时时间三个参数同时放一个在线测试按钮。预置类做不到这种混合排布。先创建页面然后用 TNewStaticText、TNewEdit、TNewButton 三个类创建控件。这些控件类在 Inno Setup 的 Pascal 脚本头文件中预定义用法与 VCL 对应类相似。一个关键点是必须把 Parent 设置为自定义页面的 Surface 属性而不是那个页面对象本身。Surface 才是可视区域。下面是一个服务地址页面的最小实现var DataPage: TWizardPage; UrlEdit: TNewEdit; PortEdit: TNewEdit; TestButton: TNewButton; procedure InitializeWizard; begin DataPage : CreateCustomPage( wpSelectDir, 服务配置, 填写服务地址和端口); UrlEdit : TNewEdit.Create(WizardForm); UrlEdit.Parent : DataPage.Surface; UrlEdit.Left : 80; UrlEdit.Top : 16; UrlEdit.Width : 220; end;创建控件时owner 可以传 WizardForm最终显示的 Parent 是 DataPage.Surface。如果不设置 Parent控件不会显示也不会有句柄。Left、Top、Width、Height 的单位与界面一致在默认 96 DPI 下像素单位即可。要加标签时创建一个 TNewStaticText把 Caption 设为 服务地址:Top 对齐到 UrlEdit 的那一行Left 比 UrlEdit.Left 小即可。这个页面就不再依赖预置布局了。控件类常用场景关键属性TNewStaticText静态标签Caption、FontTNewEdit单行输入框Text、PasswordTNewButton动作按钮Caption、OnClick5.2 DPI 与缩放不能直接把宽度写死自定义安装界面出现的错位问题绝大多数来自 DPI。Inno Setup 6 默认支持 DPI 感知界面在高 DPI 下会自动缩放但你在脚本里写的 Left、Width 是逻辑像素还是物理像素取决于界面缩放时机。为了避免在 150% DPI 下控件变成一团乱麻需要用 ScaleX 和 ScaleY 包装所有尺寸。下面这个动态调整片段演示了在页面显示后重新计算控件宽度procedure CurPageChanged(CurPageID: Integer); begin if CurPageID DataPage.ID then begin UrlEdit.Width : DataPage.Surface.Width - ScaleX(110); TestButton.Left : UrlEdit.Left UrlEdit.Width ScaleX(8); TestButton.Width : ScaleX(64); end; end;ScaleX 接受一个以 96 DPI 为基准的像素值当前 DPI 下返回值已经换算好。把页面底部或右侧的空间作为弹性区宽度通过 Surface.Width 减去固定偏移计算这样窗口放大或缩小控件都能跟着走。TestButton.Left 因 UrlEdit.Width 变化而重新定位避免了按钮被盖住。尽量不要在 InitializeWizard 里给这些控件写死最终位置因为那时窗口尺寸可能尚未按最终 DPI 调整。5.3 把控件内容存入全局变量供安装过程使用手工创建控件的最终目的是把用户填的信息带到安装流程里。不能依赖控件对象在安装阶段仍然存在因为进入实际安装阶段后向导页面组件仍然存在但你更愿意用一个普通全局变量来保存。这样可以在 InitDir、PerformInstall 等过程里引用。var ServerUrl: string; function NextButtonClick(CurPageID: Integer): Boolean; begin Result : True; if CurPageID DataPage.ID then begin ServerUrl : Trim(UrlEdit.Text); if ServerUrl then begin MsgBox(服务地址不能为空, mbError, MB_OK); Result : False; end; end; end;NextButtonClick 中把 UrlEdit.Text 赋值给全局变量 ServerUrl。后续在[Setup]段中的FileName: {app}\config.ini; Section: server; Key: url; String: {code:GetServerUrl}可以调用一个函数返回这个值或者直接在[Files]之后的某个函数里读取 ServerUrl。注意在 NextButtonClick 返回 True 之前页面对象和控件仍然有效所以取值要放在这个时机而不是等下一页的 CurPageChanged 再去读。6. 自定义安装界面的验证与排错日志、句柄与重绘6.1 在页面切换时输出日志调试自定义安装界面的第一步是确认事件是否真的被触发。Inno Setup 的 Log 函数会把字符串写入安装日志配合/LOG参数运行时最方便。在 CurPageChanged 里加一行procedure CurPageChanged(CurPageID: Integer); begin Log(当前页面: IntToStr(CurPageID)); end;运行安装程序时加上/LOGC:\setup-debug.log然后打开日志文件对比你实际看到的界面顺序。例如你预期页面顺序是 欢迎0 - 序列号1 - 目录2如果日志里没有出现序列号页的 ID说明页面创建失败或者被 ShouldSkipPage 跳过了。这个方法比弹窗更轻量也不会中断安装流程。6.2 检查控件句柄解决空白页自定义动画中常见的“页面是空白”其实有两种原因控件没有设置 Parent或者页面对象还在但控件被其他页面遮挡。判断控件是否实际创建可以看它的 Handle 是否为 0。在 CurPageChanged 里用 Assigned 检查if Assigned(UrlEdit) and (UrlEdit.Handle 0) then UrlEdit.Visible : True else Log(UrlEdit 无效句柄为 0);如果 UrlEdit 没有被赋 Parent句柄就是 0。这时日志会提示控件无效而不是空白页面。另一种做法是检查 Parent 是否为 nil。下面表格列出几个常见界面问题的定位方向现象最常见原因排错切入点自定义页面完全空白控件 Parent 未设置或设置错误Parent 改为 Surface页面显示但标题没变修改放在了 CurPageChanged 之后确认顺序和语法点击下一步无动作NextButtonClick 返回 False检查返回值逻辑高 DPI 下按钮错位宽度、坐标没有 ScaleX/Y用 ScaleX/Y 包装尺寸6.3 抑制批量修改时的闪烁在 InitializeWizard 里连续修改多个控件的 Caption、Width、Color会让界面在创建阶段反复重绘。症状是安装程序启动后窗口有短暂闪烁或控件位置在第一次显示时才突然跳动。抑制闪烁有两个习惯一是把所有默认界面修改集中到一个过程二是用 WM_SETREDRAW 暂停重绘完成后再恢复。后者适合大量控件同时调整的场景。const WM_SETREDRAW $000B; begin SendMessage(WizardForm.Handle, WM_SETREDRAW, 0, 0); try // 集中修改多个控件 finally SendMessage(WizardForm.Handle, WM_SETREDRAW, 1, 0); end; end;WM_SETREDRAW 的第二个参数 0 表示关闭重绘1 表示恢复。恢复后 VCL 会自动重绘整个窗口。需要注意这段代码只能在向导窗口已经创建的前提下运行所以放在 InitializeWizard 之后比较安全。这里的 try/finally 保证即使某个控件属性报错也能恢复重绘否则窗口会永远白屏。这是自定义安装界面过程中最值得记的一条边界。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →