Custom dialog for your installer

在这里我总结的是用installshield 打包软件的时候自定义安装界面的问题,阅读下面的文章请确保您具有以下工具的使用经验:
spy++
installshield 12
visual studio 2005
熟悉以下语言:
vc++
installscript
并且熟悉windows的系统操作。

另外本文的例子是基于installscript 类型项目。

各种方法:
1, 利用installshield的Dialogs面板工具。
这里不用复述,它像我们所用过的大多数软件开发工具一样提供了可见即可得的效果,我们可以轻松利用它的拖放操作自定义对话框。

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
添加如下代码:
szResoucePath   = SUPPORTDIR ^ "resource.dll";        // Load custom DLL.
    
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:
                
DoEXIT );
            
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, 利用函数显示对话框
  •     创建资源文件
打开vs,新建一mfc dll 项目。
添加一个函数, 并注册。
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 );
}

definition:
#ifdef __cplusplus
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

添加一个dialog并生成一个类:CECDialogsDLL  ,这就是我们要在DisplayAskDestPathDlg这个函数里面调用的窗体了。你可以利用vc的强大功能为他添加各种各样的行为。
  • 应用
同样把这个项目的resouce.h 文件的内容添加到installshield 的resouce.h文件中。
声明函数:
prototype ECDIALOGS.DisplayAskDestPathDlg(INTSTRINGINTINTINT);

调用:
再调用的时候,也要先load进dll 文件,具体请参考上面的代码。
prototype ECDIALOGS.DisplayAskDestPathDlg(INTSTRINGINTINTINT);

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). 我们只能修改它。

  •  修改它的dialog title:
 
SetDialogTitle( DLG_STATUS, "mimeo.com - setup" );

这句代码的有效前提是你调用的是 STATUSEX 的setup status dialog.
SetStatusWindow( 1"" );
    Enable( STATUSEX );
    StatusUpdate( 
ON100 );
关闭它:
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( 
ON100 );
     
    
// change the setup status dialog's UI  
    try
        hWnd 
= FindWindow( """mimeo.com - setup" );
        
if (hWnd != NULLthen
            HiddenDlgItem( hWnd, 
50 );

            
// change the progress' position
            hWndStatus = GetDlgItem( hWnd, 1500 );
            
if ( hWndStatus != NULL )  then
                SetWindowPos( hWndStatus, 
04926040023, SWP_SHOWWINDOW );  
            endif;
            
//  change the top banner's ctrl weight and height                    
            hWndTopBanner = GetTopBannerCtrlWnd( hWnd );  
            
if ( hWndTopBanner != NULL ) then   
                SetWindowPos( hWndTopBanner, 
000498235, SWP_SHOWWINDOW ); 
            endif;    
            
            
// change botton line's position          
            //hWndStatus = GetDlgItem( hWnd, 1300 );   
            
//if ( hWndStatus != NULL )  then
            
//    SetWindowPos( hWndStatus, 142904902, 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, 
10000, 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, INTSTRING ); 
prototype Kernel32.GetLastError(); 
prototype User32.UpdateWindow( HWND );
       注意哦, 系统api 的dll 是不用你自己再load的哦。 如果你又usedll 出现什么问题,别说我没告诉你哦。
  •     修改文字:
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




posted @ 2007-04-28 20:36  vEteran.lu  阅读(1982)  评论(3编辑  收藏  举报