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}\botva2.dll stdcall delayload'; procedure ImgSetVisibility(img :Longint; Visible :boolean); external 'ImgSetVisibility@{tmp}\botva2.dll stdcall delayload'; procedure ImgApplyChanges(h:HWND); external 'ImgApplyChanges@{tmp}\botva2.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}\botva2.dll stdcall delayload'; procedure gdipShutdown; external 'gdipShutdown@{tmp}\botva2.dll stdcall delayload'; function BtnCreate(hParent:HWND; Left,Top,Width,Height:integer; FileName:PAnsiChar; ShadowWidth:integer; IsCheckBtn:boolean):HWND; external 'BtnCreate@{tmp}\botva2.dll stdcall delayload'; procedure BtnSetText(h:HWND; Text:PAnsiChar); external 'BtnSetText@{tmp}\botva2.dll stdcall delayload'; procedure BtnSetVisibility(h:HWND; Value:boolean); external 'BtnSetVisibility@{tmp}\botva2.dll stdcall delayload'; procedure BtnSetFont(h:HWND; Font:Cardinal); external 'BtnSetFont@{tmp}\botva2.dll stdcall delayload'; procedure BtnSetFontColor(h:HWND; NormalFontColor, FocusedFontColor, PressedFontColor, DisabledFontColor: Cardinal); external 'BtnSetFontColor@{tmp}\botva2.dll stdcall delayload'; procedure BtnSetEvent(h:HWND; EventID:integer; Event:Longword); external 'BtnSetEvent@{tmp}\botva2.dll stdcall delayload'; procedure BtnSetCursor(h:HWND; hCur:Cardinal); external 'BtnSetCursor@{tmp}\botva2.dll stdcall delayload'; procedure BtnSetEnabled(h:HWND; Value:boolean); external 'BtnSetEnabled@{tmp}\botva2.dll stdcall delayload'; function GetSysCursorHandle(id:integer):Cardinal; external 'GetSysCursorHandle@{tmp}\botva2.dll stdcall delayload'; function BtnGetChecked(h:HWND):boolean; external 'BtnGetChecked@{tmp}\botva2.dll stdcall delayload'; procedure BtnSetChecked(h:HWND; Value:boolean); external 'BtnSetChecked@{tmp}\botva2.dll stdcall delayload'; procedure CreateFormFromImage(h:HWND; FileName:PAnsiChar); external 'CreateFormFromImage@{tmp}\botva2.dll stdcall delayload'; procedure BtnSetPosition(h:HWND; NewLeft, NewTop, NewWidth, NewHeight: integer); external 'BtnSetPosition@{tmp}\botva2.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}\btn_close.png'),1,False) BtnSetEvent(btn_Close,BtnClickEventID,CallBack_Button(@btn_CloseOnClick,1)); //初始化最小化按鈕 btn_Min:=BtnCreate(WizardForm.Handle,607,8,17,15,ExpandConstant('{tmp}\btn_min.png'),1,False) BtnSetEvent(btn_Min,BtnClickEventID,CallBack_Button(@btn_MinOnClick,1)); //初始化立即安裝按鈕 btn_InstallNow:=BtnCreate(WizardForm.Handle,225,308,199,58,ExpandConstant('{tmp}\btn_setup.png'),1,False) BtnSetEvent(btn_InstallNow,BtnClickEventID,CallBack_Button(@btn_InstallNowOnClick,1)); //初始化瀏覽按鈕 btn_Browser:=BtnCreate(WizardForm.Handle,520,375,82,32,ExpandConstant('{tmp}\btn_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}\btn_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),這個會通知頁面刷新,不然按鈕的創建移位等效果都不會生效。
