杉宫竹苑工作室

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 3471|回复: 0

NSIS 进阶教程

[复制链接]
发表于 2017-12-22 17:53:17 | 显示全部楼层 |阅读模式

正式会员享受无限制浏览网站功能和高速网盘下载,赶快加入本站吧!

您需要 登录 才可以下载或查看,没有账号?立即注册

x
**前言**

在Windows下,有很多人想做一个完全自己把控的安装程序,想过很多种途径去实现,有人说MFC可以实现,有人说C#可以实现,有人说Delphi可以实现,有人说VB又未尝不可呢。MFC,Delphi,VB,C#都需要自己去实现打包压缩,释放,释放过程中的业务逻辑跟界面功能,是一项比较麻烦的工作,甚至于C#程序需要运行的话,还需要装dotnet Framework的runtime。NSIS制作的安装包可以运行在Win9x下,完全是WinAPI的调用,不需要额外装任何的runtime,安装包双击就能运行,本身封装了很多Win的函数,方便调用与开放接口。功能部分也是实现了基本的安装过程所需的操作,NSIS的很多Editor做到了向导模式的脚本生成,很是方便。

本篇主要讲讲以下几点:
如何消除普通的NSIS脚本生成的窗体的边框
如何使得无边框窗体能够移动
如何给这个窗体贴上一张大小合适的背景图

所用到的NSIS插件:
nsDialogs
nsWindows
WinProc
System

题外话,本来想用新浪爱问做文件分享平台的,发现上传后一直在审核中……练葵花宝典能力谁也比过性浪呀,用CSDN也不好,还要登录,本人就因为积分太少而不得不去做无聊的工作赢得积分,用于CSDN下载,自从CSDN把我的密码明码保存还被黑客给搞了之后,我不再上此网站。115虽然下载页广告多的一笔,但是后台上传页相当的干净,还不用审核以及无登陆下载,极致方便大家。(115被政府搞了,转性浪爱问)

使用时:把插件DLL跟头文件分别放入到你的NSIS本地对应的安装目录中,然后编译源码即可。

下文都用%NSIS_Install_DIR%来替代你本地安装路径
去除窗体Border

在去除窗体边框之前有一项工作是必须做的,那就是更改默认窗体的大小,因为每个人想做的打包窗体不可能都一样大,更改窗体大小有两种方法,也可以两种方法并用

修改NSIS内部的UI

NSIS的默认UI放在"%NSIS_Install_DIR%\Contrib\UIs"中,其中常常见到的创建自定义窗体的1018,1044都在此路径的modern.exe中。我们只要修改modern.exe里面的资源文件即可,做过MFC的都知道,VC在创建程序的时候是有Resources的,只要找到一些能更改Resources里面Dialog的工具即可,本文推荐ResHacker 。

修改的时候宁可大点,也绝不小,因为开发过程中我遇到用nsWindows命令扩大窗体的时候,出现不起作用的情况,但是默认窗体比需要的窗体小的时候可以用nsWindows命令控制。

打开ResHacker工具拖入modern.exe,操作前请备份modern.exe,拖动资源窗体或者直接修改你想要的大小。默认的1044跟1018窗体都在105分类下。
1.jpeg 2.jpeg
b、通过nsWindows命令

  1. nsDialogs::Create 1044
  2.     Pop $0
  3.     ${If} $0 == error
  4.         Abort
  5.     ${EndIf}
  6.     SetCtlColors $0 ""  transparent ;背景设成透明
  7.                                                                                                         
  8.     ${NSW_SetWindowSize} $HWNDPARENT 513 354 ;改变窗体大小
  9.     ${NSW_SetWindowSize} $0 513 354 ;改变Page大小
复制代码

该脚本添加在自定义窗体的创建Function中,创建的是1044类型窗口,修改命令是两条,分别是对$HWNDPARENT的窗体跟创建的1044page的修改,确保默认的modern.exe的窗口大小比这个要大!
修改好窗体大小后,直接在初始化的Function中直接填入以下代码即可去除边框
  1. Function onGUIInit
  2.     ;消除边框
  3.     System::Call `user32::SetWindowLong(i$HWNDPARENT,i${GWL_STYLE},0x9480084C)i.R0`
  4.     ;隐藏一些既有控件
  5.     GetDlgItem $0 $HWNDPARENT 1034
  6.     ShowWindow $0 ${SW_HIDE}
  7.     GetDlgItem $0 $HWNDPARENT 1035
  8.     ShowWindow $0 ${SW_HIDE}
  9.     GetDlgItem $0 $HWNDPARENT 1036
  10.     ShowWindow $0 ${SW_HIDE}
  11.     GetDlgItem $0 $HWNDPARENT 1037
  12.     ShowWindow $0 ${SW_HIDE}
  13.     GetDlgItem $0 $HWNDPARENT 1038
  14.     ShowWindow $0 ${SW_HIDE}
  15.     GetDlgItem $0 $HWNDPARENT 1039
  16.     ShowWindow $0 ${SW_HIDE}
  17.     GetDlgItem $0 $HWNDPARENT 1256
  18.     ShowWindow $0 ${SW_HIDE}
  19.     GetDlgItem $0 $HWNDPARENT 1028
  20.     ShowWindow $0 ${SW_HIDE}
  21. FunctionEnd
复制代码

用System::Call命令调用SetWindowLong的API函数改变GWL_STYLE的样式即可,System是NSIS官方插件用于帮助用户调用系统函数,是相当重要的自动安装程序的插件!程序中其余的代码是把创建的1004页面上其余的控件给隐藏掉,后面携带的ID都是可以通过ResHacker在105包中查询到。

2、贴一张大小合适的背景图
贴图需要用到nsDialogs插件的命令:
  1. ;贴背景大图
  2.     ${NSD_CreateBitmap} 0 0 100% 100% ""
  3.     Pop $BGImage
  4.     ${NSD_SetImage} $BGImage $PLUGINSDIR\bg.bmp $ImageHandle
  5.                                                                   
  6.     ${NSD_FreeImage} $ImageHandle
复制代码

${NSD_CreateBitmap}命令创建一个跟窗体一样大小的图片区域,后面的五个参数分别是x,y,width,height,text,坐标,宽高,文字。紧接着给这张图贴上一张合适的图片bg.bmp,贴图片之前需要把这个图片打包到安装程序中,这个是基本的操作,源码包中有,这里就不做说明了。最后还要通过${NSD_FreeImage}去释放该图片内存区。
3、无标题移动

做到无标题移动的潜台词是把原本传递给标题栏的Message通过你定义的元素回调传递给标题栏,所以只要给你添加的资源加上传递信息的回调函数就可以了。这里是通过WinProc这个插件完成的,WinProc这个插件在官方的插件库中没有,Google一下就可以查询到,这里的源码包中也有。除了WinProc,第三方插件SkinBtn也可以帮助实现。

  1. Function onGUICallback
  2.   ${If} $MSG = ${WM_LBUTTONDOWN}
  3.     SendMessage $HWNDPARENT ${WM_NCLBUTTONDOWN} ${HTCAPTION} $0
  4.   ${EndIf}
  5. FunctionEnd
复制代码
以上是回调函数,判断鼠标左键的Down事件,并且传递消息给标题栏。

  1. GetFunctionAddress $0 onGUICallback
  2.     WndProc::onCallback $BGImage $0 ;处理无边框窗体移动
复制代码

以上是把当前的$BGImage作为回调主体,当用户左键点击$BGImage的时候,消息就传递给了窗体标题栏,实现了无边框的移动。

3.jpeg
有一个注意点:贴图的时候注意了!代码的运行是自上而下,如果要贴的图需要在另一张上面的话,需要把代码写在前面。
比如:

  1. ;贴小图
  2.     ${NSD_CreateBitmap} 0 34 100% 100% ""
  3.     Pop $MiddleImage
  4.     ${NSD_SetImage} $MiddleImage $PLUGINSDIR\middle.bmp $ImageHandle
  5.                   
  6.     ;贴背景大图
  7.     ${NSD_CreateBitmap} 0 0 100% 100% ""
  8.     Pop $BGImage
  9.     ${NSD_SetImage} $BGImage $PLUGINSDIR\bg.bmp $ImageHandle
复制代码


在上一节中我们粗略的处理一下无边框窗体、背景贴图、鼠标移动。这节主要是创建用于响应事件的Button以及能展示软件License的窗口,还能用Button控制软件协议的展示与否。

代码还是延续上一节的。

本篇主要讲讲以下几点:
如何创建一个自己的按钮
如何创建一个自己的License窗口
如何用自己的按钮控制自己的License窗口事件

所用到的插件:
nsDialogs
SkinBtn

本文还是在安装程序的首张Welcome中执行,上一节例子中的背景图需要做适当的修改,因为原本需要创建Button的地方背后有图,适当用PS去抹去就好。本文的前期工具就是PS了一部分元素。加入了几张Button的背景图。

1、创建一个属于自己Design的Button
  1. ;下一步
  2.     ${NSD_CreateButton} 319 303 88 25 ""
  3.         Pop $Btn_Next
  4.         StrCpy $1 $Btn_Next
  5.         Call SkinBtn_Next
  6.         GetFunctionAddress $3 onClickNext
  7.     SkinBtn::onClick $1 $3
复制代码
原本nsDialogs插件就可以用${NSD_CreateButton}命令创建一个Button按钮出来,可惜的是此Button按钮默认的有点丑。

下面就要说到如何改观Button的样式,再接触之前我一直想找有没有用一张图片来替代Button的区域,并且给Image附上事件之类的想法。等到刘若英的后来,我发现有一个SkinBtn的插件可以完成此类工作。

SkinBtn是我在梦想吧中下载的一个第三方插件,国人开发,支持Button的五种状态:Normal,Hover,Click,Disabled,Focus,正常、鼠标悬浮、单击时、不可用时、获得焦点时。这五种状态已经完全够用了。

所要注意的就是SkinBtn的五种状态图需要竖行排列,宽高没有限制,但是高度像素最好的5的倍数(我猜是为了整除,方便取图)下面贴一张示例Button的贴图:

4.jpg
SkinBtn的用法是这样的:

在.onInit的初始化图片资源,并且要用SkinBtn插件的方法初始化一下:
File `/ONAME=$PLUGINSDIR\btn_next.bmp` `images\btn_next.bmp`
SkinBtn::Init "$PLUGINSDIR\btn_next.bmp"
紧接着Call以下方法,给当前的Button附上定义的图形:

  1. Function SkinBtn_Next
  2.   SkinBtn::Set /IMGID=$PLUGINSDIR\btn_next.bmp $1
  3. FunctionEnd
复制代码
现在窗体中的按钮已经被处理成自己想要的效果了,这时候衣服算是穿起来了,我们再试一试他的功能怎么样,我们定义一个onClickNext的function,然后调用SkinBtn插件的onClick方法进行调用:

  1. GetFunctionAddress $3 onClickNext
  2.     SkinBtn::onClick $1 $3
  3. ;下一步按钮事件
  4. Function onClickNext
  5.   MessageBox MB_OK "下一步"
  6.   Abort
  7. FunctionEnd
复制代码
单击事件里面只做了弹出消息框这么一个临时的操作,后面的教程中会说明怎么弹到另一张page中,今天这里不谈。

这样一个自己的贴图的Button,从外观到功能都已经完成了。

4、创建显示License内容的控件

关于License的框如何显示的问题,NSIS的默认窗体有一个专门的协议Page,不过在我们的自定义Page中就不说那个了,在我们的Welcome页面中加入一个协议显示的窗口,这时候需要准备一个协议按钮的贴图,协议窗口。
5.png

协议的按钮样子看上去是超链接,我们可以通过Button贴图去实现,不要一味认死理去创建超链接的命令。因为有两种状态,所以有两份贴图。

图片预载入跟贴图的步骤就不说了,这里初始化该Button的时候是默认的箭头向上的图,我们只要额外的创建一个变量来控制点击的状态,并且在按钮点击事件中重新给Button贴上第二张箭头向上的图就可以实现按钮的贴图跟功能的统一:

  1. Function SkinBtn_Agreement1
  2.   SkinBtn::Set /IMGID=$PLUGINSDIR\btn_agreement1.bmp $1
  3. FunctionEnd
  4.                                                                                                   
  5. Function SkinBtn_Agreement2
  6.   SkinBtn::Set /IMGID=$PLUGINSDIR\btn_agreement2.bmp $1
  7. FunctionEnd
  8.                                                                                                   
  9. ;协议按钮事件
  10. Function onClickAgreement
  11.     ${IF} $Bool_License == 1
  12.         IntOp $Bool_License $Bool_License - 1
  13.         StrCpy $1 $Btn_Agreement
  14.         Call SkinBtn_Agreement1
  15.     ${ELSE}
  16.         IntOp $Bool_License $Bool_License + 1
  17.         StrCpy $1 $Btn_Agreement
  18.         Call SkinBtn_Agreement2
  19.     ${EndIf}
  20. FunctionEnd
复制代码
以上代码中,onClickAgreement方法是Button的点击响应事件,变量Bool_License是控制显示那张Button背景图的控制单元,它的初始值是0,IF判断中写的是当前状态下需要处理的事件。IntOp命令是执行加减运算。

这样,一个根据点击来更改背景图片的按钮就做好了。

5、用自定义的按钮去控制License窗口的显示

在做License之前首先要准备一份License的文件,NSIS命令中有读取文件,显示文件内容的命令,该文件可以是Txt,也可以是带有格式的RTF,这里示例中选用RTF文件。在源码包的RTF协议用的KuGou的。而用于显示RTF的控件则是nsDialogs插件提供的RichEdit20A,还要注意引入头文件LoadRTF.nsh
;读取RTF的文本框
    nsDialogs::CreateControl "RichEdit20A" \
    ${ES_READONLY}|${WS_VISIBLE}|${WS_CHILD}|${WS_TABSTOP}|${WS_VSCROLL}|${ES_MULTILINE}|${ES_WANTRETURN} \
    ${WS_EX_STATICEDGE} \
    5 44 500 203 ''
    Pop $Txt_License
    ${LoadRTF} '$PLUGINSDIR\license.rtf' $Txt_License
    ShowWindow $Txt_License ${SW_HIDE}
以上代码就是创建读取RTF文件的多行文本框,并且设定了一些常用的属性,具体的样式属性可以通过查询nsDialogs插件的官方文件可以得到,我们让该控件默认是不显示的状态。

这样一个,协议的显示控件就创建完毕了。

创建好了协议控件$Txt_License,那么接着就是用刚才创建的Button控制该窗口的显示与否,修改刚才的代码:

  1. ;协议按钮事件
  2. Function onClickAgreement
  3.     ${IF} $Bool_License == 1
  4.         ShowWindow $MiddleImage ${SW_SHOW}
  5.         ShowWindow $Txt_License ${SW_HIDE}
  6.         IntOp $Bool_License $Bool_License - 1
  7.         StrCpy $1 $Btn_Agreement
  8.         Call SkinBtn_Agreement1
  9.     ${ELSE}
  10.         ShowWindow $MiddleImage ${SW_HIDE}
  11.         ShowWindow $Txt_License ${SW_SHOW}
  12.         IntOp $Bool_License $Bool_License + 1
  13.         StrCpy $1 $Btn_Agreement
  14.         Call SkinBtn_Agreement2
  15.     ${EndIf}
  16. FunctionEnd
复制代码
在事件当中,加入隐藏中间背景图跟显示License窗口的代码,这样一个修改Button贴图跟控制协议窗口显示与否的关联就做好了。

6.png
上一节中我们处理了Button的自定义以及Button的事件消息、协议框的创建等等,这节中我们要更加完美的要求我们的提示框也要漂亮,CheckBox也要自定义样式。有人说MessageBox在NSIS默认情况下是带边框的API窗口,是一个比较丑雏形,但是NSIS的nsDialogs插件也没有提供一个可以创建弹出窗的命令行呀,CheckBox系统自带的只能是那个默认的皮肤,如果想把CheckBox的背景改成蓝色,或者把CheckBox的勾改成爱心,把叉改成骷髅头如何去做呢?这些问题在任何一个面向对象的编程语言中都可以很容易实现,在NSIS这样局限的环境中也可以变通实现,接下来我们处理一下这些问题。

本篇主要讲讲以下几点:
如何抛弃系统的MessageBox
如何实现自定义页面的跳转
如何用自己的贴图实现CheckBox功能

所用到的插件【新增】:
FindProcDLL(查找当前进程插件)
KillProcDLL (关闭指定进程插件)

题外话,本节的重点是如何把系统的MessageBox替换掉自定义的窗口,一开始这个命题挺难的,原本因为nsDialogs可以创建一个弹窗,然后给它披上一层皮就可以了,在查了很多资料后,我了解到根本不行,nsDialogs插件不能完成这么简单的工作,而是通过另一个插件nsWindows来完成。另一个自定义CheckBox功能还是有点讨巧的,其实在窗体编程中,Button跟CheckBox本质上是一样的东西,CheckBox就是Button披上一层皮加上一些单击事件改变皮肤而存在的,于是就有了CheckBox的自定义的可能性。

还是要讲一点不足,此处CheckBox不包含旁边的说明文字,也就是说,当你促发CheckBox旁边的文字某些事件的时候CheckBox本事不会有任何反应,这样就不能构成一个CheckBox的整体,不过对于要求不是非常高的人来说,这点完全可以忽略。

1、如何抛弃系统的MessageBox

通常在使用警告框的时候,我们会调用MessageBox的NSIS命令,然而此窗口无比丑陋,简直是对我们正在做的窗体的亵渎。于是我们自己创建警告框,此警告框也要跟我们已有的窗口风格一致,包含几大问题,“无边框”、“美观贴图”、“移动”、“逻辑判断功能”,做到以上几点就可以了。上代码:

  1. Function onCancel
  2.     IsWindow $WarningForm Create_End
  3.     !define Style ${WS_VISIBLE}|${WS_OVERLAPPEDWINDOW}
  4.     ${NSW_CreateWindowEx} $WarningForm $hwndparent ${ExStyle} ${Style} "" 1018
  5.                                                                                                                               
  6.     ${NSW_SetWindowSize} $WarningForm 349 184
  7.     EnableWindow $hwndparent 0
  8.     System::Call `user32::SetWindowLong(i$WarningForm,i${GWL_STYLE},0x9480084C)i.R0`
  9.     ${NSW_CreateButton} 148 122 88 25 ''
  10.     Pop $R0
  11.     StrCpy $1 $R0
  12.     Call SkinBtn_Quit
  13.     ${NSW_OnClick} $R0 OnClickQuitOK
  14.                                                                                                                               
  15.     ${NSW_CreateButton} 248 122 88 25 ''
  16.     Pop $R0
  17.     StrCpy $1 $R0
  18.     Call SkinBtn_Cancel
  19.     ${NSW_OnClick} $R0 OnClickQuitCancel
  20.                                                                                                                               
  21.     ${NSW_CreateBitmap} 0 0 100% 100% ""
  22.     Pop $BGImage
  23.   ${NSW_SetImage} $BGImage $PLUGINSDIR\quit.bmp $ImageHandle
  24.     GetFunctionAddress $0 onWarningGUICallback
  25.     WndProc::onCallback $BGImage $0 ;处理无边框窗体移动
  26.   ${NSW_CenterWindow} $WarningForm $hwndparent
  27.     ${NSW_Show}
  28.     Create_End:
  29.   ShowWindow $WarningForm ${SW_SHOW}
  30. FunctionEnd
复制代码
这个onCancel方法是在我们在主窗体上点击“取消”或者“关闭”的时候促发的方法,这时会弹出一个命令窗口出来。

创建跟设置样式:WS_VISIBLE  是显示的意思,不加会隐藏掉,WS_OVERLAPPEDWINDOW  是创建一个层叠窗体的属性也需要加上。NSW_CreateWindowEx  是创建窗体的主命令。

创建完窗体后,用NSW_SetWindowSize  更改一下窗体的大小。

EnableWindow $hwndparent 0 这段代码一定要加上,这是创建一个警告框模式窗体的必要条件,就是让主窗体不能操作。

接下来就是消除边框,用nsWindows命令创建按钮的一套,跟nsDialogs的创建是一致的,包括无边框移动的一套,在第一节中已经讲过,这里就不啰嗦了。

最后把这个窗体展示出来就可以了。把这个onCancel这个方法赋给“取消”按钮的单击事件,这样一个无边框模式窗体警告框就好了。

7.jpeg
当我们点击“退出”的时候,就要关掉当前程序,所以要添加一个关闭的方法:
  1. Function onClickClose
  2.     FindProcDLL::FindProc "test.exe"
  3.     Sleep 500
  4.     Pop $R0
  5.     ${If} $R0 != 0
  6.     KillProcDLL::KillProc "test.exe"
  7.     ${EndIf}
  8. FunctionEnd
复制代码
通过FindProcDLL插件的FindProc方法找到安装进程,并且通过KillProcDLL插件的KillProc杀之。

当我们点击“取消”的时候,这时候需要关闭当前警告框,并且让主窗体能够处于Active状态

Function OnClickQuitCancel
  ${NSW_DestroyWindow} $WarningForm
  EnableWindow $hwndparent 1
  BringToFront
FunctionEnd
NSW_DestroyWindow 命令销毁掉警告框,使主窗体能活动,并且Bring到前端。

2、如何实现自定义页面的跳转

一开始我们就定义了两个自定义页面:
Page custom WelcomePage
Page custom InstallationPage
从WelcomePage跳转到InstallationPage,这个就是“下一步”按钮的事件。

创建一个RelGotoPage方法:

  1. Function RelGotoPage
  2.   IntCmp $R9 0 0 Move Move
  3.     StrCmp $R9 "X" 0 Move
  4.       StrCpy $R9 "120"
  5.   Move:
  6.   SendMessage $HWNDPARENT "0x408" "$R9" ""
  7. FunctionEnd
复制代码
详细解释在这里 http://nsis.sourceforge.net/Go_to_a_NSIS_page

If a number > 0: Goes foward that number of pages. Code of that page will be executed, not returning to this point. If it is bigger than the number of pages that are after that page, it simulates a "Cancel" click.

If a number < 0: Goes back that number of pages. Code of that page will be executed, not returning to this point. If it is bigger than the number of pages that are before that page, it simulates a "Cancel" click.

If X: Simulates a "Cancel" click. Code will go to callback functions, not returning to this point.

If 0: Continues on the same page. Code will still be running after the call.

在“下一步”的单击事件中加入以下代码,跳转就好了,而且以后的调转RelGotoPage都适用

  1. Function onClickNext
  2.   StrCpy $R9 1
  3.   Call RelGotoPage
  4.   Abort
  5. FunctionEnd
复制代码

3\如何用自己的贴图实现CheckBox功能

CheckBox也有系统自带的那种健全的,但是效果没有Button贴皮肤后好,所以弃用之,在第二个页面中创建几个Button跟标签:

  1. ${NSD_CreateButton} 26 150 15 15 ""
  2.     Pop $Ck_ShortCut
  3.     StrCpy $1 $Ck_ShortCut
  4.     Call SkinBtn_Checked
  5.     GetFunctionAddress $3 OnClick_CheckShortCut
  6.     SkinBtn::onClick $1 $3
  7.     StrCpy $Bool_ShortCut 1
  8.     ${NSD_CreateLabel} 45 151 100 15 "添加桌面快捷方式"
  9.     Pop $Lbl_ShortCut
  10.     SetCtlColors $Lbl_ShortCut ""  transparent ;背景设成透明
复制代码

创建CheckBox的时候要考虑周全,首先要定义该CheckBox的变量,该CheckBox的皮肤,记录该CheckBox状态的变量$Bool_ShortCut ,该CheckBox旁边的提示文字变量$Lbl_ShortCut
8.png
Button的贴图跟变换方式在第二节中已经介绍,这里就不啰嗦。

这里的OnClick_CheckShortCut 方法不仅仅是一个变换的实现,也通过$Bool_ShortCut记录了当前CheckBox的状态

  1. Function OnClick_CheckShortCut
  2.   ${IF} $Bool_ShortCut == 1
  3.         IntOp $Bool_ShortCut $Bool_ShortCut - 1
  4.         StrCpy $1 $Ck_ShortCut
  5.         Call SkinBtn_UnChecked
  6.     ${ELSE}
  7.         IntOp $Bool_ShortCut $Bool_ShortCut + 1
  8.         StrCpy $1 $Ck_ShortCut
  9.         Call SkinBtn_Checked
  10.     ${EndIf}
  11. FunctionEnd
复制代码
最后我们在完成安装的时候,可以通过$Bool_ShortCut该变量来了解用户的选择

9.jpeg
上一节中我们已经处理了有关CheckBox自定义贴图的部分,但是目录选择的部分还没有加上,这节,我们先处理一下目录的选择部分,选择完路径之后就剩下安装了,于是进度条的创建的显得很有必要,但是系统的进度条创建简单,如何改变进度条的背景色跟进度色呢,这节我们也处理掉。有关图片切换的效果的插件也有很多,但是大部分都是基于默认安装窗体进行的,如何在一个完全自定义的页面上面创建一个图片切换效果呢,这节也可以得到答案。

本篇主要讲讲以下几点:
创建目录选择按钮与文本框
创建自定义的进度条
创建图片切换效果

所用到的插件【新增】:
WebCtrl
SkinProgress
BgWorker

这次的教程基本是安装界面的最后的阶段的处理了,主要是创建一个目录选择控件,用于确定安装程序安装的路径,在安装过程中等待时间也是比较长的,遇到打广告的好时机不要错过,切换多图是个不错的选择;在安装过程中,释放文件是一个主要动作。如果不是自定义页面,该动作是在系统的安装页面的Section里面完成的,如果不做多线程处理,该动作会阻塞主线程也就是主界面的消息传递,主界面没有了消息传递也就不会响应拖动、点击关闭等等操作,这个是本节主要说明的地方。最后说一下图片切换的实现,以我目前尝试的,效果最好的还算是直接放一张网页最实在,通过网页的js实现切换效果。

1、目录选择框

目录选择框包括一个文本框跟一个Button按钮,做过Web的人都知道,html中有一个file的控件与之相吻合,在form中则是两个不同的component,文本框很好创建,按钮的单击事件是一个重点,我们看看代码:

  1. ;更改目录控件创建
  2.     ${NSD_CreateDirRequest} 26 79 358 25 "$INSTDIR"
  3.     Pop $Txt_Browser
  4.     ${NSD_OnChange} $Txt_Browser OnChange_DirRequest
  5.                                                                                                                                                                         
  6.     ${NSD_CreateBrowseButton} 400 79 88 25 ""
  7.     Pop $Btn_Browser
  8.     StrCpy $1 $Btn_Browser
  9.     Call SkinBtn_Browser
  10.     GetFunctionAddress $3 OnClick_BrowseButton
  11.       SkinBtn::onClick $1 $3
复制代码
这里创建了一个$Txt_Browser的文本框,一个$Btn_Browser的按钮,其中按钮的单击事件是 OnClick_BrowserButton,看看按钮的事件代码:

  1. Function OnClick_BrowseButton
  2.   Pop $0
  3.                                                                                                                                                                  
  4.   Push $INSTDIR
  5.   Call GetParent
  6.   Pop $R0
  7.                                                                                                                                                                  
  8.   Push $INSTDIR
  9.   Push ""
  10.   Call GetLastPart
  11.   Pop $R1
  12.                                                                                                                                                                  
  13.   nsDialogs::SelectFolderDialog "请选择 $R0 安装的文件夹:" "$R0"
  14.   Pop $0
  15.   ${If} $0 == "error"
  16.     Return
  17.   ${EndIf}
  18.   ${If} $0 != ""
  19.     StrCpy $INSTDIR "$0\$R1"
  20.     system::Call `user32::SetWindowText(i $Txt_Browser, t "$INSTDIR")`
  21.   ${EndIf}
  22. FunctionEnd
  23.                                                                                                                                                             
  24. ;得到选中目录用于拼接安装程序名称
  25. Function GetParent
  26.   Exch $R0
  27.   Push $R1
  28.   Push $R2
  29.   Push $R3
  30.   StrCpy $R1 0
  31.   StrLen $R2 $R0
  32.   loop:
  33.     IntOp $R1 $R1 + 1
  34.     IntCmp $R1 $R2 get 0 get
  35.     StrCpy $R3 $R0 1 -$R1
  36.     StrCmp $R3 "" get
  37.     Goto loop
  38.   get:
  39.     StrCpy $R0 $R0 -$R1
  40.     Pop $R3
  41.     Pop $R2
  42.     Pop $R1
  43.     Exch $R0
  44. FunctionEnd
  45.                                                                                                                                                                  
  46.                                                                                                                                                            
  47. ;截取选中目录
  48. Function GetLastPart
  49.   Exch $0 ; chop char
  50.   Exch
  51.   Exch $1
  52.   Push $2
  53.   Push $3
  54.   StrCpy $2 0
  55.   loop:
  56.     IntOp $2 $2 - 1
  57.     StrCpy $3 $1 1 $2
  58.     StrCmp $3 "" 0 +3
  59.       StrCpy $0 ""
  60.       Goto exit2
  61.     StrCmp $3 $0 exit1
  62.     Goto loop
  63.   exit1:
  64.     IntOp $2 $2 + 1
  65.     StrCpy $0 $1 "" $2
  66.   exit2:
  67.     Pop $3
  68.     Pop $2
  69.     Pop $1
  70.     Exch $0
  71. FunctionEnd
复制代码

单击按钮的时候用nsDialogs创建一个SelectFolderDialog的对话框,选择需要安装的路径。GetParent方法主要是要取得当前选中的路径,GetLastPart主要是保留当前的安装程序最底层的目录名称,最终两个部分合起来作为整体的安装路径赋值给$INSTDIR。具体的效果可以运行源码查看。            

2、创建自定义进度条

nsDialogs有自带的进度条,该进度条的颜色是那种系统的颜色,如果遇到自己设计的,就会出现问题,于是用SkinProgress就可以给进度条赋上两张图片,一张是底图,一张是进度图,虽然不是非常的完美,比如圆角,比如透明,比如阴影,比如……但是已经是跟NSIS快捷脚本配合的很好的了。

  1. ${NSD_CreateProgressBar} 24 265 474 7 ""
  2.     Pop $PB_ProgressBar
  3.     SkinProgress::Set $PB_ProgressBar "$PLUGINSDIR\loading2.bmp" "$PLUGINSDIR\loading1.bmp"
复制代码
用法非常简单,用nsDialogs创建一个ProgressBar,然后用SkinProgress去set一下,后面跟上两张图片。ProgressBar的图片是有讲究的,具体的可以看源码的image文件夹中两张图片的切图,基本要使用什么效果,自己做两张图就可以了。                           

3、创建图片切换

图片的切换有很多种,gif、flash、js、甚至自己用c++做插件实现,这里提供一种很方面,但是又不失功能强大的方式。网页的形式!你很容易能自己写一段js实现多张图片的切换,这里唯一要解决的就是如何在Form上创建一个浏览器用于加载自己的本地html页面。

  1. System::Call `*(i,i,i,i)i(1,34,518,200).R0`
  2.     System::Call `user32::MapDialogRect(i$HWNDPARENT,iR0)`
  3.     System::Call `*$R0(i.s,i.s,i.s,i.s)`
  4.     System::Free $R0
  5.     FindWindow $R0 "#32770" "" $HWNDPARENT
  6.     System::Call `user32::CreateWindowEx(i,t"STATIC",in,i${DEFAULT_STYLES}|${SS_BLACKRECT},i1,i34,i518,i200,iR0,i1100,in,in)i.R0`
  7.     StrCpy $WebImg $R0
  8.     WebCtrl::ShowWebInCtrl $WebImg "$PLUGINSDIR/index.htm"
复制代码
首先在界面上创建一个STATIC的对象(对话框),通过MapDialogRect来定位该对话框的位置(1,34),大小(518,200),然后通过WebCtrl控件来把自己的网页index.htm加载进去,WebCtrl插件实现的就是调用本地浏览器。这里定位浏览器的位置以及大小是难点,还需多多熟悉才是。

网页里面的代码我就不讲解了,主要是图片js切换,你也可以更换js代码,网络中很多效果都有。

如果发现界面上的图片切换画面跟外边框有距离,就去查看网页里面的css,是否把margin跟padding都设成了0,这样就没有间隙了,看上去跟贴在form上面的一样。:)                                    

4、多线程安装

界面都画好了,现在说到安装文件的时候释放了。源码中我是通过sleep来模拟的,具体的释放跟这个差不多。首先创建的是一个只运行一次的定时器Timer:

  1. GetFunctionAddress $0 NSD_TimerFun
  2.     nsDialogs::CreateTimer $0 1
  3. 其中nsDialogs::CreateTimer后面的参数1代表的是1毫秒,正常情况下就是1毫秒执行一次NSD_TimerFun介个方法。

  4. Function NSD_TimerFun
  5.     GetFunctionAddress $0 NSD_TimerFun
  6.     nsDialogs::KillTimer $0
  7.     !if 1   ;是否在后台运行,1有效
  8.         GetFunctionAddress $0 InstallationMainFun
  9.         BgWorker::CallAndWait
  10.     !else
  11.         Call InstallationMainFun
  12.     !endif
  13. FunctionEnd
复制代码
本身定时器就是一个异步的功能,如果定时器是同步的,那就没有意义了对不,那么既然是异步的,为什么在NSD_TimerFun里面做Sleep操作的时候,主界面会卡死不响应呢。这个就要看看nsDialogs插件如何实现这个定时器的了,我也没有详查,应该不是创建子线程Thread 的方式。

这里如果抛弃Timer,直接使用BgWorker的话,也可以,但是会有一个缺陷:当你需要同时运行两个方法的时候,只有通过创建两个Timer来实现,并且在Timer调用的方法里面采用BgWorker来实现子线程操作。

看看上面代码在Timer执行方法里面,第一步是停止Timer:因为我们只用了他的异步的特点,并没有想做定时器。然后使用BgWorker插件。

使用BgWorker插件非常简单,只需用$0接收方法地址,然后调用BgWorker::CallAndWait。顾明思意,BgWorker调用过程是同步的,而且会Wait到InstallationMainFun方法执行结束,如果BgWorker::CallAndWait调用下面还有代码的话,只有在执行完InstallationMainFun方法后才能执行。

下面看看InstallationMainFun的实现:

  1. Function InstallationMainFun
  2.     SendMessage $PB_ProgressBar ${PBM_SETRANGE32} 0 100
  3.     SendMessage $PB_ProgressBar ${PBM_SETPOS} 10 0
  4.     Sleep 1000
  5.     SendMessage $PB_ProgressBar ${PBM_SETPOS} 20 0
  6.     Sleep 1000
  7.     SendMessage $PB_ProgressBar ${PBM_SETPOS} 30 0
  8.     Sleep 1000
  9.     SendMessage $PB_ProgressBar ${PBM_SETPOS} 40 0
  10.     Sleep 1000
  11.     SendMessage $PB_ProgressBar ${PBM_SETPOS} 50 0
  12.     Sleep 1000
  13.     SendMessage $PB_ProgressBar ${PBM_SETPOS} 60 0
  14.     Sleep 1000
  15.     SendMessage $PB_ProgressBar ${PBM_SETPOS} 70 0
  16.     Sleep 1000
  17.     SendMessage $PB_ProgressBar ${PBM_SETPOS} 80 0
  18.     Sleep 1000
  19.     SendMessage $PB_ProgressBar ${PBM_SETPOS} 90 0
  20.     Sleep 1000
  21.     SendMessage $PB_ProgressBar ${PBM_SETPOS} 100 0
  22.                                    
  23.     ShowWindow $Btn_Next ${SW_SHOW}
  24.     ShowWindow $Btn_Install ${SW_HIDE}
  25.     EnableWindow $Btn_Cancel 0
  26. FunctionEnd
复制代码
所做的工作主要是操作滚动条的位置。在实际的释放过程中,这个设置也是这样的,目前还没有自定义页面的释放插件,能回调得到精确的释放量,以及释放文件等等信息。所以只能模拟,尽可能的细化下来,估计着大概多少。这个是一个需要情商的工作,如果你有洁癖,偏执之类的病,我想你还是用系统自带的释放好了,也不需要自定义页面了 。
10.jpeg
11.jpeg
12.jpeg
在线下载,下载后的安装,本地解压安装
前言
想做在线安装包教程已经很久了,一直找不到合适的时间,如今在一个慵懒的周末早晨终于开始了。今天晚上还要去虹口体育场看申花跟国安的比赛,还是要抓紧时间呀。 在线安装是以前教程的一个延续。

意义一:国内很多的IT厂商在做安装包的时候首先考虑的是用户下载的时间可接受度,而一个动辄100mb的包会把用户吓跑,而且下载的入口还控制在诸如迅雷,旋风,IE,等等,稍等,还有360安全浏览器。。。你如果没有给360打个招呼,它说不定会报你的程序有毒。。。太可拍了。入口最重要,掌握在任何人手里都会成为收门票的工具。

意义二:用户对着迅雷,旋风的界面,千遍一律,没有任何信息的展示,就是一个进度条,你丧失了在第一时间占领用户意识的契机。如果你在安装的时候有动画播放,并且让用户在安装的过程中获取产品的使用技巧,或者像广告植入一样的严肃的对待这个不起眼的时间,你会收获很多。

本篇主要讲讲以下几点:
在线下载
下载后的安装
下载后的解压

所用到的插件【新增】:
inetc
nsis7z




















































































回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|手机版|Archiver|SgzyStudio

GMT+8, 2024-4-20 18:21 , Processed in 0.122865 second(s), 21 queries .

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表