建立 NSIS 指令碼的習慣: 建立一個 include 目錄,用來儲存安裝用的檔案, .nsi 檔案放在 include 的上級目錄,再在 include 目錄裡建立一個 resource,用來儲存一些資源檔比標、介面位元影像、自己修改的 UI 等等。
然後分析一下官方的安裝程式,嗯嗯,先清空臨時檔案夾,這是為了為了找東西方便,然後啟動安裝程式,再到臨時目錄裡找一個 nxxx.tmp 這樣的目錄,裡面有一些釋放出來的資源 gaydata.ini、modern-header.bmp、classic256.bmp、modern256.bmp、opt2page.ini、opt3page.ini。那幾個位元影像一看就明白,不用解釋,gaydata.ini 呢,裡面有從 sec0 到 sec47 的定義,所以我們可以確定一共有 47 個區段,而且區段的名稱是根據 gaydata.ini 來確定的,如何知道是根據 gaydata.ini 來確定的 的呢,你在安裝程式剛啟動的時候(剛顯示許可頁面的時候)找到臨時的那個目錄(也就是 NSIS 裡的 $PLUGINSDIR 目錄),把一個區段名稱改一下,比如把“Winamp (required)”改為 aaa,等進入組件選擇頁面的時候第一個就是 aaa 了,而如果把“Winamp (required)”清空的話,第一個區段就不見了。 opt2page.ini、opt3page.ini 分別是最後兩個頁面用來選擇串連方式和外觀的。分析後就可以動手了……
1.建立基本的結構
首先在指令碼頭部定義一些版本號碼等值,比如
!define VERSION "5.05"
!define VERSION_NUM "505"
這樣版本號碼變的時候在指令碼頭部改一下就行了,不用在指令碼的每個地方都改
然後定義輸出檔案名,為了方便 full、pro、lite 三個版本切換方便。
!define FILE_NAME "Winamp${VERSION_NUM}_full"
有關定義的說明可以看這裡
再下來就是安裝程式屬性的設定了,必須的設定有
Name "Winamp"
OutFile "${FILE_NAME}.exe"
當然
SetCompressor lzma
應該也是必須的,LZMA 不止壓縮率大很多,而且不太準確的一個屬性是啟動快不少,然後再設定一個區段就構成了主體部分,已經能夠編譯了
Section "主程式"
SectionEnd
2. 插入頁面
首先要
!include "MUI.nsh"
這樣才能使用 NSIS 提供的一些宏來插入頁面,要插入的頁面是
!insertmacro MUI_PAGE_LICENSE "./resource/License.txt"
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
最後還要插入語言
!insertmacro MUI_LANGUAGE English
3. 完善安裝程式屬性設定
當然安裝程式的屬性還要增加一些設定
BrandingText "Nullsoft Install System -- built on ${__DATE__} at ${__TIME__}"
這是設定安裝程式個人標誌的
InstallDir "$PROGRAMFILES/Winamp"
設定一個預設的安裝路徑
InstallDirRegKey HKCU "Software/Winamp" ""
優先讀取註冊表裡儲存的路徑,如果存在就是用註冊表儲存的路徑
4. 設定頁面
表徵圖的定義
!define MUI_ICON "./resource/inst.ico"
!define MUI_UNICON "./resource/uninst.ico"
定義了安裝程式表徵圖和卸載程式表徵圖
!define MUI_HEADERIMAGE
定義在安裝程式頂端顯示一個位元影像
!define MUI_HEADERIMAGE_BITMAP "./resource/modern-header.bmp"
定義要顯示的位元影像,必須是本地機器上的
!define MUI_COMPONENTSPAGE_NODESC
指定組件選擇頁面不使用描述地區
5. 設定頁面文本
!define MUI_LICENSEPAGE_TEXT_TOP "Please read and agree to the license terms below before installing."
指定許可頁面上頂端顯示的文本
!define MUI_COMPONENTSPAGE_TEXT_TOP "This will install Winamp ${VERSION}. This installer contains the full install."
指定組件選擇頁面頂端的文本
!define MUI_DIRECTORYPAGE_TEXT_TOP "Setup has determined the optimal location to install. If you would like to change the folder, do so now."
指定目錄選擇頁面的文本
!define MUI_ABORTWARNING
定義按取消按鈕時,提示是否真的退出
6. 設定安裝類型,並把補全所有的區段
InstType "Full"
InstType "Standard"
InstType "Lite"
InstType "Minimal"
一共四個安裝類型,還有一個 Custom 類型系統會自動添加,不必幹預
然後在建立 46 個區段,一共有 47 個,名稱可以隨便起,因為區段的名稱到後面會由 gaydata.ini 來從命名,比如
Section " "
SectionEnd
7. .onInit 函數
這個函數是在安裝程式 GUI 啟動完畢的時候開始執行裡面的代碼,應該把那些資源檔在這個階段釋放到使用者電腦以供使用
InitPluginsDir
初始化 $PLUGINSDIR 也就是外掛程式目錄
File "/oname=$PLUGINSDIR/gaydata.ini" "./resource/gaydata.ini"
File "/oname=$PLUGINSDIR/opt2page.ini" "./resource/opt2page.ini"
File "/oname=$PLUGINSDIR/opt3page.ini" "./resource/opt3page.ini"
File "/oname=$PLUGINSDIR/classic256.bmp" "./resource/classic256.bmp"
File "/oname=$PLUGINSDIR/modern256.bmp" "./resource/modern256.bmp"
因為在 .onInit 裡使用 File 會使程式啟動時要搜尋很久,所以還應該使用 ReserveFile,ReserveFile 的說明看這裡。
在 !include "MUI.nsh" 上面增加
ReserveFile "./resource/gaydata.ini"
ReserveFile "./resource/opt2page.ini"
ReserveFile "./resource/opt3page.ini"
ReserveFile "./resource/classic256.bmp"
ReserveFile "./resource/modern256.bmp"
ReserveFile "${NSISDIR}/Plugins/InstallOptions.dll"
因為 InstallOptions.dll 在自訂介面要使用,所以也要加入
8. 組件的隱藏和顯示
細心的朋友都看到了指令碼裡面有兩個 !insertmacro MUI_PAGE_COMPONENTS,那麼組件選擇頁面就會出現兩次,察看 gaydata.ini 就知道第一次顯示的是 sec0 到 sec36,第二次顯示的是 sec37 到 sec 47。
關於頁面的說明請看這裡
每個頁面都有三個函數: Pre、Show、Leave,分別是預載入、顯示、離開,在 MUI 介面可以用定義的方法來插入函數,比如在 !insertmacro MUI_PAGE_COMPONENTS 前(插入上一個頁面之後) 定義一個 MUI_PAGE_CUSTOMFUNCTION_PRE 函數就可以插入一個預載入函數。在本次指令碼中在第一個組件選擇頁面作如下定義
!define MUI_PAGE_CUSTOMFUNCTION_PRE ComponentPre
!define MUI_PAGE_CUSTOMFUNCTION_SHOW ComponentShow
上面定義了 ComponentPre、ComponentShow 函數,當然定義的函數名可以隨便起,但一般來說名字都要表達它的含義,便於閱讀。
在開始建立這兩個函數之前還要定義一些內容
!define SECTION_COMPONENT_END 36
!define SECTION_ASSCOIATION_START 37
!define SECTION_TOTAL 47
上面定義了 36 是要安裝的組件最後的區段索引好,37 是檔案關聯等的開始區段索引號,47 是總共的區段數。ComponentPre 函數的內容如下
Function ComponentPre
Push $0
Push $1
Call SectionTextReset
StrCpy $1 0
loop:
ReadINIStr $0 "$PLUGINSDIR/gaydata.ini" "secnames" "sec$1"
StrCmp $0 "" 0 +2
SectionSetText $1 ""
StrCmp $1 ${SECTION_COMPONENT_END} loop_quit
IntOp $1 $1 + 1
Goto loop
loop_quit:
StrCpy $1 ${SECTION_ASSCOIATION_START}
SectionSetText $1 ""
StrCmp $1 ${SECTION_TOTAL} +3
IntOp $1 $1 + 1
Goto -3
Pop $1
Pop $0
FunctionEnd
這個函數調用了 SectionTextReset 函數,SectionTextReset 函數如下
Function SectionTextReset
Push $R0
StrCpy $R0 0
SectionSetText $R0 " "
StrCmp $R0 ${SECTION_TOTAL} +3
IntOp $R0 $R0 + 1
Goto -3
Pop $R0
FunctionEnd
SectionTextReset 函數構成一個迴圈,$R0 從 0 開始遞增,直到等於 ${SECTION_TOTAL} 後跳出迴圈,這個迴圈把所有區段的名稱都重設為空白格,在兩個 MUI_PAGE_COMPONENTS 頁面的預載入函數都調用一次。這是因為 Show 函數會把一些區段隱藏,即把區段名稱設為空白值,在下一個 MUI_PAGE_COMPONENTS 頁面的 Pre 階段必須給它一個名稱,否則它將一直隱藏。
調用了 SectionTextReset 函數之後是一個迴圈,這個迴圈讀取 "$PLUGINSDIR/gaydata.ini" 的 sec0 到 ${SECTION_COMPONENT_END} ,如果某個 sec 讀到的值為空白,則把該區段隱藏,也就是把區段名設為空白值。你可以試試英文原版,剛啟動時把 "$PLUGINSDIR/gaydata.ini" 的 sec0 設為空白值,到了組件選擇頁面 Winamp (required) 區段就被隱藏了。
再下來也是一個迴圈,把 ${SECTION_ASSCOIATION_START} 到 ${SECTION_TOTAL} 的區段隱藏,因為第一個 MUI_PAGE_COMPONENTS 只需要顯示 0 到 ${SECTION_COMPONENT_END} 的區段。ComponentShow 函數如下
Function ComponentShow
Push $0
Push $1
StrCpy $1 0
loop:
ReadINIStr $0 "$PLUGINSDIR/gaydata.ini" "secnames" "sec$1"
SectionSetText $1 $0
StrCmp $1 ${SECTION_COMPONENT_END} loop_quit
IntOp $1 $1 + 1
Goto loop
loop_quit:
Pop $1
Pop $0
FunctionEnd
也是一個迴圈,$1 的值從 0 到 ${SECTION_COMPONENT_END} 遞增,則是依次從 sec0 到 sec36 讀取 gaydata.ini 相應的值,並根據讀取道的值來從命名區段名稱。
第二個組件頁面對應的 AsscoiationPre、AsscoiationShow 與上面的基本一致,只是要隱藏的區段索引不同而已。
9. 隱藏控制項
組件頁面第二次顯示的時候,有幾個控制項是隱藏的,用 Resource Hacker 開啟 ${NSISDIR}/Contrib/UIs/modern.exe 裡面的 104 對話方塊就是組件顯示頁面,要隱藏的空間 ID 為 1017 (顯示安裝類型) 和 1021 (它左邊顯示的文本) 還有 1023 (磁碟空間顯示的控制項)。顯示和隱藏控制項的指令為 ShowWindow ,說明請看這裡。
隱藏控制項的代碼需要加在 AsscoiationShow 函數裡。
FindWindow $0 "#32770" "" $HWNDPARENT
擷取一個視窗控制代碼儲存在 $0 裡
GetDlgItem $1 $0 1017
擷取 1017 控制項的控制代碼
ShowWindow $1 ${SW_HIDE}
隱藏 1017 控制項,其他幾個控制項的隱藏指令依次為
GetDlgItem $1 $0 1021
ShowWindow $1 ${SW_HIDE}
GetDlgItem $1 $0 1023
ShowWindow $1 ${SW_HIDE}
除了控制項隱藏之外,還有兩處文本需要更改,由於使用 !define 只能對第一次顯示的組件頁面更改,所以第二次顯示的文本只能自己用 SendMessage 來改了
GetDlgItem $1 $0 1006
SendMessage $1 ${WM_SETTEXT} 0 "STR:Select which icons you want installed, and whether you want files and CDs associated with"
GetDlgItem $1 $0 1022
SendMessage $1 ${WM_SETTEXT} 0 "STR:Select icons to install and media associations:"