spy++
installshield 12
visual studio 2005
熟悉以下语言:
vc++
installscript
并且熟悉windows的系统操作。
另外本文的例子是基于installscript 类型项目。
各种方法:
1, 利用installshield的Dialogs面板工具。
这里不用复述,它像我们所用过的大多数软件开发工具一样提供了可见即可得的效果,我们可以轻松利用它的拖放操作自定义对话框。
2, 利用外部的资源文件
这里不用复述,它像我们所用过的大多数软件开发工具一样提供了可见即可得的效果,我们可以轻松利用它的拖放操作自定义对话框。
2, 利用外部的资源文件
- 制作资源文件
打开你的vs(笔者用的是2005),新建一个MFC DLL项目,在左边找到你的资源面板,添加一个dialog, 添加一些相应的控间,build.
- 引用资源文件
把你build出来得资源文件 resouce.dll 添加到installshield 的Support Files language independent 下面
打开installscript, 新建一个脚本文件,重命名为resouce.h 把你vc 项目里面的resouce.h 文件的内容复制过来, 然后再你的Setup.Rul 文件里面添加引用:
#include resource.h
添加如下代码:
打开installscript, 新建一个脚本文件,重命名为resouce.h 把你vc 项目里面的resouce.h 文件的内容复制过来, 然后再你的Setup.Rul 文件里面添加引用:
#include resource.h
添加如下代码:
szResoucePath
=
SUPPORTDIR
^
"
resource.dll
"
;
//
Load custom DLL.
if ( UseDLL( szResoucePath ) < 0 ) then
abort;
endif;
if ( UseDLL( szResoucePath ) < 0 ) then
abort;
endif;
显示窗体, 处理消息:
nResult
=
DefineDialog( szDlgName,
hInstance,
szDLL,
IDD_DIALOG_ASKPATH,
"" ,
hwndParent,
HWND_INSTALL,
DLG_MSG_STANDARD | DLG_CENTERED );
if nResult then
// TODO: show error message then exit .
abort;
endif;
repeat // handle dialog message
nResult = WaitOnDialog( szDlgName ); // begin our message loop
switch ( nResult )
case DLG_CLOSE:
EndDialog (szDlgName);
ReleaseDialog (szDlgName);
abort;
case DLG_INIT:
CtrlSetText( szDlgName, IDC_EDIT_PATH, svdir );
CtrlSetState( szDlgName, IDC_CHECK_OFFICETOOLBAR, BUTTON_CHECKED );
CtrlSetState( szDlgName, IDC_CHECK_DESTOP, BUTTON_CHECKED );
CtrlSetState( szDlgName, IDC_CHECK_QUICKLAUNCH, BUTTON_CHECKED );
case IDC_BUTTON_BROWSE:
SelectDir ( " Choose Folder " , "" , svdir, TRUE );
CtrlSetText( szDlgName, IDC_EDIT_PATH, svdir );
case RES_PBUT_CANCEL:
Do ( EXIT );
case RES_PBUT_NEXT:
CtrlGetText( szDlgName, IDC_EDIT_PATH, svdir ); // get ctrl ' s value
if ( CtrlGetState( szDlgName, IDC_CHECK_OFFICETOOLBAR ) = BUTTON_CHECKED ) then
bAddOfficeToolbar = TRUE ;
else
bAddOfficeToolbar = FALSE ;
endif;
if ( CtrlGetState( szDlgName, IDC_CHECK_DESTOP ) = BUTTON_CHECKED ) then
bDeskTopShortcut = TRUE ;
else
bDeskTopShortcut = FALSE ;
endif;
if ( CtrlGetState( szDlgName, IDC_CHECK_QUICKLAUNCH ) = BUTTON_CHECKED ) then
bQuickLuanchToolbar = TRUE ;
else
bQuickLuanchToolbar = FALSE ;
endif;
bDone = TRUE ;
case RES_PBUT_BACK:
EndDialog( szDlgName );
ReleaseDialog( szDlgName );
goto Dlg_SdLicense2;
endswitch;
until bDone;
EndDialog( szDlgName ); // exit dialog
ReleaseDialog( szDlgName ); // release resouce
hInstance,
szDLL,
IDD_DIALOG_ASKPATH,
"" ,
hwndParent,
HWND_INSTALL,
DLG_MSG_STANDARD | DLG_CENTERED );
if nResult then
// TODO: show error message then exit .
abort;
endif;
repeat // handle dialog message
nResult = WaitOnDialog( szDlgName ); // begin our message loop
switch ( nResult )
case DLG_CLOSE:
EndDialog (szDlgName);
ReleaseDialog (szDlgName);
abort;
case DLG_INIT:
CtrlSetText( szDlgName, IDC_EDIT_PATH, svdir );
CtrlSetState( szDlgName, IDC_CHECK_OFFICETOOLBAR, BUTTON_CHECKED );
CtrlSetState( szDlgName, IDC_CHECK_DESTOP, BUTTON_CHECKED );
CtrlSetState( szDlgName, IDC_CHECK_QUICKLAUNCH, BUTTON_CHECKED );
case IDC_BUTTON_BROWSE:
SelectDir ( " Choose Folder " , "" , svdir, TRUE );
CtrlSetText( szDlgName, IDC_EDIT_PATH, svdir );
case RES_PBUT_CANCEL:
Do ( EXIT );
case RES_PBUT_NEXT:
CtrlGetText( szDlgName, IDC_EDIT_PATH, svdir ); // get ctrl ' s value
if ( CtrlGetState( szDlgName, IDC_CHECK_OFFICETOOLBAR ) = BUTTON_CHECKED ) then
bAddOfficeToolbar = TRUE ;
else
bAddOfficeToolbar = FALSE ;
endif;
if ( CtrlGetState( szDlgName, IDC_CHECK_DESTOP ) = BUTTON_CHECKED ) then
bDeskTopShortcut = TRUE ;
else
bDeskTopShortcut = FALSE ;
endif;
if ( CtrlGetState( szDlgName, IDC_CHECK_QUICKLAUNCH ) = BUTTON_CHECKED ) then
bQuickLuanchToolbar = TRUE ;
else
bQuickLuanchToolbar = FALSE ;
endif;
bDone = TRUE ;
case RES_PBUT_BACK:
EndDialog( szDlgName );
ReleaseDialog( szDlgName );
goto Dlg_SdLicense2;
endswitch;
until bDone;
EndDialog( szDlgName ); // exit dialog
ReleaseDialog( szDlgName ); // release resouce
3, 利用函数显示对话框
- 创建资源文件
添加一个函数, 并注册。
extern
"
C
"
LONG WINAPI DisplayAskDestPathDlg( HWND hWndParent, LPSTR lpszPath,
PBOOL pfAddOfficeToolbar, PBOOL pfDeskTopShortcut, PBOOL pfQuickLuanchToolbar)
{
LONG nReturn;
TRACE0( " Inside ECDialogs.DLL\n " );
CString csName = _T( "" );
LPSTR lpszLicense = NULL;
TRY
{
CECDialogsDLL dlg(CWnd::FromHandle(hWndParent), 0 , pfAddOfficeToolbar, pfDeskTopShortcut, pfQuickLuanchToolbar, lpszPath, lpszLicense,
CECDialogsDLL::askPathDestDialog);
nReturn = (LONG) dlg.DoModal();
if ( (nReturn == (LONG)RS_PBUT_RETRY ) ||
(nReturn == (LONG)RS_PBUT_BROWSE) ||
(nReturn == (LONG)RS_PBUT_CANCEL) )
return ( nReturn );
}
CATCH_ALL(e)
{
// A failure caused an exception!
return FALSE;
}
END_CATCH_ALL
return ( nReturn );
}
LONG WINAPI DisplayAskDestPathDlg( HWND hWndParent, LPSTR lpszPath,
PBOOL pfAddOfficeToolbar, PBOOL pfDeskTopShortcut, PBOOL pfQuickLuanchToolbar)
{
LONG nReturn;
TRACE0( " Inside ECDialogs.DLL\n " );
CString csName = _T( "" );
LPSTR lpszLicense = NULL;
TRY
{
CECDialogsDLL dlg(CWnd::FromHandle(hWndParent), 0 , pfAddOfficeToolbar, pfDeskTopShortcut, pfQuickLuanchToolbar, lpszPath, lpszLicense,
CECDialogsDLL::askPathDestDialog);
nReturn = (LONG) dlg.DoModal();
if ( (nReturn == (LONG)RS_PBUT_RETRY ) ||
(nReturn == (LONG)RS_PBUT_BROWSE) ||
(nReturn == (LONG)RS_PBUT_CANCEL) )
return ( nReturn );
}
CATCH_ALL(e)
{
// A failure caused an exception!
return FALSE;
}
END_CATCH_ALL
return ( nReturn );
}
definition:
#ifdef __cplusplus
extern " C " {
#endif
LONG WINAPI DisplayAskDestPathDlg( HWND hWndParent, LPSTR lpszPath,
PBOOL pfAddOfficeToolbar, PBOOL pfDeskTopShortcut, PBOOL pfQuickLuanchToolbar);
#ifdef __cplusplus
}
#endif
extern " C " {
#endif
LONG WINAPI DisplayAskDestPathDlg( HWND hWndParent, LPSTR lpszPath,
PBOOL pfAddOfficeToolbar, PBOOL pfDeskTopShortcut, PBOOL pfQuickLuanchToolbar);
#ifdef __cplusplus
}
#endif
registry:
LIBRARY
"
ECDialogs
"
DESCRIPTION ' ECDialogs Windows Dynamic Link Library '
EXPORTS
; Explicit exports can go here
DisplayAskDestPathDlg @ 1
DESCRIPTION ' ECDialogs Windows Dynamic Link Library '
EXPORTS
; Explicit exports can go here
DisplayAskDestPathDlg @ 1
添加一个dialog并生成一个类: CECDialogsDLL ,这就是我们要在 DisplayAskDestPathDlg这个函数里面调用的窗体了。你可以利用vc的强大功能为他添加各种各样的行为。
- 应用
声明函数:
prototype ECDIALOGS.DisplayAskDestPathDlg(
INT
,
STRING
,
INT
,
INT
,
INT
);
调用:
再调用的时候,也要先load进dll 文件,具体请参考上面的代码。
prototype ECDIALOGS.DisplayAskDestPathDlg(
INT
,
STRING
,
INT
,
INT
,
INT
);
ReleasePackager.exe
"
C:\My InstallShield 11 Projects\My Project\Media\My Release\Disk Images
"
"
C:\My InstallShield 11 Projects\My Project\Media\My Release\Package\MyPackage.exe
"
"
C:\My Icon Files\MyIcons.dll
"
2
4, 利用win api 修改installshield 内部对话框
在有些时候我们会发现,有些对话框我们是不能直接从installshield Dialogs面板直接修改,也不能用dll和调用外部函数来实现,比如说安装进度对话框(setup status dialog). 我们只能修改它。
这句代码的有效前提是你调用的是 STATUSEX 的setup status dialog.
还请注意,这个图片的大小是固定的哦。 那我要显示不一样大小的图片呢? 我要修改,去掉它上面那些该死的文字呢?
那你就跟我一起来使用windows api 吧。我们的思路是,利用窗体的title 找到当前这个窗口的handle, 利用SetWindowPos api修改窗口的属性。 mfc中所有的控件都是窗体哦。show 代码吧,估计你已经听我罗索的烦了。
如果你自己写的代码跑不通,看看这里,你有没有忘了声明系统api乐呢?
5, 直接修改installshield生成的资源文件。在我们启动安装程序的时候,会有一个preparing dialog,这个窗体的也是可以修改的。
在 你项目文件夹下搜索这个文件, _setup.dll ,它应该在你的 Media\<release name>\Disk Images\Disk1下面,直接用vs 就可以编辑修改。然后你双击这个文件夹下的setup.exe,你会惊喜的发现你的preparing dialog 已经被修改了。
但是,如果你需要的仅仅是一个单独的可执行文件呢?你再去用installshield rebuild的时候,你会很痛苦的发现_setup.dll 又被installshield改成它原来的了。这里installshield 为我们提供了一个exe ,他可以把media 下面的文件打包成一个self-extracting 的exe。 那就是 ReleasePackager.exe 它在<intall direcotry>\IS12\System 下面。 你可以用命令行执行它一次,如果没有参数,它会打印出帮助。具体参数说明如下:
给各例子吧,copy过来的:
如果你发现它生成了一个 C:\My InstallShield 11 Projects\My Project\Media\My Release\Package\MyPackage.exe文件。那你就大功告成了。
遇到的问题:- 修改它的dialog title:
SetDialogTitle( DLG_STATUS,
"
mimeo.com - setup
"
);
这句代码的有效前提是你调用的是 STATUSEX 的setup status dialog.
SetStatusWindow(
1
,
""
);
Enable( STATUSEX );
StatusUpdate( ON , 100 );
Enable( STATUSEX );
StatusUpdate( ON , 100 );
关闭它:
Disable( STATUSEX );
- 修改它的top banner,就是它最上面的图片:
DialogSetInfo(DLG_INFO_ALTIMAGE, SUPPORTDIR
^
"
topbanner.bmp
"
,
TRUE
);
请注意,这句代码会修改你所有的窗体的top banner 的图片, 什么时候调用? 你应该猜到了吧 :-)
还请注意,这个图片的大小是固定的哦。 那我要显示不一样大小的图片呢? 我要修改,去掉它上面那些该死的文字呢?
那你就跟我一起来使用windows api 吧。我们的思路是,利用窗体的title 找到当前这个窗口的handle, 利用SetWindowPos api修改窗口的属性。 mfc中所有的控件都是窗体哦。show 代码吧,估计你已经听我罗索的烦了。
SetDialogTitle( DLG_STATUS,
"
mimeo.com - setup
"
);
SetStatusWindow( 1 , "" );
Enable( STATUSEX );
StatusUpdate( ON , 100 );
// change the setup status dialog ' s UI
try
hWnd = FindWindow( "" , " mimeo.com - setup " );
if (hWnd ! = NULL ) then
HiddenDlgItem( hWnd, 50 );
// change the progress ' position
hWndStatus = GetDlgItem( hWnd, 1500 );
if ( hWndStatus ! = NULL ) then
SetWindowPos( hWndStatus, 0 , 49 , 260 , 400 , 23 , SWP_SHOWWINDOW );
endif;
// change the top banner ' s ctrl weight and height
hWndTopBanner = GetTopBannerCtrlWnd( hWnd );
if ( hWndTopBanner ! = NULL ) then
SetWindowPos( hWndTopBanner, 0 , 0 , 0 , 498 , 235 , SWP_SHOWWINDOW );
endif;
// change botton line ' s position
// hWndStatus = GetDlgItem( hWnd, 1300 );
// if ( hWndStatus ! = NULL ) then
// SetWindowPos( hWndStatus, 1 , 4 , 290 , 490 , 2 , SWP_SHOWWINDOW );
// endif;
User32.UpdateWindow( hWnd );
endif;
catch
endcatch;
SetStatusWindow( 1 , "" );
Enable( STATUSEX );
StatusUpdate( ON , 100 );
// change the setup status dialog ' s UI
try
hWnd = FindWindow( "" , " mimeo.com - setup " );
if (hWnd ! = NULL ) then
HiddenDlgItem( hWnd, 50 );
// change the progress ' position
hWndStatus = GetDlgItem( hWnd, 1500 );
if ( hWndStatus ! = NULL ) then
SetWindowPos( hWndStatus, 0 , 49 , 260 , 400 , 23 , SWP_SHOWWINDOW );
endif;
// change the top banner ' s ctrl weight and height
hWndTopBanner = GetTopBannerCtrlWnd( hWnd );
if ( hWndTopBanner ! = NULL ) then
SetWindowPos( hWndTopBanner, 0 , 0 , 0 , 498 , 235 , SWP_SHOWWINDOW );
endif;
// change botton line ' s position
// hWndStatus = GetDlgItem( hWnd, 1300 );
// if ( hWndStatus ! = NULL ) then
// SetWindowPos( hWndStatus, 1 , 4 , 290 , 490 , 2 , SWP_SHOWWINDOW );
// endif;
User32.UpdateWindow( hWnd );
endif;
catch
endcatch;
function
HiddenDlgItem(hWnd, nItemID) // hidden the ctrl by ctrl's ID
HWND hWndItem;
begin
hWndItem = GetDlgItem( hWnd, nItemID );
if ( hWndItem ! = NULL ) then
SetWindowPos( hWndItem, 1 , 0 , 0 , 0 , 0 , SWP_HIDEWINDOW );
endif;
end ;
INT nArray( 16 ); // save Ctrl's IDs of setup status dialog
function GetTopBannerCtrlWnd( hWnd)
INT nIndex, nPicID, nErrorCode;
HWND hWndItem, hWndPic;
begin
InitialDlgCtrlIDs( );
nIndex = 0 ;
while ( nIndex < 16 )
nPicID = nArray( nIndex );
hWndItem = GetDlgItem( hWnd, nPicID );
hWndPic = GetWindow( hWndItem, GW_HWNDNEXT );
if ( hWndPic ! = NULL ) then
nPicID = GetWindowLong( hWndPic, GWL_ID );
if ( IsPictureCtrlID( nPicID ) ) then
return hWndPic;
endif;
endif;
hWndItem = GetDlgItem( hWnd, nPicID );
hWndPic = GetWindow( hWndItem, GW_HWNDPREV );
if ( hWndPic ! = NULL ) then
nPicID = GetWindowLong( hWndPic, GWL_ID );
if ( IsPictureCtrlID( nPicID ) ) then
return hWndPic;
endif;
endif;
nIndex ++ ;
endwhile;
return NULL ;
end ;
function InitialDlgCtrlIDs( ) // 这些是该setup status 窗口中的所有控件ID, 怎么找到的, 用spy++啊。
begin
nArray( 0 ) = 0x5dc;
nArray( 1 ) = 0x34;
nArray( 2 ) = 0x4b0;
nArray( 3 ) = 0x2c6;
nArray( 4 ) = 0x578;
nArray( 5 ) = 0x32;
nArray( 6 ) = 0x33;
nArray( 7 ) = 0x7;
nArray( 8 ) = 0x514;
nArray( 9 ) = 0x2;
nArray( 10 ) = 0x515;
nArray( 11 ) = 0x4591;
nArray( 12 ) = 0x4592;
nArray( 13 ) = 0x4593;
nArray( 14 ) = 0x4594;
nArray( 15 ) = 0x5aa;
end ;
function IsPictureCtrlID( nItem ) // is top pictrue ctrl's ID?
INT nIndex;
begin
nIndex = 0 ;
while ( nIndex < 16 )
if ( nItem = nArray( nIndex ) ) then
return FALSE ;
endif;
nIndex ++ ;
endwhile;
return TRUE ;
end ;
HWND hWndItem;
begin
hWndItem = GetDlgItem( hWnd, nItemID );
if ( hWndItem ! = NULL ) then
SetWindowPos( hWndItem, 1 , 0 , 0 , 0 , 0 , SWP_HIDEWINDOW );
endif;
end ;
INT nArray( 16 ); // save Ctrl's IDs of setup status dialog
function GetTopBannerCtrlWnd( hWnd)
INT nIndex, nPicID, nErrorCode;
HWND hWndItem, hWndPic;
begin
InitialDlgCtrlIDs( );
nIndex = 0 ;
while ( nIndex < 16 )
nPicID = nArray( nIndex );
hWndItem = GetDlgItem( hWnd, nPicID );
hWndPic = GetWindow( hWndItem, GW_HWNDNEXT );
if ( hWndPic ! = NULL ) then
nPicID = GetWindowLong( hWndPic, GWL_ID );
if ( IsPictureCtrlID( nPicID ) ) then
return hWndPic;
endif;
endif;
hWndItem = GetDlgItem( hWnd, nPicID );
hWndPic = GetWindow( hWndItem, GW_HWNDPREV );
if ( hWndPic ! = NULL ) then
nPicID = GetWindowLong( hWndPic, GWL_ID );
if ( IsPictureCtrlID( nPicID ) ) then
return hWndPic;
endif;
endif;
nIndex ++ ;
endwhile;
return NULL ;
end ;
function InitialDlgCtrlIDs( ) // 这些是该setup status 窗口中的所有控件ID, 怎么找到的, 用spy++啊。
begin
nArray( 0 ) = 0x5dc;
nArray( 1 ) = 0x34;
nArray( 2 ) = 0x4b0;
nArray( 3 ) = 0x2c6;
nArray( 4 ) = 0x578;
nArray( 5 ) = 0x32;
nArray( 6 ) = 0x33;
nArray( 7 ) = 0x7;
nArray( 8 ) = 0x514;
nArray( 9 ) = 0x2;
nArray( 10 ) = 0x515;
nArray( 11 ) = 0x4591;
nArray( 12 ) = 0x4592;
nArray( 13 ) = 0x4593;
nArray( 14 ) = 0x4594;
nArray( 15 ) = 0x5aa;
end ;
function IsPictureCtrlID( nItem ) // is top pictrue ctrl's ID?
INT nIndex;
begin
nIndex = 0 ;
while ( nIndex < 16 )
if ( nItem = nArray( nIndex ) ) then
return FALSE ;
endif;
nIndex ++ ;
endwhile;
return TRUE ;
end ;
如果你自己写的代码跑不通,看看这里,你有没有忘了声明系统api乐呢?
prototype User32.SetDlgItemText( HWND,
INT
,
STRING
);
prototype Kernel32.GetLastError();
prototype User32.UpdateWindow( HWND );
注意哦, 系统api 的dll 是不用你自己再load的哦。 如果你又usedll 出现什么问题,别说我没告诉你哦。
prototype Kernel32.GetLastError();
prototype User32.UpdateWindow( HWND );
- 修改文字:
User32.SetDlgItemText(hWnd, nItemID, szStr);
5, 直接修改installshield生成的资源文件。在我们启动安装程序的时候,会有一个preparing dialog,这个窗体的也是可以修改的。
在 你项目文件夹下搜索这个文件, _setup.dll ,它应该在你的 Media\<release name>\Disk Images\Disk1下面,直接用vs 就可以编辑修改。然后你双击这个文件夹下的setup.exe,你会惊喜的发现你的preparing dialog 已经被修改了。
但是,如果你需要的仅仅是一个单独的可执行文件呢?你再去用installshield rebuild的时候,你会很痛苦的发现_setup.dll 又被installshield改成它原来的了。这里installshield 为我们提供了一个exe ,他可以把media 下面的文件打包成一个self-extracting 的exe。 那就是 ReleasePackager.exe 它在<intall direcotry>\IS12\System 下面。 你可以用命令行执行它一次,如果没有参数,它会打印出帮助。具体参数说明如下:
ReleasePackager.exe "disk_images_folder" "package_file" ["icon_file" [icon_index]]
给各例子吧,copy过来的:
ReleasePackager.exe "C:\My InstallShield
11
Projects\My Project\Media\My Release\
Disk
Images" "C:\My InstallShield
11
Projects\My Project\Media\My Release\Package\MyPackage.exe" "C:\My Icon Files\MyIcons.dll"
2
如果你发现它生成了一个 C:\My InstallShield 11 Projects\My Project\Media\My Release\Package\MyPackage.exe文件。那你就大功告成了。
在查找setup status dialog 的最上面的图片控件的时候,笔者发现这个控件的ID 每次运行的时候都市不一样的,而且,每个空间的顺序随着系统的每次系统也是不一样的,所以没有办法用GetWindow来找他的ID, 所以笔者采用了一种比较 stupid的方式来处理。具体请察看函数 GetTopBannerCtrlWnd( hWnd)
总结:
installshield 自定义窗体的方式确实很多,也很灵活,但是并不是每种方式都是power,effective, simple的,在不同的场合下我们需要应用不同的方式创建来满足需求。原则就是能用simple 的方式解决的尽量用simple的方式,如果不能,那就只能用其他的方式了。最重要的是能为客户带来一个愉快地安装过程。
相关资源:
Updating the Progress Bar - Macrovision Community
Changing Dialog Box Bitmaps
Macrovision Community - Editing text in STATUSEX dialog
http://helpnet.installshield.com/robo/projects/installshield11helplib/IHelpRelease_ReleasePackagerExe.htm