Inno Setup用Delphi写成,其官方网站同时也提供源程序免费下载。它虽不能与Installshield这类恐龙级的安装制作软件相比,但也当之无愧算是后起之秀。Inno Setup是一个免费的安装制作软件,小巧、简便、精美是其最大特点,支持pascal脚本,能快速制作出标准Windows2000风格的安装界面,足以完成一般安装任务。
公司目前是使用的Installshield,但是我们项目原先基于innosetup做的打包脚本,所以我也就接着用InnoSetup来开发了。
一开始以为innoseup想要做好看的界面,像百度360腾讯这些大厂的安装程序那样的界面有些不可能,但跟着https://blog.csdn.net/linxinfa/article/details/108995508?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-2&spm=1001.2101.3001.4242这篇文章的指引,发现innosetup真的强大,理论上只要能封装dll,也许拿QT写个安装界面给innosetup用也不是不可能。
下面是我通过网上一些现成的资源改的安装脚本的实际运行GIF,为避免误会,我抹去了原先产品的logo,项目下载链接:https://files-cdn.cnblogs.com/files/suxia/test.rar
基础知识网上有大把教程,我就不再赘述了。在开始innosetup美化工作的前面,得先扩充下一些API接口,单纯靠innosetup是做不出好看的界面的,我们得准备一些接口,主要依赖于botva2.dll和innocallback.dll,前者负责按钮图标选择框等美化控件的创建,后者则是完善这些控件操作后的回调函数。
//***************************************************************************************************************************************************************************************************************************** //系统api接口 function SetLayeredWindowAttributes(hwnd:HWND; crKey:Longint; bAlpha:byte; dwFlags:longint ):longint;external 'SetLayeredWindowAttributes@user32 stdcall'; //函数声明 function SetWindowLong(Wnd: HWnd; Index: Integer; NewLong: Longint): Longint; external 'SetWindowLongA@user32.dll stdcall'; function CallWindowProc(lpPrevWndFunc: Longint; hWnd: HWND; Msg: UINT; wParam, lParam: Longint): Longint; external 'CallWindowProcA@user32.dll stdcall'; function SetTimer(hWnd: LongWord; nIDEvent, uElapse: LongWord; lpTimerFunc: LongWord): LongWord; external 'SetTimer@user32.dll stdcall'; function KillTimer(hWnd: LongWord; nIDEvent: LongWord): LongWord; external 'KillTimer@user32.dll stdcall'; function GetWindowLong(Wnd: HWnd; Index: Integer): Longint; external 'GetWindowLongA@user32.dll stdcall'; function ReleaseCapture(): Longint; external 'ReleaseCapture@user32.dll stdcall'; function CreateRoundRectRgn(p1, p2, p3, p4, p5, p6: Integer): THandle; external 'CreateRoundRectRgn@gdi32 stdcall'; function SetWindowRgn(hWnd: HWND; hRgn: THandle; bRedraw: Boolean): Integer; external 'SetWindowRgn@user32 stdcall'; function LoadCursorFromFile(FileName: String): Cardinal; external 'LoadCursorFromFileA@user32.dll stdcall'; function DeleteObject(p1: Longword): BOOL; external 'DeleteObject@gdi32.dll stdcall'; function GetPM(nIndex:Integer):Integer; external 'GetSystemMetrics@user32.dll stdcall'; function GetSystemMetrics(nIndex: Integer): Integer; external 'GetSystemMetrics@user32.dll stdcall'; //***************************************************************************************************************************************************************************************************************************** //botva2.dll function ImgLoad(Wnd :HWND; FileName :PAnsiChar; Left, Top, Width, Height :integer; Stretch, IsBkg :boolean) :Longint; external 'ImgLoad@{tmp}otva2.dll stdcall delayload'; procedure ImgSetVisibility(img :Longint; Visible :boolean); external 'ImgSetVisibility@{tmp}otva2.dll stdcall delayload'; procedure ImgApplyChanges(h:HWND); external 'ImgApplyChanges@{tmp}otva2.dll stdcall delayload'; procedure ImgSetPosition(img :Longint; NewLeft, NewTop, NewWidth, NewHeight :integer); external 'ImgSetPosition@files:botva2.dll stdcall'; procedure ImgRelease(img :Longint); external 'ImgRelease@{tmp}otva2.dll stdcall delayload'; procedure gdipShutdown; external 'gdipShutdown@{tmp}otva2.dll stdcall delayload'; function BtnCreate(hParent:HWND; Left,Top,Width,Height:integer; FileName:PAnsiChar; ShadowWidth:integer; IsCheckBtn:boolean):HWND; external 'BtnCreate@{tmp}otva2.dll stdcall delayload'; procedure BtnSetText(h:HWND; Text:PAnsiChar); external 'BtnSetText@{tmp}otva2.dll stdcall delayload'; procedure BtnSetVisibility(h:HWND; Value:boolean); external 'BtnSetVisibility@{tmp}otva2.dll stdcall delayload'; procedure BtnSetFont(h:HWND; Font:Cardinal); external 'BtnSetFont@{tmp}otva2.dll stdcall delayload'; procedure BtnSetFontColor(h:HWND; NormalFontColor, FocusedFontColor, PressedFontColor, DisabledFontColor: Cardinal); external 'BtnSetFontColor@{tmp}otva2.dll stdcall delayload'; procedure BtnSetEvent(h:HWND; EventID:integer; Event:Longword); external 'BtnSetEvent@{tmp}otva2.dll stdcall delayload'; procedure BtnSetCursor(h:HWND; hCur:Cardinal); external 'BtnSetCursor@{tmp}otva2.dll stdcall delayload'; procedure BtnSetEnabled(h:HWND; Value:boolean); external 'BtnSetEnabled@{tmp}otva2.dll stdcall delayload'; function GetSysCursorHandle(id:integer):Cardinal; external 'GetSysCursorHandle@{tmp}otva2.dll stdcall delayload'; function BtnGetChecked(h:HWND):boolean; external 'BtnGetChecked@{tmp}otva2.dll stdcall delayload'; procedure BtnSetChecked(h:HWND; Value:boolean); external 'BtnSetChecked@{tmp}otva2.dll stdcall delayload'; procedure CreateFormFromImage(h:HWND; FileName:PAnsiChar); external 'CreateFormFromImage@{tmp}otva2.dll stdcall delayload'; procedure BtnSetPosition(h:HWND; NewLeft, NewTop, NewWidth, NewHeight: integer); external 'BtnSetPosition@{tmp}otva2.dll stdcall delayload'; procedure ImgSetVisiblePart(img:Longint; NewLeft, NewTop, NewWidth, NewHeight : integer); external 'ImgSetVisiblePart@files:botva2.dll stdcall'; //***************************************************************************************************************************************************************************************************************************** //innocallback.dll type TBtnEventProc = procedure (h:HWND); TPBProc = function(h:hWnd;Msg,wParam,lParam:Longint):Longint; //百分比 TTimerProc = procedure(h:longword; msg:longword; idevent:longword; dwTime:longword); function CallBack_Button(Callback: TBtnEventProc; ParamCount: Integer): Longword; external 'wrapcallback@{tmp}innocallback.dll stdcall delayload'; function CallBack_ProgressBar(P:TPBProc;ParamCount:integer):LongWord; external 'wrapcallback@files:innocallback.dll stdcall'; function CallBack_Timer(callback: TTimerProc; Paramcount: Integer): Longword; external 'wrapcallback@files:innocallback.dll stdcall'; //****************************************************************************************************************************************************************************************************************************** //退出当前安装或者卸载进程 procedure ExitProcess(exitCode:integer);external 'ExitProcess@kernel32.dll stdcall'; //******************************************************************************************************************************************************************************************************************************
按我的理解,innosetup分成两条线,一条线就是显示给我们用户看的界面,还有一条线就是他的后台处理逻辑,他后台不变的就是安装和卸载的前期准备工作,还有就是安装和卸载的执行过程,这个过程我们是基本动不了什么的,innosetup会把file字段里提供的文件解压到我们的安装目录,卸载的时候会把安装的文件再删除。最后一个过程就是我们安装卸载完成后的那段时间,用户不点击完成,程序不回结束。像下面图一样,画的有些难看,凑合看下就行了,安装的三个大部分都是单向的,一旦进入到下一步了就不能回到上一步了,但是每个大过程中的每个界面是双向的,可以回到上一步,这应该是好理解的
后台逻辑这条线什么时候跳转到下一步,完全是由innosetupde的界面控制着。比如下面这段代码,是立即安装按钮的按下的回调函数,这时候我们会调用WizardForm.NextButton.OnClick(WizardForm);这一句话等同于按下安装界面的下一步按钮,让后台逻辑进入到安装的执行过程,就是那个有进度条的界面。
//点击立即安装按钮 procedure btn_InstallNowOnClick(hBtn:HWND); begin if BtnGetChecked(chk_License) then begin if edit_Path.Text = '' then begin SuppressibleMsgBox('路径名不能为空!', mbConfirmation, MB_OK, IDOK); end else begin WizardForm.NextButton.OnClick(WizardForm); end; end else begin SuppressibleMsgBox('请先阅读并同意用户协议!', mbConfirmation, MB_OK, IDOK); end;
为了做好看的界面,innosetup提供给我们的页面基本可以都隐藏掉,当然你也可以选择魔改原来的界面,这都是一样的,看你选择,然后剩下来就是我们的界面美化工作,我们只需要在我们的界面上的每个按钮响应事件里控制着innosetup后面那条处理逻辑线的运行就行了,安装程序开始就是选择安装目录界面或者一键安装界面,这时候没什么处理逻辑,就是实例化一个页面,然后把我们的图片元素都放到对应的位置,没什么技术含量,可以参考代码。
//********************************************************************************************************************** //安装程序主体部分 //使用这个事件函数启动时改变向导或向导页。你不能在它触发之后使用 InitializeSetup 事件函数,向导窗体不退出。 procedure InitializeWizard(); begin //设置页面属性 WizardForm.OuterNotebook.hide; WizardForm.Bevel.Hide; WizardForm.BorderStyle:=bsnone; WizardForm.Position:=poScreenCenter; WizardForm.Width:=650; WizardForm.Height:=508; WizardForm.Color:=clWhite ; WizardForm.OnMouseDown := @WizardMouseDown; //从[Files]段提取指定的文件到临时目录中 ExtractTemporaryFile('btn_n.png'); ExtractTemporaryFile('btn_complete.png'); ExtractTemporaryFile('btn_setup.png'); ExtractTemporaryFile('xy.png'); ExtractTemporaryFile('bigbg.png'); ExtractTemporaryFile('btn_Browser.png'); ExtractTemporaryFile('editbox_bkg.png'); ExtractTemporaryFile('loadingbk.png'); ExtractTemporaryFile('loading.png'); ExtractTemporaryFile('license.png'); ExtractTemporaryFile('loading_pic1.png'); ExtractTemporaryFile('loading_pic2.png'); ExtractTemporaryFile('loading_pic3.png'); ExtractTemporaryFile('loading_pic4.png'); ExtractTemporaryFile('checkbox.png'); ExtractTemporaryFile('checkboxdeep.png'); ExtractTemporaryFile('loading_pic.png'); ExtractTemporaryFile('finish_bg.png'); ExtractTemporaryFile('btn_close.png'); ExtractTemporaryFile('btn_min.png'); //ExtractTemporaryFile('vcredist_x64.exe'); //ExtractTemporaryFile('vcredist_x86.exe'); //ExtractTemporaryFile('ConsoleApplication12.exe'); //初始化背景页面 img_Background:=ImgLoad(WizardForm.Handle,ExpandConstant('{tmp}xy.png'),0,0,650,450,false,true); bool_custom:=true; //初始化编辑框背景 img_EditBox:= ImgLoad(WizardForm.Handle,ExpandConstant('{tmp}editbox_bkg.png'),60,380,436,21,false,false); ImgSetVisibility(img_EditBox,false) //初始化关闭按钮 btn_Close:=BtnCreate(WizardForm.Handle,627,8,17,15,ExpandConstant('{tmp}tn_close.png'),1,False) BtnSetEvent(btn_Close,BtnClickEventID,CallBack_Button(@btn_CloseOnClick,1)); //初始化最小化按钮 btn_Min:=BtnCreate(WizardForm.Handle,607,8,17,15,ExpandConstant('{tmp}tn_min.png'),1,False) BtnSetEvent(btn_Min,BtnClickEventID,CallBack_Button(@btn_MinOnClick,1)); //初始化立即安装按钮 btn_InstallNow:=BtnCreate(WizardForm.Handle,225,308,199,58,ExpandConstant('{tmp}tn_setup.png'),1,False) BtnSetEvent(btn_InstallNow,BtnClickEventID,CallBack_Button(@btn_InstallNowOnClick,1)); //初始化浏览按钮 btn_Browser:=BtnCreate(WizardForm.Handle,520,375,82,32,ExpandConstant('{tmp}tn_Browser.png'),1,False) BtnSetEvent(btn_Browser,BtnClickEventID,CallBack_Button(@btn_BrowserOnClick,1)); BtnSetVisibility(btn_Browser,false) //初始化自定义安装按钮 btn_CustomInstall:=BtnCreate(WizardForm.Handle,560,421,75,15,ExpandConstant('{tmp}tn_n.png'),1,False) BtnSetEvent(btn_CustomInstall,BtnClickEventID,CallBack_Button(@btn_CustomInstallOnClick,1)); //初始化用户协议按钮和选择框 btn_License:=BtnCreate(WizardForm.Handle,100,421,54,15,ExpandConstant('{tmp}license.png'),1,False) BtnSetEvent(btn_License,BtnClickEventID,CallBack_Button(@btn_LicenseOnClick,1)); chk_License:=BtnCreate(WizardForm.Handle,22,421,15,15,ExpandConstant('{tmp}checkboxdeep.png'),4,TRUE) //设置安装进度条监听 PBOldProc:=SetWindowLong(WizardForm.ProgressGauge.Handle,-4,CallBack_ProgressBar(@PBProc,4)); //初始化定时器 js1:=0 js2:=650 timer_Page := TTimer.Create(WizardForm); timer_Page.OnTimer := @func_Timer; //初始化安装路径编辑框 edit_Path:= TEdit.Create(WizardForm); with edit_Path do begin Parent := WizardForm; text :=WizardForm.DirEdit.text; Font.Name:='宋体' BorderStyle:=bsNone; SetBounds(60,385,436,15) OnChange:=@edit_PathChange; Color := $00FFE2D0 TabStop :=false; end; edit_Path.Hide; ShapeForm(WizardForm, WINDDOW_RADIUS); //应用图片变化 ImgApplyChanges(WizardForm.Handle) end;
这里面有些需要注意的点,你要使用的图片资源,要像下面这样设计:
按钮从上到下要有四张,分别对应按钮什么都不做的样子,鼠标悬浮在按钮上方的样子,按钮被按下的样子,还有按钮被禁用的样子。
选择框则对应八张,上面四张是选择框没被选中时候对应鼠标什么都没做的样子,鼠标悬浮在选择框上方的样子,鼠标按下选择框的样子,选择框被禁用的样子。然后下面四张是选择框选中时对应的四种样子。
设计是这样设计的,具体为什么这样设计,就是botva2.dll这个dll的接口要求,我也没深究过。
然后还有一个计数器的概念,这个东西很关键,控制着innosetup的动画部分,后面我们会专门介绍下安装过程中的那个图片滚动动画是怎么做的,那是我们界面美化的最难的部分,需要专门开一个贴讲下逻辑。
注意每次设置完界面都要调用下ImgApplyChanges(WizardForm.Handle),这个会通知页面刷新,不然按钮的创建移位等效果都不会生效。