在上一篇中曾留下兩個問題,.Net捆綁安裝不觸發以及路徑選擇的問題現在都已經解決,這段時間花的最多的地方還是WPF調樣式上面,奈何WPF功力不夠,暫時還是沒有達到自己想要的效果。另外ViewModel做了些調整,狀態更加分明。安裝效果是仿照搜狗輸入法做的。先上效果圖。



以上這只是四個基本的頁面,更細化一些可以分出卸載的進度頁面,卸載的完成頁面,對修復同理,還有安裝失敗,用戶取消提示頁面,再就是能檢測新版本並覆蓋安裝,這些都可以根據狀態去添加頁面。下面簡單的說下思路和新的問題點。
關鍵流程
安裝包運行之后,首先觸發的重要的事件之一就是 DetectPackageComplete,Bootstrapper按照Bundle.wxs Chain里面Package的順序來檢測當前電腦是否安裝了其中的程序。
<Chain DisableRollback='yes'> <PackageGroupRef Id="Netfx4Full" /> <MsiPackage Id="HeartBeats" SourceFile="D:\SetUp\Main\TestWix\bin\Debug\zh-cn\WPFDemo.msi" Compressed="yes" DisplayInternalUI="no" > <MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]"/> </MsiPackage> </Chain>
像這樣,首先會判斷是否安裝了.Net4.0,但是在WPF程序里面我們需要判斷的還是HeartBeats這個安裝包。·
protected void DetectPackageComplete(object sender, DetectPackageCompleteEventArgs e) { PackageId = e.PackageId; //對應的是MsiPackage Id="HeartBeats" //MessageBox.Show(e.PackageId + e.State); if (e.PackageId.Equals("HeartBeats", StringComparison.Ordinal)) { State = e.State == PackageState.Present ? InstallState.Present : InstallState.NotPresent; //State = InstallState.NotPresent; } }
如果這個安裝包的State等於 PackageState.Present 那說明當前電腦已經安裝過了,那就要出現卸載和修復的界面,反之就是出現安裝的界面(因此也可以控制用戶不能直接卸載,可以讓用戶提交一些數據之后再卸載)。然后會執行PlanBegin->PlanComplete->
ApplyBegin->ApplyComplete. 從字面意思理解就是安裝准備到執行完畢的過程。InstallState的值就是在這些事件中發生變化,這也是從進度條頁面切換到完成頁面的順序。InstallState 為Applied就是安裝完成了。
public enum InstallState { Initializing, Present, NotPresent, Applying, Cancelled, Applied, Failed, }
其他的一些細節在前文里面有講,這里就不再贅述。
安裝界面布局
這里的布局主要是四個Grid根據狀態不斷的切換。當然你可以添加更多的狀態也展示不同的頁面。
<Grid Grid.Row="1" Background="White" Visibility="{Binding InstallEnabled,Converter={StaticResource BooleanToVisibilityConverter}}" > <Grid.RowDefinitions> <RowDefinition Height="2*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Rectangle MouseLeftButtonDown="Background_MouseLeftButtonDown" /> <StackPanel Grid.Row="0" VerticalAlignment="Stretch" Style="{StaticResource InstallGrid}" > <StackPanel.Background> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Color="White" Offset="0" /> <GradientStop Color="AliceBlue" Offset="1" /> </LinearGradientBrush> </StackPanel.Background> <TextBlock HorizontalAlignment="Center" Padding="10" FontSize="40" Foreground="#33CCFF" IsHitTestVisible="False" Text="{Binding SoftName}" ></TextBlock> <Button Height="40" HorizontalAlignment="Center" IsEnabled="{Binding IsAgree,UpdateSourceTrigger=PropertyChanged}" Width="200" Style="{StaticResource BuleBt}" Command="{Binding InstallCommand}" >立即安裝</Button> <TextBlock Foreground="Gray" Padding="5 10 0 0" VerticalAlignment="Center" HorizontalAlignment="Center" TextDecorations="{x:Null}"> <Hyperlink NavigateUri="http://www.cnblogs.com/stoneniqiu/" RequestNavigate="Hyperlink_OpenGuid" > <Label Content="安裝指南" VerticalContentAlignment="Bottom" FontSize="13" Margin="0" Padding="0" Foreground="#666" ></Label> </Hyperlink> </TextBlock> </StackPanel> <Border Grid.Row="1" HorizontalAlignment="Left" Width="580" Height="110" VerticalAlignment="Top" BorderBrush="Gainsboro" BorderThickness="0 0 0 0"> <Grid Style="{StaticResource DirGrid}" > <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="3*" ></ColumnDefinition> </Grid.ColumnDefinitions> <Label Grid.Row="0" Grid.Column="0" Content="安裝位置:" HorizontalAlignment="Right" VerticalContentAlignment="Center" ></Label> <TextBox Grid.Column="1" Grid.Row="0" Width="280" Height="25" Background="White" Margin="10 5" HorizontalAlignment="Left" Text="{Binding InstallFollder,UpdateSourceTrigger=PropertyChanged}" ></TextBox> <Button Grid.Column="1" Grid.Row="0" Width="50" Height="25" Style="{StaticResource BuleBt}" Margin="0 0 90 0" Name="SelectFile" Click="SelectFile_OnClick" FontSize="14" HorizontalAlignment="Right" >瀏覽</Button> <CheckBox Grid.Row="1" IsChecked="{Binding IsAgree,UpdateSourceTrigger=PropertyChanged}" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10 0 0 0" > <TextBlock Foreground="Gray" Padding="5 0 0 0" VerticalAlignment="Center" TextDecorations="{x:Null}"> <Label Content="我已經閱讀並最終接受:" VerticalContentAlignment="Bottom" FontSize="12" Margin="0" Padding="0" Foreground="Gray" ></Label> <Hyperlink NavigateUri="http://www.cnblogs.com/stoneniqiu/" RequestNavigate="Hyperlink_OnRequestNavigate" > <Label Content="用戶協議" VerticalContentAlignment="Bottom" FontSize="12" Margin="0" Padding="0" Foreground="DodgerBlue" ></Label> </Hyperlink> </TextBlock> </CheckBox> <Label Name="InstalLabel" Grid.Column="0" Grid.ColumnSpan="2" Content="{Binding SpaceInfo,UpdateSourceTrigger=PropertyChanged}" Foreground="Gray" HorizontalAlignment="Center" Grid.Row="2" Margin="5 0 0 0" /> </Grid> </Border> </Grid> <Grid Grid.Row="1" Visibility="{Binding UninstallEnabled,Converter={StaticResource BooleanToVisibilityConverter}}"> <Grid.Background> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Color="White" Offset="0" /> <GradientStop Color="WhiteSmoke" Offset="1" /> </LinearGradientBrush> </Grid.Background> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="2*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="35" /> <RowDefinition Height="45"/> <RowDefinition /> <RowDefinition Height="50" /> </Grid.RowDefinitions> <Label Grid.Row="1" Grid.Column="1" Content="請選擇:" HorizontalAlignment="Left" VerticalAlignment="Top" Foreground="Green" FontSize="25" ></Label> <StackPanel Grid.Row="2" Grid.Column="1"> <Button Content="卸載" IsDefault="True" HorizontalAlignment="Left" Margin="10 10" Height="35" Width="120" Style="{StaticResource BuleBt}" Background="Tomato" Command="{Binding UninstallCommand}" ></Button> <Button Content="修復" IsDefault="True" HorizontalAlignment="Left" Margin="10 10" Height="35" Width="120" Style="{StaticResource BuleBt}" Command="{Binding RepairCommand}" ></Button> </StackPanel> <TextBlock Grid.Row="3" Foreground="Gray" Grid.Column="0" Padding="5 0 0 0" VerticalAlignment="Center" Grid.ColumnSpan="2" TextDecorations="{x:Null}"> <Label Content="登陸官網了解更多:" VerticalContentAlignment="Bottom" FontSize="12" Margin="0" Padding="0" Foreground="Gray" ></Label> <Hyperlink NavigateUri="{Binding WebSite }" RequestNavigate="Hyperlink_OnRequestNavigate" > <Label Content="{Binding WebSite}" VerticalContentAlignment="Bottom" FontSize="12" Margin="0" Padding="0" Foreground="Gray" ></Label> </Hyperlink> </TextBlock> </Grid>
像安裝頁面對應的是InstallEnabled 屬性,來決定是否顯示。
public bool InstallEnabled { get { return State == InstallState.NotPresent; } }
public InstallState State { get { return state; } set { if (state != value) { state = value; Message = "Status: " + state; OnPropertyChanged("State"); OnPropertyChanged("CompleteEnabled"); OnPropertyChanged("ExitEnabled"); OnPropertyChanged("CancelEnabled"); OnPropertyChanged("InstallEnabled"); OnPropertyChanged("ProgressEnabled"); OnPropertyChanged("UninstallEnabled"); } } }
一些文字的修改直接寫在InstallConfig里面
public static class InstallConfig { public static string SoftName = "HeartBeats"; public static string WebSite = "http://www.cnblogs.com/stoneniqiu/"; public static string IcoUrl = "../Resources/heart.ico"; public static string BkUrl = "../Resources/Slider/b1.png"; public static string SoftTitle = "HeartBeats2.0正式版 安裝向導"; public static string InstallFinished = "HeartBeats2.0安裝完成"; }
.Net安裝
這個問題的解決還得的感謝一位網友,WIX的很多問題困擾着我,就像《勿忘心安》那首歌里面唱的,“只是苦於這些問題無人交流,只好任憑生命去阻礙”。很多時候一個人思路有限,知識有限,沒有交流很難解決問題。解決辦法就是修改一下bundle文件。
<Fragment> <WixVariable Id="WixMbaPrereqPackageId" Value="Netfx4Full" /> <WixVariable Id="WixMbaPrereqLicenseUrl" Value="NetfxLicense.rtf" /> <util:RegistrySearch Root="HKLM" Key="SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full" Value="Version" Variable="Netfx4FullVersion" /> <util:RegistrySearch Root="HKLM" Key="SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full" Value="Version" Variable="Netfx4x64FullVersion" Win64="yes" /> <PackageGroup Id="Netfx4Full"> <ExePackage Id="Netfx4Full" Cache="yes" Compressed="yes" PerMachine="yes" Permanent="yes" Vital="yes" SourceFile="$(var.Dia)dotNetFx40_Full_x86_x64.exe" InstallCommand="/q /norestart " DownloadUrl="http://go.microsoft.com/fwlink/?LinkId=164193" DetectCondition="Netfx4FullVersion AND (NOT VersionNT64 OR Netfx4x64FullVersion)"/> </PackageGroup> </Fragment>
如果是4.5就修改成
<Fragment> <WixVariable Id="WixMbaPrereqPackageId" Value="Netfx45Full" /> <WixVariable Id="WixMbaPrereqLicenseUrl" Value="NetfxLicense.rtf" /> <util:RegistrySearch Root="HKLM" Key="SOFTWARE\Microsoft\Net Framework Setup\NDP\v4.5\Full" Value="Version" Variable="Netfx45FullVersion" /> <util:RegistrySearch Root="HKLM" Key="SOFTWARE\Microsoft\Net Framework Setup\NDP\v4.5\Full" Value="Version" Variable="Netfx45x64FullVersion" Win64="yes" /> <PackageGroup Id="Netfx45Full"> <ExePackage Id="Netfx45Full" Cache="yes" Compressed="yes" PerMachine="yes" Permanent="yes" Vital="yes" SourceFile="D:\dotNetFx45_Full_setup.exe" InstallCommand="/q /norestart " DownloadUrl="http://go.microsoft.com/fwlink/?LinkId=164193" DetectCondition="Netfx45FullVersion AND (NOT VersionNT64 OR Netfx45x64FullVersion)"/> </PackageGroup> </Fragment> </Wix>
如果用原生的Bootstrap界面,這樣寫就行了。
<util:RegistrySearchRef Id="NETFRAMEWORK40"/> <PackageGroup Id="Netfx4Full"> <ExePackage Id="Netfx4FullExe" Cache="no" Compressed="yes" PerMachine="yes" Permanent="yes" Vital="yes" SourceFile="$(var.Dia)dotNetFx40_Full_x86_x64.exe" InstallCommand="/q /norestart " DetectCondition="NETFRAMEWORK40" DownloadUrl="http://go.microsoft.com/fwlink/?LinkId=164193"/> </PackageGroup>
對應的BootstrapperCore.config 稍微做一下修改。
<configuration> <configSections> <sectionGroup name="wix.bootstrapper" type="Microsoft.Tools.WindowsInstallerXml.Bootstrapper.BootstrapperSectionGroup, BootstrapperCore"> <section name="host" type="Microsoft.Tools.WindowsInstallerXml.Bootstrapper.HostSection, BootstrapperCore" /> </sectionGroup> </configSections> <startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" /> </startup> <wix.bootstrapper> <host assemblyName="CustomBA"> <supportedFramework version="v4\Full" /> <supportedFramework version="v4\Client" /> </host> </wix.bootstrapper> </configuration>
在沒有.Net4.0的時候會出現類似於如下的安裝界面。完成之后再會啟動WPF安裝界面。不然WPF界面是跑步起來的。

設置安裝路徑
這個功能就是講WPF用戶自定義的路徑傳遞到MSI文件中去。分三步。
1.去掉目錄中的ProgramFilesFolder。
在原來的模式下,我們的路徑應該是這樣的:目標目錄下是ProgramFiles文件夾再是我們的軟件目錄。
<!--文件安裝目錄--> <Directory Id="TARGETDIR" Name="SourceDir"> <!--程序目錄--> <Directory Id='ProgramFilesFolder'> <Directory Id='INSTALLFOLDER' Name='!(loc.SoftName)'> </Directory> </Directory> </Directory>
修改成:
<!--文件安裝目錄--> <Directory Id="TARGETDIR" Name="SourceDir"> <!--程序目錄--> <Directory Id='INSTALLFOLDER' Name='!(loc.SoftName)'> </Directory>
</Directory>
我這是實驗得來的結果,我的理解是ProgramFilesFolder對於Program Files或者Program Files(x86) 文件夾,有強制性,導致我們自定義的路徑設置失敗。
2.在Bundl.wxs文件中加入MsiProperty。
<MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]"/>
INSTALLFOLDER 對應的就是MSI安裝包中的變量,也就是上一步驟中的 Directory。而[InstallFolder]是我們在Bundle中自定義的變量。接下來看最后一步。
3.Bing定並設置變量InstallFolder
在ViewModel中定義一個InstallFolder屬性
public string InstallFollder { get { return _installFollder; } set { if (_installFollder != value && ValidDir(value)) { _installFollder = value; OnPropertyChanged("InstallFollder"); model.SetTargetFolderPath(value); } } }
private string TargetFolder = "InstallFolder"; public void SetTargetFolderPath(string path) { SetBurnVariable(TargetFolder,path); } public void SetBurnVariable(string variableName, string value) { BootstrapperApplication.Engine.StringVariables[variableName] = value; }
改變的時候,先驗證,再設置這個變量。
<TextBox Grid.Column="1" Grid.Row="0" Width="280" Height="25" Background="White" Margin="10 5" HorizontalAlignment="Left" Text="{Binding InstallFollder,UpdateSourceTrigger=PropertyChanged}" ></TextBox>
界面上直接綁定這個屬性即可。
Installer4.5及vcredist安裝
這是兩個額外的知識點,有的電腦沒有Installer4.5不能觸發安裝,沒有vcredist 也會導致一些com注冊失敗。
<Chain DisableRollback='yes'> <PackageGroupRef Id="Netfx4Full" /> <PackageGroupRef Id="WindowsInstaller45" /> <PackageGroupRef Id="vcredist" />
<MsiPackage ..... Compressed="yes" DisplayInternalUI="yes" /> </Chain>
開發電腦太強大,測試電腦可能缺這個缺那個的,歡迎大家補充一些。
<Fragment> <util:RegistrySearchRef Id="NETFRAMEWORK40"/> <PackageGroup Id="Netfx4Full"> <ExePackage Id="Netfx4FullExe" Cache="no" Compressed="yes" PerMachine="yes" Permanent="yes" Vital="yes" SourceFile="$(var.Dia)dotNetFx40_Full_x86_x64.exe" InstallCommand="/q /norestart " DetectCondition="NETFRAMEWORK40" DownloadUrl="http://go.microsoft.com/fwlink/?LinkId=164193"/> </PackageGroup> <util:RegistrySearch Root="HKLM" Key="SOFTWARE\Microsoft\DevDiv\VC\Servicing\9.0\RED\1033" Value="SP" Variable="vcredist" /> <PackageGroup Id="vcredist"> <ExePackage Id="vcredist_x86" Cache="no" Compressed="yes" PerMachine="yes" Permanent="yes" Vital="yes" Name="vcredist_x86.exe" SourceFile="$(var.Dia)vcredist_x86.exe" DownloadUrl="http://www.microsoft.com/zh-cn/download/confirmation.aspx?id=5638" InstallCommand="/q" DetectCondition="vcredist AND (vcredist >= 1)"> <ExitCode Value ="3010" Behavior="forceReboot" /> </ExePackage> </PackageGroup> <!-- Windows Installer 4.5 --> <PackageGroup Id="WindowsInstaller45"> <ExePackage Cache="no" Compressed="yes" PerMachine="yes" Permanent="yes" Vital="yes" SourceFile="$(var.Dia)WindowsXP-KB942288-v3-x86.exe" DownloadUrl="http://download.microsoft.com/download/2/6/1/261fca42-22c0-4f91-9451-0e0f2e08356d/WindowsXP-KB942288-v3-x86.exe" InstallCondition="VersionNT=v5.1 AND NOT VersionNT64 AND VersionMsi < v4.5" InstallCommand="/quiet /norestart"> <ExitCode Behavior="forceReboot"/> </ExePackage> <ExePackage Cache="no" Compressed="yes" PerMachine="yes" Permanent="yes" Vital="yes" SourceFile="$(var.Dia)WindowsServer2003-KB942288-v4-x86.exe" DownloadUrl="http://download.microsoft.com/download/2/6/1/261fca42-22c0-4f91-9451-0e0f2e08356d/WindowsServer2003-KB942288-v4-x86.exe" InstallCondition="VersionNT=v5.2 AND NOT VersionNT64 AND VersionMsi < v4.5" InstallCommand="/quiet /norestart"> <ExitCode Behavior="forceReboot"/> </ExePackage> <ExePackage Cache="no" Compressed="yes" PerMachine="yes" Permanent="yes" Vital="yes" SourceFile="$(var.Dia)WindowsServer2003-KB942288-v4-x64.exe" DownloadUrl="http://download.microsoft.com/download/2/6/1/261fca42-22c0-4f91-9451-0e0f2e08356d/WindowsServer2003-KB942288-v4-x64.exe" InstallCondition="VersionNT=v5.2 AND VersionNT64 AND VersionMsi < v4.5" InstallCommand="/quiet /norestart"> <ExitCode Behavior="forceReboot"/> </ExePackage> <MsuPackage Cache="no" Compressed="yes" Permanent="yes" Vital="yes" KB="KB942288" SourceFile="$(var.Dia)Windows6.0-KB942288-v2-x86.msu" DownloadUrl="http://download.microsoft.com/download/2/6/1/261fca42-22c0-4f91-9451-0e0f2e08356d/Windows6.0-KB942288-v2-x86.msu" InstallCondition="VersionNT=v6.0 AND NOT VersionNT64 AND VersionMsi < v4.5"/> <MsuPackage Cache="no" Compressed="yes" Permanent="yes" Vital="yes" KB="KB942288" SourceFile="$(var.Dia)Windows6.0-KB942288-v2-x64.msu" DownloadUrl="http://download.microsoft.com/download/2/6/1/261fca42-22c0-4f91-9451-0e0f2e08356d/Windows6.0-KB942288-v2-x64.msu" InstallCondition="VersionNT=v6.0 AND VersionNT64 AND VersionMsi < v4.5"/> </PackageGroup> </Fragment>
小結
QQ,搜狗,UC,360等等這些互聯網巨頭的安裝包都蠻炫酷的,對卸載和升級都做的很細致精美,很多效果我想WPF是可以做到的,但是現在有些效果我還沒有成功實現:
1.炫彩滾動的進度條,想到用漸變,但效果不理想也就沒有放上來。如果是css3,那是很好寫的。像這些效果-->猛擊

2.圖片滾動。像Web里面的Slider一樣,在顯示進度條的時候放個三張介紹圖片來回切換。這個我找到了一個Demo,還沒有套用到這個安裝包中。
3.安裝軟件的時候,用戶需要打開用戶許可或者安裝向導,而這個除了用官網的網址,我想用文件來展示,比如html、txt。一般的WPF程序,復制到輸出目錄就可以了,但是在安裝包中需要打開dll資源中的文件,這個不知道大家有什么辦法。
比如我的根目錄下有一個文件,InstallGuid.Html文件。生成操作為Resource。

在Hyperlink的事件中用
Process.Start(new ProcessStartInfo(filepath));
打開這個文件,寫相對路徑是會報錯找不到文件。
以上三個問題,如有知道的園友還請不吝指教下。
需要Demo的同學 http://pan.baidu.com/s/1bntxKsB。
我喜歡看書,也喜歡分享書籍(不限技術書籍),建了一個書山有路群,誠邀有興趣的朋友加入q:452450927
書山有路一起走,學海無涯是朋友。
