在上一篇中曾留下两个问题,.Net捆绑安装不触发以及路径选择的问题现在都已经解决,这段时间花的最多的地方还是WPF调样式上面,奈何WPF功力不够,暂时还是没有达到自己想要的效果。另外ViewModel做了些调整,状态更加分明。安装效果是仿照搜狗输入法做的。先上效果图。
以上这只是四个基本的页面,更细化一些可以分出卸载的进度页面,卸载的完成页面,对修复同理,还有安装失败,用户取消提示页面,再就是能检测新版本并覆盖安装,这些都可以根据状态去添加页面。下面简单的说下思路和新的问题点。
关键流程
安装包运行之后,首先触发的重要的事件之一就是 DetectPackageComplete,Bootstrapper按照Bundle.wxs Chain里面Package的顺序来检测当前电脑是否安装了其中的程序。
<Chain DisableRollback='yes'> <PackageGroupRef Id="Netfx4Full" /> <MsiPackage Id="HeartBeats" SourceFile="D:SetUpMainTestWixinDebugzh-cnWPFDemo.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="SOFTWAREMicrosoftNet Framework SetupNDPv4Full" Value="Version" Variable="Netfx4FullVersion" /> <util:RegistrySearch Root="HKLM" Key="SOFTWAREMicrosoftNet Framework SetupNDPv4Full" 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="SOFTWAREMicrosoftNet Framework SetupNDPv4.5Full" Value="Version" Variable="Netfx45FullVersion" /> <util:RegistrySearch Root="HKLM" Key="SOFTWAREMicrosoftNet Framework SetupNDPv4.5Full" 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="v4Full" /> <supportedFramework version="v4Client" /> </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="SOFTWAREMicrosoftDevDivVCServicing9.0RED1033" 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
书山有路一起走,学海无涯是朋友。