使用InnoSetup实现Setup安装程序界面美化

文章目录

      • 一、前言
      • 二、最终效果
      • 三、GitHub工程
      • 四、实现原理
      • 五、界面美化逻辑实现
        • 1、资源目录
        • 2、创建文本
        • 3、创建按钮
        • 4、创建背景图
        • 5、启动定时器
        • 6、创建输入编辑框
        • 7、设置UI对象显示与隐藏
        • 8、监听安装进度
      • 六、Demo工程具体使用
        • 1、中文支持
        • 2、放exe程序文件
        • 3、放界面图片素材
        • 4、使用
      • 七、补充:iss脚本基础知识
        • 1、[Setup]
        • 2、[Languages]
        • 3、[Tasks]
        • 4、[Files]
        • 5、[Icons]
        • 6、[Run]
        • 7、[Registry]
        • 8、[Code]
      • 八、补充2:Pascal语法简单介绍
        • 1、数据类型
          • 1.1、整型数据
          • 1.2、实型数据
          • 1.3、字符类型
          • 1.4、布尔类型
          • 1.5、CONST常量
        • 2、赋值语句
        • 3、if语句
        • 4、case语句
        • 5、for循环语句
        • 6、while循环语句
        • 7、引用其他脚本
        • 8、define常量与应用

一、前言

之前我写过一篇《Unity发布PC版,使用Inno Setup制作安装程序》,建议先看那一下那篇文章。今天,我要讲讲通过InnoSetup如何实现Setup安装程序的界面美化。

二、最终效果

如下,效果还可以吧,觉得不错的可以点个赞~
在这里插入图片描述

三、GitHub工程

本文的Demo我已上传到GitHub,感兴趣的同学可以下载下来学习,觉得不错的话,记得给个星星~
GitHub工程地址:https://github.com/linxinfa/Beautiful-InnoSetup-Demo
在这里插入图片描述

四、实现原理

首先,你需要安装InnoSetup
InnoSetup下载地址:https://jrsoftware.org/isinfo.php

InnoSetup通过 Pascal Scripting: Event Functions这种事件机制,把流程节点的控制交给Pascal Script,使其可以控制上一步、下一步等等的操作。
界面的美化,主要是调用两个美化插件动态库:botva2.dllInnoCallback.dll。用其来控制贴图的位置和样式,和给按钮绑定相应的事件等等的。

点击下载InnoCallback.dll
点击下载botva2.dll

五、界面美化逻辑实现

美化界面用到了botva2.dll。需要引入DllsImport.iss

1
#include "DllsImport.iss"

1、资源目录

界面资源放在res/tmp目录中,包括我们引用的两个dllbotva2.dllInnoCallback.dll
在这里插入图片描述

2、创建文本

接口:

1
function TLabel.Create(hParent:HWND):TLabel

例:
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var lblWelcome:TLabel;

begin
    lblWelcome := TLabel.Create(WizardForm);
    with lblWelcome do
        begin
             Parent := WizardForm;
             Caption := '欢迎安装XXX应用程序';
             Transparent := true;
             Font.Size:= 20
             Font.Name:='黑体'
             Font.Color:=$ffffff
             Left := DpiScale(190);
             Top := DpiScale(195);
        end;
end;

3、创建按钮

接口

1
2
3
4
5
function BtnCreate(hParent:HWND;
                    Left,Top,Width,Height:integer;
                    FileName:PAnsiChar;
                    ShadowWidth:integer;
                    IsCheckBtn:boolean):HWND;

例:
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var
    btnOneKey:HWND;
    BtnOneKeyFont:TFont;
begin
    // 创建按钮
    btnOneKey:=BtnCreate(WizardForm.Handle,DpiScale(240),DpiScale(260),DpiScale(177), DpiScale(43),
                ExpandConstant('{tmp}\btnOneKeyInstall.png'), 1, False);
               
    //设置按钮文字
    BtnSetText(btnOneKey, '一键安装');
    //文字字体
    BtnOneKeyFont:= TFont.Create;
    with BtnOneKeyFont do begin
        Size := 20;
        Name:='黑体';
        Color:=$ffffff;
    end;
    //应用字体
    BtnSetFont(btnOneKey, BtnOneKeyFont.Handle);
    BtnSetFontColor(btnOneKey,$FAFAFA,$FFFFFF,$FFFFFF,$FFFFFF);
    //设置按钮点击函数
    BtnSetEvent(btnOneKey, BtnClickEventID, WrapBtnCallback(@btnOneKey_OnClick,1));
end;

// 按钮点击回调
procedure btnOneKey_OnClick(hBtn:HWND);
begin
    WizardForm.NextButton.OnClick(WizardForm);
    WizardForm.NextButton.OnClick(WizardForm);
end;

4、创建背景图

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
var
    imgBg1:Longint;
    winW:integer;
    winH:integer;
begin
    //窗口宽高
    winW:=DpiScale(660)
    winH:=DpiScale(480)
    //创建背景图
    imgBg1 := ImgLoad(WizardForm.Handle, ExpandConstant('{tmp}\bg.png'), (0), (0), winW, winH, True, True);
    //设置背景图的隐藏
    //ImgSetVisibility(imgBg1, false);
end

5、启动定时器

接口

1
function SetTimer(hWnd: longword; nIDEvent, uElapse: LongWord; lpTimerFunc: LongWord): LongWord;

例:
通过定时器轮播背景图

1
2
3
4
5
6
SetTimer(0, 0, 50, WrapTimerProc(@PageInstall_TimerProc, 4));

procedure PageInstall_TimerProc(H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);
begin
    //轮播背景图逻辑
end

6、创建输入编辑框

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
begin
    edtSelectDir1 := TEdit.Create(WizardForm);
    with edtSelectDir1 do
    begin
        Parent:= WizardForm;
        Text := WizardForm.DirEdit.Text;
        Font.Size:= 10
        Font.Color:=$555555
        Left:= DpiScale(132);
        Top := DpiScale(189);
        Width:= DpiScale(311);
        Height:= DpiScale(24);
        BorderStyle:=bsNone;
        TabStop := false;
        OnChange:=@EdtSelectDir1_EditChanged;
    end;
end

7、设置UI对象显示与隐藏

1
2
3
4
5
6
7
//隐藏按钮
BtnSetVisibility(btn, false);
//隐藏图片
ImgSetVisibility(imgBig, false);
//隐藏文本TLabel、隐藏输入框TEdit
TconSetVisible(lbl, false);
TconSetVisible(edt, false);

8、监听安装进度

用到了WindowsAPI

1
2
function SetWindowLong(Wnd: HWnd; Index: Integer; NewLong: Longint): Longint;
    external '[email protected] stdcall';

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//设置监听
PBOldProc:=SetWindowLong(WizardForm.ProgressGauge.Handle,-4, PBCallBack(@PBProc,4));

//回调
function PBProc(h:hWnd;Msg, wParam,lParam:Longint):Longint;
var
    pr, pos,total: Longint;
    w : integer;
begin
    // CallWindowProc是将消息信息传送给指定的窗口过程的函数
    Result:=CallWindowProc(PBOldProc, h, Msg, wParam,lParam);
    if (Msg=$402) and (WizardForm.ProgressGauge.Position > WizardForm.ProgressGauge.Min) then
    begin
        pos:=WizardForm.ProgressGauge.Position-WizardForm.ProgressGauge.Min;
        total:=WizardForm.ProgressGauge.Max-WizardForm.ProgressGauge.Min;
        pr:=pos*100/total;
        //更新进度条
        PageInstall_SetProgress(pr);
        Notify_DoNotifyProgress(pos, total);
    end;
end;

六、Demo工程具体使用

1、中文支持

为了支持setup程序显示中文,需要配置中文语言文件:ChineseSimplified.isl
点击查看ChineseSimplified.isl

需将ChineseSimplified.isl放在InnoSetup安装目录中的Languages目录中。
在这里插入图片描述

2、放exe程序文件

将要打包的程序文件放在pkgs目录中,如下放在pkgs/v1目录中,我用的是Unity发布了一个windows平台的包。
在这里插入图片描述
iss脚本中指明路径,并指明主程序文件,即.exe文件

1
2
#define MyAppPkgDir ".\pkgs\v1"
#define MyAppExeName "我的应用.exe"

3、放界面图片素材

界面素材放在res/tmp目录中
在这里插入图片描述
iss中需要执行解压拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
// 将tmp的资源, 解压到安装运行时搞出来的临时目录
ExtractTemporaryFile('bg.png');
ExtractTemporaryFile('bg2.png');
ExtractTemporaryFile('btclose.png');
ExtractTemporaryFile('btmin.png');
ExtractTemporaryFile('btn.png');
ExtractTemporaryFile('btnOneKeyInstall.png');
ExtractTemporaryFile('check.png');
ExtractTemporaryFile('progress.png');
ExtractTemporaryFile('progressBg.png');

for i := 1 to InsBgAni_ImgCount do
    ExtractTemporaryFile(Format('pic%d.png',[i]) );

4、使用

InnoSetup打开MakePCSetup.iss
在这里插入图片描述
执行脚本(按F9),即可生成setup程序
在这里插入图片描述
生成的setup文件在output目录中。
在这里插入图片描述

七、补充:iss脚本基础知识

我们通过上一篇的教程(点击查看),可以生成一个iss脚本,我们需要看懂这个脚本,然后对他进行修改和新增代码,实现我们的界面美化功能。
建议过程中可以翻翻Inno Setup帮助说明文档:https://jrsoftware.org/ishelp/index.php

我们的iss脚本的结构是这样的:

1
2
3
4
5
6
7
8
[Setup]
[Languages]
[Tasks]
[Files]
[Icons]
[Run]
[Registry]
[Code]

1、[Setup]

这个段包含用于安装程序和卸载程序的全局设置。

1
2
3
4
5
6
7
[Setup]
AppId=123456789
AppName="我的应用"
AppVersion=1.5
AppPublisher="公司名"
DefaultDirName={<!-- -->autopf}\My Program
DefaultGroupName=My Program

2、[Languages]

InnoSetup制作的安装程序,支持多种语言设置,默认是英文的,如果要设置中文,需要配置对应的中文语言。
语言文件是.isl格式的,将其放在InnoSetup安装目录中的Languages目录中。
在这里插入图片描述
然后在脚本中指明语言文件。

1
2
[Languages]
Name: "chs"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"

哪里下载这个ChineseSimplified.isl呢?点我下载。

3、[Tasks]

它定义安装程序在执行安装期间所有由用户定制的任务。这些任务以选项框和单选项形式在附加任务向导页中出现。
例:

1
2
3
[Tasks]
Name: "desktopicon"; Description: "创建桌面快捷方式"; GroupDescription: "添加快捷方式:"; Flags: checkedonce
Name: "quicklaunchicon"; Description: "创建快速运行栏快捷方式"; GroupDescription: "添加快捷方式:"; Flags: checkedonce

4、[Files]

定义安装程序要安装在用户系统上的任何文件。
例:

1
2
3
4
5
[Files]
Source: "CTL3DV2.DLL"; DestDir: "{sys}"; Flags: onlyifdoesntexist uninsneveruninstall
Source: "MYPROG.EXE"; DestDir: "{app}"
Source: "MYPROG.CHM"; DestDir: "{app}"
Source: "README.TXT"; DestDir: "{app}"; Flags: isreadme

5、[Icons]

定义所有创建在开始菜单或其它位置 (比如桌面) 的快捷方式。
例:

1
2
3
[Icons]
Name: "{group}\My Program"; Filename: "{app}\MYPROG.EXE"; WorkingDir: "{app}"
Name: "{group}\Uninstall My Program"; Filename: "{uninstallexe}"

6、[Run]

用来指定程序完成安装后要执行的程序。
例:

1
2
[Run]
Filename: "{app}\MYPROG.EXE"; Description: "Launch application"; Flags: postinstall nowait skipifsilent unchecked

7、[Registry]

定义安装程序在用户系统上创建、修改或删除的任何注册表项/值。
例:

1
2
3
4
[Registry]
Root: HKLM; Subkey: "Software\My Company"; Flags: uninsdeletekeyifempty
Root: HKLM; Subkey: "Software\My Company\My Program"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\My Company\My Program\Settings"; ValueType: string; ValueName: "InstallPath"; ValueData: "{app}"

其中Root的值简写对照表如下

1
2
3
4
5
HKCU    (HKEY_CURRENT_USER)
HKLM    (HKEY_LOCAL_MACHINE)
HKCR    (HKEY_CLASSES_ROOT)
HKU     (HKEY_USERS)
HKCC    (HKEY_CURRENT_CONFIG)

8、[Code]

最重头的部分来了,[Code]部分使用的是Pascal语言编写的。我们的界面美化,就是在这里实现的。

八、补充2:Pascal语法简单介绍

1、数据类型

1.1、整型数据
类型 数值范围 占字节数 格式
shortint -128 ~ 128 1 带符号 8 位
inteter -32768 ~ 32767 2 带符号 16 位
longint -2147483648 ~ 2147483647 4 带符号 32 位
byte 0 ~ 255 1 带符号 8 位
word 0 ~ 65535 2 带符号 16 位

例:

1
2
3
4
5
var
    age:shortint;
begin
    age:=18;
end;
1.2、实型数据
类型 占字节数 有效位数
single 4 7~8
real 6 11~12
double 8 15~16
extended 10 19~20
comp 8 19~20

例:

1
2
3
4
5
var
    a:single;
begin
    a:=1.3;
end;
1.3、字符类型

例:

1
2
3
4
5
6
7
var
    a:String;
    b:String;
begin
    a:="hello world";
    b:=format('字符串拼接:%s', a);
end;
1.4、布尔类型

例:

1
2
3
4
5
var
    a:boolean;
begin
    a:=true;
end;
1.5、CONST常量

例:

1
2
3
const
    pi=3.14159;
    zero=0;

2、赋值语句

变量名:=表达式;
例:

1
a:="hello world";

3、if语句

例:

1
2
3
4
if x>y then
    a:="big"
else
    a:="small";

4、case语句

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var
    month,days:integer;  
begin  
    write('Input month:');
    readln(month);  
    case month of  
        1,3,5,7,8,10,12:
            days:=31;  
        4,6,9,11:
            days:=30;  
        2:
            days:=28;  
        else
            days:=0;  
    end;
end;

5、for循环语句

例:
N的阶乘,N!=1*2*3*…*N,这里 N不大于 10

1
2
3
4
5
6
7
8
9
10
11
var  
    n,i:integer;
    s:longint;
begin  
    write('Enter n=');
    readln(n);
    s:=1;  
    for i:=2 to n do
        s:=s*i;  
    writeln(n, '的阶乘为:', s);
end;

6、while循环语句

例:

1
2
3
4
5
6
7
8
9
var
    a,:integer;
begin
    a:=10;
    while a>0 do
        begin
            a:=a-1;
        end;
end;

7、引用其他脚本

1
#include "DllsImport.iss"

8、define常量与应用

1
2
3
#define MyAppName "我的应用名"
[Setup]
AppName={<!-- -->#MyAppName}