自定义目录选择,自定义进度条,自定义图片切换效果
**前言**
上一节中我们已经处理了有关CheckBox自定义贴图的部分,但是目录选择的部分还没有加上,这节,我们先处理一下目录的选择部分,选择完路径之后就剩下安装了,于是进度条的创建的显得很有必要,但是系统的进度条创建简单,如何改变进度条的背景色跟进度色呢,这节我们也处理掉。有关图片切换的效果的插件也有很多,但是大部分都是基于默认安装窗体进行的,如何在一个完全自定义的页面上面创建一个图片切换效果呢,这节也可以得到答案。
本篇主要讲讲以下几点:
创建目录选择按钮与文本框
创建自定义的进度条
创建图片切换效果
所用到的插件【新增】:
WebCtrl
SkinProgress
BgWorker
**讲义**
首先贴出今天教程的完整的例子【已测试】【附带图片】https://github.com/nicecai/nsissource/tree/master/4
这次的教程基本是安装界面的最后的阶段的处理了,主要是创建一个目录选择控件,用于确定安装程序安装的路径,在安装过程中等待时间也是比较长的,遇到打广告的好时机不要错过,切换多图是个不错的选择;在安装过程中,释放文件是一个主要动作。如果不是自定义页面,该动作是在系统的安装页面的Section里面完成的,如果不做多线程处理,该动作会阻塞主线程也就是主界面的消息传递,主界面没有了消息传递也就不会响应拖动、点击关闭等等操作,这个是本节主要说明的地方。最后说一下图片切换的实现,以我目前尝试的,效果最好的还算是直接放一张网页最实在,通过网页的js实现切换效果。
1.目录选择框
目录选择框包括一个文本框跟一个Button按钮,做过Web的人都知道,html中有一个file的控件与之相吻合,在form中则是两个不同的component,文本框很好创建,按钮的单击事件是一个重点,我们看看代码:
;更改目录控件创建 ${NSD_CreateDirRequest} 26 79 358 25 "$INSTDIR" Pop $Txt_Browser ${NSD_OnChange} $Txt_Browser OnChange_DirRequest ${NSD_CreateBrowseButton} 400 79 88 25 "" Pop $Btn_Browser StrCpy $1 $Btn_Browser Call SkinBtn_Browser GetFunctionAddress $3 OnClick_BrowseButton SkinBtn::onClick $1 $3
这里创建了一个$Txt_Browser的文本框,一个$Btn_Browser的按钮,其中按钮的单击事件是 OnClick_BrowserButton,看看按钮的事件代码:
Function OnClick_BrowseButton Pop $0 Push $INSTDIR Call GetParent Pop $R0 Push $INSTDIR Push "" Call GetLastPart Pop $R1 nsDialogs::SelectFolderDialog "请选择 $R0 安装的文件夹:" "$R0" Pop $0 ${If} $0 == "error" Return ${EndIf} ${If} $0 != "" StrCpy $INSTDIR "$0$R1" system::Call `user32::SetWindowText(i $Txt_Browser, t "$INSTDIR")` ${EndIf} FunctionEnd ;得到选中目录用于拼接安装程序名称 Function GetParent Exch $R0 Push $R1 Push $R2 Push $R3 StrCpy $R1 0 StrLen $R2 $R0 loop: IntOp $R1 $R1 + 1 IntCmp $R1 $R2 get 0 get StrCpy $R3 $R0 1 -$R1 StrCmp $R3 "" get Goto loop get: StrCpy $R0 $R0 -$R1 Pop $R3 Pop $R2 Pop $R1 Exch $R0 FunctionEnd ;截取选中目录 Function GetLastPart Exch $0 ; chop char Exch Exch $1 Push $2 Push $3 StrCpy $2 0 loop: IntOp $2 $2 - 1 StrCpy $3 $1 1 $2 StrCmp $3 "" 0 +3 StrCpy $0 "" Goto exit2 StrCmp $3 $0 exit1 Goto loop exit1: IntOp $2 $2 + 1 StrCpy $0 $1 "" $2 exit2: Pop $3 Pop $2 Pop $1 Exch $0 FunctionEnd
单击按钮的时候用nsDialogs创建一个SelectFolderDialog的对话框,选择需要安装的路径。GetParent方法主要是要取得当前选中的路径,GetLastPart主要是保留当前的安装程序最底层的目录名称,最终两个部分合起来作为整体的安装路径赋值给$INSTDIR。具体的效果可以运行源码查看。
2.创建自定义进度条
nsDialogs有自带的进度条,该进度条的颜色是那种系统的颜色,如果遇到自己设计的,就会出现问题,于是用SkinProgress就可以给进度条赋上两张图片,一张是底图,一张是进度图,虽然不是非常的完美,比如圆角,比如透明,比如阴影,比如……但是已经是跟NSIS快捷脚本配合的很好的了。
${NSD_CreateProgressBar} 24 265 474 7 "" Pop $PB_ProgressBar SkinProgress::Set $PB_ProgressBar "$PLUGINSDIRloading2.jpg" "$PLUGINSDIRloading1.jpg"
用法非常简单,用nsDialogs创建一个ProgressBar,然后用SkinProgress去set一下,后面跟上两张图片。ProgressBar的图片是有讲究的,具体的可以看源码的image文件夹中两张图片的切图,基本要使用什么效果,自己做两张图就可以了。
3.创建图片切换
图片的切换有很多种,gif、flash、js、甚至自己用c++做插件实现,这里提供一种很方面,但是又不失功能强大的方式。网页的形式!你很容易能自己写一段js实现多张图片的切换,这里唯一要解决的就是如何在Form上创建一个浏览器用于加载自己的本地html页面。
System::Call `*(i,i,i,i)i(1,34,518,200).R0` System::Call `user32::MapDialogRect(i$HWNDPARENT,iR0)` System::Call `*$R0(i.s,i.s,i.s,i.s)` System::Free $R0 FindWindow $R0 "#32770" "" $HWNDPARENT System::Call `user32::CreateWindowEx(i,t"STATIC",in,i${DEFAULT_STYLES}|${SS_BLACKRECT},i1,i34,i518,i200,iR0,i1100,in,in)i.R0` StrCpy $WebImg $R0 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:
GetFunctionAddress $0 NSD_TimerFun nsDialogs::CreateTimer $0 1 其中nsDialogs::CreateTimer后面的参数1代表的是1毫秒,正常情况下就是1毫秒执行一次NSD_TimerFun介个方法。 Function NSD_TimerFun GetFunctionAddress $0 NSD_TimerFun nsDialogs::KillTimer $0 !if 1 ;是否在后台运行,1有效 GetFunctionAddress $0 InstallationMainFun BgWorker::CallAndWait !else Call InstallationMainFun !endif 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的实现:
Function InstallationMainFun SendMessage $PB_ProgressBar ${PBM_SETRANGE32} 0 100 SendMessage $PB_ProgressBar ${PBM_SETPOS} 10 0 Sleep 1000 SendMessage $PB_ProgressBar ${PBM_SETPOS} 20 0 Sleep 1000 SendMessage $PB_ProgressBar ${PBM_SETPOS} 30 0 Sleep 1000 SendMessage $PB_ProgressBar ${PBM_SETPOS} 40 0 Sleep 1000 SendMessage $PB_ProgressBar ${PBM_SETPOS} 50 0 Sleep 1000 SendMessage $PB_ProgressBar ${PBM_SETPOS} 60 0 Sleep 1000 SendMessage $PB_ProgressBar ${PBM_SETPOS} 70 0 Sleep 1000 SendMessage $PB_ProgressBar ${PBM_SETPOS} 80 0 Sleep 1000 SendMessage $PB_ProgressBar ${PBM_SETPOS} 90 0 Sleep 1000 SendMessage $PB_ProgressBar ${PBM_SETPOS} 100 0 ShowWindow $Btn_Next ${SW_SHOW} ShowWindow $Btn_Install ${SW_HIDE} EnableWindow $Btn_Cancel 0 FunctionEnd
所做的工作主要是操作滚动条的位置。在实际的释放过程中,这个设置也是这样的,目前还没有自定义页面的释放插件,能回调得到精确的释放量,以及释放文件等等信息。所以只能模拟,尽可能的细化下来,估计着大概多少。这个是一个需要情商的工作,如果你有洁癖,偏执之类的病,我想你还是用系统自带的释放好了,也不需要自定义页面了 。