常常安裝PC端的應用,特別是重裝系統以後,大體分爲兩類。一類像QQ,搜狗輸入法這樣的。分三步走的:第一個頁面能夠自定義安裝路徑和軟件許可。第二個頁面顯示安裝進度條,第三個頁面推薦其餘應用。先無論人家怎麼實現的,咱們先回顧一下。web
QQ:bootstrap
再一個就是分六步或七步走的,如QQ影音:歡迎界面,用戶許可,安裝組件,安裝目錄,安裝進度,安裝完成,有七步的,通常會多一些軟件推薦。固然還有其餘的,好比是基於ClickOnce打包的,就一個界面,一個進度條。沒有安裝目錄選擇,這通常不是商業軟件。先說第二種,上一步下一步的有不少第三方的打包工具,比較有名的有InstallShield,setupfactory等,看似有了圖形化的操做界面,作簡單的還能夠,可是要是有一些自定義的部分就比較麻煩,由於你沒地方改,好比你要在一個安裝包中去靜默的觸發另外一個安裝包悄悄的安裝,並且這些仍是商業化的。開源的WIX,基於XML文件配置的方法來打包應用。Wix的基本打包(6,7個步驟),能夠出門左拐能夠看我以前的教程。接下來要說的,是基於WIX的Bootstrapper工程用WPF接入安裝界面。app
WIX是基於BootstrapperCore.dll提供UI擴展的,你可使用WPF,也可使用Winform做爲安裝界面。而這裏先不得不先說其中的兩個對象。一個是Engine,它提供了最根本的安裝方法,如Detect,Plan,Apply和Quit等,另一個就是引導對象BootstrapperApplication,他提供了安裝相關事件,設置窗體對象等。這爲咱們自定義界面的時候控制安裝包提供了基礎。ide
1、工程準備函數
1)新建一個Bootstrapper工程。工具
新建完成以後會有一個Bundle.wxs文件,裏面包含你要安裝的msi文件和依賴組件好比.net4.0. 優化
<?xml version="1.0" encoding="UTF-8"?> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension" xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"> <Bundle Name="WIXTest" Version="1.1.1.0" Manufacturer="Delta" UpgradeCode="{51C1EB78-C0D2-4C30-803C-8D7993CB38A5}" Compressed="yes" > <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense" /> <WixVariable Id="WixMbaPrereqLicenseUrl" Value=""/> <WixVariable Id="WixMbaPrereqPackageId" Value=""/> <Chain DisableRollback='yes'> <PackageGroupRef Id="Netfx4Full" /> <MsiPackage Id="DIAView" SourceFile="D:\TestWix\bin\Debug\zh-cn\TestWix.msi" Compressed="yes" DisplayInternalUI="yes" > </MsiPackage> </Chain> </Bundle> <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> </Fragment> </Wix>
最終的EXE仍是經過這個工程來生成的。MsiPackage的ID 後面能夠用來檢測當前電腦是否安裝了這個軟件。ui
2、建立WPF界面程序。this
1).建立一個C# Library工程CustomBA,引用BootstrapperCore.dll 及 WPF相關dlllua
BootstrapperCore在你安裝的wix安裝目錄中就能找到。好比我使用的wix3.8,路徑就是C:\Program Files (x86)\WiX Toolset v3.8\SDK\BootstrapperCore.dll
另一個就是Prism 組件,這個能夠經過Nuget安裝。
但這個有點嫌多,其實工程只使用到了屬性更改通知和命令。可使用wix源碼中的兩個對象。如此就沒必要引用上面的Prism
RelayCommand
public class RelayCommand : ICommand { private readonly Action<object> execute; private readonly Predicate<object> canExecute; public RelayCommand(Action<object> execute) : this(execute, null) { } public RelayCommand(Action<object> execute, Predicate<object> canExecute) { this.execute = execute; this.canExecute = canExecute; } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } [DebuggerStepThrough] public bool CanExecute(object parameter) { return this.canExecute == null ? true : this.canExecute(parameter); } public void Execute(object parameter) { this.execute(parameter); } }
PropertyNotifyBase
public abstract class PropertyNotifyBase : INotifyPropertyChanged { /// <summary> /// Initializes a new instance of the <see cref="PropertyNotifyBase"/> class. /// </summary> protected PropertyNotifyBase() { } /// <summary> /// Raised when a property on this object has a new value. /// </summary> public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// Warns the developer if this object does not have a public property with the /// specified name. This method does not exist in a Release build. /// </summary> /// <param name="propertyName">Property name to verify.</param> [Conditional("DEBUG")] [DebuggerStepThrough] public void VerifyPropertyName(string propertyName) { // Verify that the property name matches a real, public, instance property // on this object. if (null == TypeDescriptor.GetProperties(this)[propertyName]) { Debug.Fail(String.Concat("Invalid property name: ", propertyName)); } } /// <summary> /// Raises this object's PropertyChanged event. /// </summary> /// <param name="propertyName">The property that has a new value.</param> protected virtual void OnPropertyChanged(string propertyName) { this.VerifyPropertyName(propertyName); PropertyChangedEventHandler handler = this.PropertyChanged; if (null != handler) { PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName); handler(this, e); } } }
使用方法差異不大。
2)能夠創建以下的文件目錄,配合WPF的MVVM。
3)增長配置文件,以讓Burn使用新的程序集。必須命名爲BootstrapperCore.config ,也能夠從C:\Program Files (x86)\WiX Toolset v3.8\SDK 目錄下拷貝一個版本。
<?xml version="1.0" encoding="utf-8" ?> <!-- <copyright file="BootstrapperCore.config" company="Outercurve Foundation"> Copyright (c) 2004, Outercurve Foundation. This software is released under Microsoft Reciprocal License (MS-RL). The license and further copyright text can be found in the file LICENSE.TXT at the root directory of the distribution. </copyright> --> <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" /> <supportedRuntime version="v2.0.50727" /> </startup> <wix.bootstrapper> <!-- Example only. Use only if the startup/supportedRuntime above cannot discern supported frameworks. --> <!-- <supportedFramework version="v4\Client" /> <supportedFramework version="v3.5" /> <supportedFramework version="v3.0" /> --> <!-- Example only. Replace the host/@assemblyName attribute with assembly that implements BootstrapperApplication. --> <host assemblyName="AssemblyWithClassThatInheritsFromBootstrapperApplication" /> </wix.bootstrapper> </configuration>
修改成:
<?xml version="1.0" encoding="utf-8" ?> <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>
CustomBA.dll,BootstrapperCore.config 和 Microsoft.Practices. Prism.dll(若是使用了) 都會拷貝到bootstrapper項目中去。
4)修改Assemblyinfo
增長:
[assembly: BootstrapperApplication(typeof(CustomBootstrapperApplication))] [assembly: AssemblyTitle("CustomBA")]
CustomBootstrapperApplication 是咱們接下來要建立的一個對象,Bundle會調用它的Run方法,這是整個安裝包的起點。
5)建立WPF對象。
根據MVVM模式,咱們分別建立Model,ViewModel和View 以及一些輔助的對象。
1.Model-->BootstrapperApplicationModel
public class BootstrapperApplicationModel { private IntPtr hwnd; public BootstrapperApplicationModel( BootstrapperApplication bootstrapperApplication) { BootstrapperApplication = bootstrapperApplication; hwnd = IntPtr.Zero; } public BootstrapperApplication BootstrapperApplication { get; private set; } public int FinalResult { get; set; } public void SetWindowHandle(Window view) { hwnd = new WindowInteropHelper(view).Handle; } public void PlanAction(LaunchAction action) { BootstrapperApplication.Engine.Plan(action); } public void ApplyAction() { BootstrapperApplication.Engine.Apply(hwnd); } public void LogMessage(string message) { BootstrapperApplication.Engine.Log(LogLevel.Standard, message); } public void SetBurnVariable(string variableName, string value) { BootstrapperApplication.Engine.StringVariables[variableName] = value; } }
從代碼看是包裝了Engine的方法,設置窗口,PlanAction,ApplyAction,LogMessage以及設置變量等。
2.ViewModel-->InstallViewModel
這是一個重要的交互對象。InstallState 安裝狀態,UpdateState 更新狀態,Command用來處理用戶在安裝界面的操做,在構造函數裏進行初始化。而後就是事件訂閱。
using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Windows; using System.Windows.Input; using Microsoft.Tools.WindowsInstallerXml.Bootstrapper; namespace CustomBA.ViewModels { public class InstallViewModel : PropertyNotifyBase { public enum InstallState { Initializing, Present, NotPresent, Applying, Cancelled, Applied, Failed, } public enum UpdateState { Unknown, Initializing, Checking, Current, Available, Failed, } /// <summary> /// 記錄狀態 /// </summary> private InstallState state; public UpdateState updatestate; /// <summary> /// 須要顯示在WPFWindow /// </summary> private string message; private BootstrapperApplicationModel model; private string _packageId = string.Empty; private bool canceled; private Dictionary<string, int> executingPackageOrderIndex; private string username; private int progress; private int cacheProgress; private int executeProgress; private Version _version = new Version("2.0.0.0"); private bool _installEnabled; private int progressPhases=1; private bool isUnstalling=false; #region Command /// <summary> /// 執行安裝命令 /// </summary> public ICommand InstallCommand { get; private set; } public ICommand UninstallCommand { get; private set; } public ICommand CancelCommand { get; private set; } public ICommand LaunchNewsCommand { get; private set; } private ICommand repairCommand; public ICommand RepairCommand { get { return this.repairCommand ?? (this.repairCommand = new RelayCommand(param => model.PlanAction(LaunchAction.Repair) , param => State == InstallState.Present)); } } #endregion #region 屬性 public string Message { get { return message; } set { if (message != value) { message = value; OnPropertyChanged("Message"); } } } public InstallState State { get { return state; } set { if (state != value) { state = value; Message = "Status: " + state; OnPropertyChanged("State"); Refresh(); } } } public string PackageId { get { return _packageId; } set { if (_packageId != value) { _packageId = "packid:" + value; OnPropertyChanged("PackageId"); } } } public bool Canceled { get { return this.canceled; } set { if (this.canceled != value) { this.canceled = value; OnPropertyChanged("Canceled"); } } } public Version Version { get { return _version; } } public string Username { get { return this.username; } set { this.username = value; this.model.SetBurnVariable("Username", this.username); } } public int Progress { get { if (isUnstalling) { return progress*2; } return this.progress; } set { this.progress = value; OnPropertyChanged("Progress"); OnPropertyChanged("Persent"); } } private string _info; public string Info { get { if(string.IsNullOrEmpty(_info)) _info= InstallEnabled ? "安裝中..." : "進行中..."; return _info; } set { _info = value; OnPropertyChanged("Info"); } } public string Persent { get { return Progress + "%"; } } public bool InstallEnabled { get { return State == InstallState.NotPresent; } } public bool UninstallEnabled { get { return UninstallCommand.CanExecute(this); } } public bool CancelEnabled { get { return State == InstallState.Applying; } } public bool ExitEnabled { get { return this.State != InstallState.Applying; } } public bool ProgressEnabled { get { return this.State == InstallState.Applying; } } /// <summary> /// 先無論 /// </summary> public bool IsUpToDate { get { return true; } } public bool RepairEnabled { get { return this.RepairCommand.CanExecute(this); } } public bool CompleteEnabled { get { return State == InstallState.Applied; } } public int Phases { get { return progressPhases; } } private string _installText = "Uninstall"; public string InstallText { get { return _installText; } set { _installText = value; OnPropertyChanged("InstallText"); } } public string RepairText { get { return _repairText; } set { _repairText = value; } } private bool _lableback=true; private string _repairText = "Repair"; public bool LabelBack { get { return _lableback; } set { _lableback = value; OnPropertyChanged("LabelBack"); } } #endregion #region 構造函數 /// <summary> /// 構造函數 /// </summary> /// <param name="_model"></param> public InstallViewModel(BootstrapperApplicationModel _model) { model = _model; executingPackageOrderIndex = new Dictionary<string, int>(); State = InstallState.Initializing; //處理由bootstrapper觸發的事件 WireUpEventHandlers(); //初始化命令 第一個參數是命令要觸發的方法,第二個匿名函數是命令執行的條件 InstallCommand = new RelayCommand(param => model.PlanAction(LaunchAction.Install), param => State == InstallState.NotPresent); UninstallCommand = new RelayCommand(param => { model.PlanAction(LaunchAction.Uninstall); isUnstalling = true; }, param => State == InstallState.Present); CancelCommand = new RelayCommand(param => { model.LogMessage("Cancelling..."); if (State == InstallState.Applying) { State = InstallState.Cancelled; } else { CustomBootstrapperApplication.Dispatcher.InvokeShutdown(); } }, param => State != InstallState.Cancelled); model.BootstrapperApplication.DetectComplete += DetectComplete; //進度條相關事件綁定 //this.model.BootstrapperApplication.CacheAcquireProgress += //(sender, args) => //{ // this.cacheProgress = args.OverallPercentage; // this.Progress = (this.cacheProgress + this.executeProgress) / 2; //}; //this.model.BootstrapperApplication.ExecuteProgress += //(sender, args) => //{ // this.executeProgress = args.OverallPercentage; // this.Progress = (this.cacheProgress + this.executeProgress) / 2; //}; model.BootstrapperApplication.CacheAcquireProgress += CacheAcquireProgress; model.BootstrapperApplication.ExecuteProgress += ApplyExecuteProgress; model.BootstrapperApplication.ExecuteMsiMessage += ExecuteMsiMessage; model.BootstrapperApplication.PlanBegin += PlanBegin; model.BootstrapperApplication.PlanPackageComplete += PlanPackageComplete; model.BootstrapperApplication.Progress += ApplyProgress; model.BootstrapperApplication.CacheComplete += CacheComplete; } #endregion private void DetectComplete(object sender, DetectCompleteEventArgs e) { if (LaunchAction.Uninstall == CustomBootstrapperApplication.Model.Command.Action) { CustomBootstrapperApplication.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for uninstall"); CustomBootstrapperApplication.Plan(LaunchAction.Uninstall); } else if (Hresult.Succeeded(e.Status)) { if (CustomBootstrapperApplication.Model.Engine.EvaluateCondition("NETFRAMEWORK35_SP_LEVEL < 1")) { string message = "WiX Toolset requires the .NET Framework 3.5.1 Windows feature to be enabled."; CustomBootstrapperApplication.Model.Engine.Log(LogLevel.Verbose, message); if (Display.Full == CustomBootstrapperApplication.Model.Command.Display) { CustomBootstrapperApplication.Dispatcher.Invoke((Action)delegate() { MessageBox.Show(message, "DIAView", MessageBoxButton.OK, MessageBoxImage.Error); if (null != CustomBootstrapperApplication.View) { CustomBootstrapperApplication.View.Close(); } } ); } State = InstallState.Failed; return; } } else { State = InstallState.Failed; } } #region 方法 private void CacheAcquireProgress(object sender, CacheAcquireProgressEventArgs e) { lock (this) { this.cacheProgress = e.OverallPercentage; this.Progress = (this.cacheProgress + this.executeProgress) / this.Phases; e.Result = Canceled ? Result.Cancel : Result.Ok; } } private void ApplyExecuteProgress(object sender, ExecuteProgressEventArgs e) { lock (this) { this.executeProgress = e.OverallPercentage; this.Progress = (this.cacheProgress + this.executeProgress) / 2; // always two phases if we hit execution. if (CustomBootstrapperApplication.Model.Command.Display == Display.Embedded) { CustomBootstrapperApplication.Model.Engine.SendEmbeddedProgress(e.ProgressPercentage, this.Progress); } e.Result = Canceled ? Result.Cancel : Result.Ok; } } /// <summary> /// 這個方法 會在Detect中被調用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void DetectPackageComplete(object sender, DetectPackageCompleteEventArgs e) { PackageId = e.PackageId; //對應的是MsiPackage Id="DIAView" if (e.PackageId.Equals("DIAView", StringComparison.Ordinal)) { State = e.State == PackageState.Present ? InstallState.Present : InstallState.NotPresent; } } private void PlanBegin(object sender, PlanBeginEventArgs e) { lock (this) { if (InstallEnabled) { this.progressPhases = (LaunchAction.Layout == CustomBootstrapperApplication.Model.PlannedAction) ? 1 : 2; } else { LabelBack = false; } InstallText = ""; RepairText = ""; OnPropertyChanged("Phases"); OnPropertyChanged("InstallEnabled"); OnPropertyChanged("InstallText"); OnPropertyChanged("RepairText"); this.executingPackageOrderIndex.Clear(); } } private void PlanPackageComplete(object sender, PlanPackageCompleteEventArgs e) { if (ActionState.None != e.Execute) { lock (this) { Debug.Assert(!this.executingPackageOrderIndex.ContainsKey(e.PackageId)); this.executingPackageOrderIndex.Add(e.PackageId, this.executingPackageOrderIndex.Count); } } } /// <summary> /// PlanAction 結束後會觸發這個方法 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void PlanComplete(object sender, PlanCompleteEventArgs e) { if (State == InstallState.Cancelled) { CustomBootstrapperApplication.Dispatcher.InvokeShutdown(); return; } State = InstallState.Applying; model.ApplyAction(); } /// <summary> /// ApplyAction 開始 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void ApplyBegin(object sender, ApplyBeginEventArgs e) { State = InstallState.Applying; OnPropertyChanged("ProgressEnabled"); OnPropertyChanged("CancelEnabled"); } /// <summary> /// 安裝 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void ExecutePackageBegin(object sender, ExecutePackageBeginEventArgs e) { if (State == InstallState.Cancelled) { e.Result = Result.Cancel; } } private void ExecuteMsiMessage(object sender, ExecuteMsiMessageEventArgs e) { lock (this) { if (e.MessageType == InstallMessage.ActionStart) { this.Message = e.Message; } e.Result = Canceled ? Result.Cancel : Result.Ok; } } private void CacheComplete(object sender, CacheCompleteEventArgs e) { lock (this) { this.cacheProgress = 100; this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; } } /// <summary> /// 卸載 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void ExecutePackageComplete(object sender, ExecutePackageCompleteEventArgs e) { if (State == InstallState.Cancelled) { e.Result = Result.Cancel; } } /// <summary> /// Apply結束 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void ApplyComplete(object sender, ApplyCompleteEventArgs e) { model.FinalResult = e.Status; State = InstallState.Applied; isUnstalling = false; OnPropertyChanged("CompleteEnabled"); OnPropertyChanged("ProgressEnabled"); // CustomBootstrapperApplication.Dispatcher.InvokeShutdown(); } private void ApplyProgress(object sender, ProgressEventArgs e) { lock (this) { e.Result = Canceled ? Result.Cancel : Result.Ok; } } /// <summary> /// 刷新命令狀態 從而改變UI是否使能 /// </summary> private void Refresh() { CustomBootstrapperApplication.Dispatcher.Invoke( (Action)(() => { //((RelayCommand)InstallCommand).CanExecute(this); //.RaiseCanExecuteChanged(); //((RelayCommand)UninstallCommand) //.RaiseCanExecuteChanged(); //((DelegateCommand)CancelCommand) //.RaiseCanExecuteChanged(); })); } /// <summary> /// 事件訂閱 /// </summary> private void WireUpEventHandlers() { model.BootstrapperApplication.DetectPackageComplete += DetectPackageComplete; model.BootstrapperApplication.PlanComplete += PlanComplete; model.BootstrapperApplication.ApplyComplete += ApplyComplete; model.BootstrapperApplication.ApplyBegin += ApplyBegin; model.BootstrapperApplication.ExecutePackageBegin += ExecutePackageBegin; model.BootstrapperApplication.ExecutePackageComplete += ExecutePackageComplete; } #endregion } }
在構造函數內部,WireUpEventHandlers方法裏面的事件訂閱處理的是安裝的狀態。其餘就是處理安裝進度。安裝進度是由兩部分組成的,CacheAcquireProgress 和ExecuteProgress,基於兩者完成的進度平均獲得當前的安裝進度。Wix3.6書中的代碼以下:
this.model.BootstrapperApplication.CacheAcquireProgress += (sender, args) => { this.cacheProgress = args.OverallPercentage; this.Progress = (this.cacheProgress + this.executeProgress) / 2; }; this.model.BootstrapperApplication.ExecuteProgress += (sender, args) => { this.executeProgress = args.OverallPercentage; this.Progress = (this.cacheProgress + this.executeProgress) / 2; };
我按照wix3.8中WixBA的源碼調整了下。卸載的時候常數是1而不是2.
3.在Views中新建一個InstallView.Xaml 。
咱們先作一個簡單的。只有幾個按鈕。最後的界面以下(美觀度先不考慮,;-)): 只有安裝,卸載和取消三個按鈕,再加一個進度條和一個消息提示。
XAML:
<Grid> <StackPanel> <Label Content="{Binding Message}" /> <Button Command="{Binding InstallCommand}">Install</Button> <Button Command="{Binding UninstallCommand}">Uninstall</Button> <Button Command="{Binding CancelCommand}">Cancel</Button> <Label VerticalAlignment="Center">Progress:</Label> <Label Content="{Binding Progress}" /> <ProgressBar Width="200" Height="30" Value="{Binding Progress}" Minimum="0" Maximum="100" /> </StackPanel> </Grid>
CS:
public InstallView(InstallViewModel viewModel) { this.InitializeComponent(); this.DataContext = viewModel; this.Closed += (sender, e) => viewModel.CancelCommand.Execute(this); }
4.入口對象CustomBootstrapperApplication。
public class CustomBootstrapperApplication:BootstrapperApplication { public static Dispatcher Dispatcher { get; set; } static public DiaViewModel Model { get; private set; } static public InstallView View { get; private set; } protected override void Run() { Model = new DiaViewModel(this); Dispatcher = Dispatcher.CurrentDispatcher; var model = new BootstrapperApplicationModel(this); var viewModel = new InstallViewModel(model); View = new InstallView(viewModel); model.SetWindowHandle(View); this.Engine.Detect(); View.Show(); Dispatcher.Run(); this.Engine.Quit(model.FinalResult); } }
public class DiaViewModel { private Version _version; private const string BurnBundleInstallDirectoryVariable = "InstallFolder"; private const string BurnBundleLayoutDirectoryVariable = "WixBundleLayoutDirectory"; public DiaViewModel(BootstrapperApplication bootstrapper) { Bootstrapper = bootstrapper; Telemetry = new List<KeyValuePair<string, string>>(); } /// <summary> /// Gets the bootstrapper. /// </summary> public BootstrapperApplication Bootstrapper { get; private set; } /// <summary> /// Gets the bootstrapper command-line. /// </summary> public Command Command { get { return Bootstrapper.Command; } } /// <summary> /// Gets the bootstrapper engine. /// </summary> public Engine Engine { get { return Bootstrapper.Engine; } } /// <summary> /// Gets the key/value pairs used in telemetry. /// </summary> public List<KeyValuePair<string, string>> Telemetry { get; private set; } /// <summary> /// Get or set the final result of the installation. /// </summary> public int Result { get; set; } /// <summary> /// Get the version of the install. /// </summary> public Version Version { get { if (null == _version) { Assembly assembly = Assembly.GetExecutingAssembly(); FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location); _version = new Version(fileVersion.FileVersion); } return _version; } } /// <summary> /// Get or set the path where the bundle is installed. /// </summary> public string InstallDirectory { get { if (!Engine.StringVariables.Contains(BurnBundleInstallDirectoryVariable)) { return null; } return Engine.StringVariables[BurnBundleInstallDirectoryVariable]; } set { Engine.StringVariables[BurnBundleInstallDirectoryVariable] = value; } } /// <summary> /// Get or set the path for the layout to be created. /// </summary> public string LayoutDirectory { get { if (!Engine.StringVariables.Contains(BurnBundleLayoutDirectoryVariable)) { return null; } return Engine.StringVariables[BurnBundleLayoutDirectoryVariable]; } set { Engine.StringVariables[BurnBundleLayoutDirectoryVariable] = value; } } public LaunchAction PlannedAction { get; set; } /// <summary> /// Creates a correctly configured HTTP web request. /// </summary> /// <param name="uri">URI to connect to.</param> /// <returns>Correctly configured HTTP web request.</returns> public HttpWebRequest CreateWebRequest(string uri) { var request = (HttpWebRequest)WebRequest.Create(uri); request.UserAgent = String.Concat("WixInstall", Version.ToString()); return request; } }
model.SetWindowHandle(view);
this.Engine.Detect();
Dispatcher.Run()
Engine.Quit
<BootstrapperApplicationRef Id="ManagedBootstrapperApplicationHost"> <Payload SourceFile="$(var.CustomBA.TargetDir)CustomBA.dll" /> <Payload SourceFile="$(var.CustomBA.TargetDir)BootstrapperCore.config" /> <Payload SourceFile="$(var.CustomBA.TargetDir)Microsoft.Practices.Prism.Composition.dll" /> <Payload SourceFile="$(var.CustomBA.TargetDir)Microsoft.Practices.Prism.Interactivity.dll" /> <Payload SourceFile="$(var.CustomBA.TargetDir)Microsoft.Practices.Prism.Mvvm.Desktop.dll" /> <Payload SourceFile="$(var.CustomBA.TargetDir)Microsoft.Practices.Prism.Mvvm.dll" /> <Payload SourceFile="$(var.CustomBA.TargetDir)Microsoft.Practices.Prism.PubSubEvents.dll" /> <Payload SourceFile="$(var.CustomBA.TargetDir)Microsoft.Practices.Prism.SharedInterfaces.dll" /> <Payload SourceFile="$(var.CustomBA.TargetDir)Microsoft.Practices.ServiceLocation.dll" /> </BootstrapperApplicationRef>
而後再編譯運行,就ok了。 運行一下安裝包,wpf界面出來,有點小激動,比起xml配置的界面仍是要好多了,主要是給了各類可能。
3、界面優化
固然,上面的界面還不能知足咱們的胃口,我就先把WIXBA的界面抓過來了。
修改了下安裝界面。明白了安裝步驟和事件控制,怎麼折騰界面那都是你本身的事情了。這個界面也不是很好,進度條不顯示的時候糾結顯示些別的什麼。
安裝前: 卸載中:
<Window x:Class="CustomBA.Views.InstallView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowStartupLocation="CenterScreen" WindowStyle="None" AllowsTransparency="True" Background="{x:Null}" Width="400" Height="400"> <Window.Resources> <ResourceDictionary Source="Styles.xaml" /> </Window.Resources> <Grid> <Rectangle MouseLeftButtonDown="Background_MouseLeftButtonDown" Fill="{StaticResource BackgroundBrush}"/> <Grid VerticalAlignment="Stretch" Margin="15"> <Grid.RowDefinitions> <RowDefinition Height="1*"/> <RowDefinition Height="1*"/> <RowDefinition Height="1*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="1*" /> <ColumnDefinition Width="1*" /> <ColumnDefinition Width="1*" /> </Grid.ColumnDefinitions> <Button Grid.Row="0" Grid.ColumnSpan="2" Background="#33CCFF" /> <TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource StatusTextStyle}" Padding="12 0 0 0" >stoneniqiu</TextBlock> <TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource StatusTextStyle}" HorizontalAlignment="Right" Text="{Binding Version}"/> <TextBlock Grid.ColumnSpan="2" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="50" Foreground="White" IsHitTestVisible="False">YourSoft</TextBlock> <Label Grid.Row="2" Grid.Column="0" Margin="3" Grid.ColumnSpan="3" Background="#33CCFF" ></Label> <Grid Grid.Row="2" Grid.Column="0" ColumnSpan="3" > <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <ProgressBar Grid.Row="0" Margin="4" VerticalAlignment="Bottom" Grid.Column="0" Grid.ColumnSpan="3" Width="350" Height="8" Value="{Binding Progress}" Minimum="0" Maximum="100" Visibility="{Binding ProgressEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" /> <Label Grid.Row="1" Grid.Column="0" Background="#33CCFF" Foreground="White" Margin="3" Visibility="{Binding ProgressEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" Content="{Binding Info}" ></Label> <Button Grid.Row="1" Grid.Column="1" Click="ButtonBase_OnClick" Content="" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" ></Button> <Label Grid.Row="1" Grid.Column="2" Background="#33CCFF" Foreground="White" Margin="3" Visibility="{Binding ProgressEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" HorizontalContentAlignment="Right" Content="{Binding Persent}" /> </Grid> <!-- Install --> <Button Grid.Row="1" Grid.ColumnSpan="3" Tag="安裝" Grid.Column="0" Command="{Binding InstallCommand}" Visibility="{Binding InstallEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" Content="Install" /> <Image Grid.Row="1" Grid.Column="2" Source="..\resources\gear.png" Visibility="{Binding InstallEnabled, Converter={StaticResource BooleanToVisibilityConverter}}"/> <Label Grid.Row="1" Grid.Column="0" Margin="3" Grid.ColumnSpan="3" Background="#33CCFF" Visibility="{Binding ProgressEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" ></Label> <!--<TextBlock Grid.Row="2" Grid.ColumnSpan="3" Style="{StaticResource StatusTextStyle}" Visibility="{Binding ProgressEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" Text="{Binding Message}"/>--> <Image Grid.Row="1" Grid.Column="2" Source="..\resources\gear.png" Visibility="{Binding ProgressEnabled, Converter={StaticResource BooleanToVisibilityConverter}}"> <Image.RenderTransform> <RotateTransform x:Name="ProgressRotateTransform" Angle="1"/> </Image.RenderTransform> <Image.Triggers> <EventTrigger RoutedEvent="Image.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="ProgressRotateTransform" Storyboard.TargetProperty="Angle" From="0.0" To="360.0" Duration="0:0:4" RepeatBehavior="Forever"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Image.Triggers> </Image> <!-- Uninstall --> <Button Grid.Row="1" Grid.Column="0" Command="{Binding UninstallCommand}" Visibility="{Binding UninstallEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" Content="{Binding InstallText}" /> <!--<Image Grid.Row="1" Grid.Column="0" Source="..\resources\gear.png" Visibility="{Binding UninstallEnabled, Converter={StaticResource BooleanToVisibilityConverter}}"/>--> <Image Grid.Row="1" Grid.Column="0" Source="..\resources\gear.png" Visibility="{Binding UninstallEnabled, Converter={StaticResource BooleanToVisibilityConverter}}"> <Image.RenderTransform> <RotateTransform x:Name="UpdateRotateTransform" Angle="1"/> </Image.RenderTransform> <Image.Triggers> <EventTrigger RoutedEvent="Image.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="UpdateRotateTransform" Storyboard.TargetProperty="Angle" From="0.0" To="360.0" Duration="0:0:4" RepeatBehavior="Forever"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Image.Triggers> </Image> <!-- Repair --> <Button Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="1" Command="{Binding RepairCommand}" Visibility="{Binding RepairEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" Content="Repair" /> <Image Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Source="..\resources\wrench.png" Visibility="{Binding RepairEnabled, Converter={StaticResource BooleanToVisibilityConverter}}"/> <!-- Complete --> <Button Grid.Row="1" Grid.ColumnSpan="3" Grid.Column="0" IsEnabled="False" Visibility="{Binding CompleteEnabled, Converter={StaticResource BooleanToVisibilityConverter}}">Complete</Button> <Image Grid.Row="1" Grid.Column="2" Source="..\resources\gear.png" Visibility="{Binding CompleteEnabled, Converter={StaticResource BooleanToVisibilityConverter}}"/> <TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource StatusTextStyle}" Visibility="{Binding ProgressEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" Text="{Binding Message}"/> <!-- Failed --> <!--<Button Grid.Row="1" Grid.ColumnSpan="3" Grid.Column="0" Background="#33CCFF" Command="{Binding TryAgainCommand}" Visibility="{Binding TryAgainEnabled, Converter={StaticResource BooleanToVisibilityConverter}}">Failed. Try Again?</Button> <TextBlock Grid.Row="1" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="60" Foreground="White" IsHitTestVisible="False" Visibility="{Binding TryAgainEnabled, Converter={StaticResource BooleanToVisibilityConverter}}">X</TextBlock>--> <!-- Cancel --> <Button Grid.Row="0" Grid.Column="2" Visibility="{Binding CancelEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" Command="{Binding CancelCommand}">Cancel</Button> <TextBlock Grid.Row="0" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="60" Foreground="White" IsHitTestVisible="False" Visibility="{Binding CancelEnabled, Converter={StaticResource BooleanToVisibilityConverter}}">X</TextBlock> <!-- Exit --> <Button Grid.Row="0" Grid.Column="2" Visibility="{Binding ExitEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" Name="Exit" Click="Exit_OnClick" >Exit</Button> <Image Grid.Row="0" Grid.Column="2" Source="..\resources\exit.png" Visibility="{Binding ExitEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" /> </Grid> <!--<StackPanel> <Label Content="{Binding Message}" /> <Label Content="{Binding PackageId}" /> <Button Command="{Binding InstallCommand}">Install</Button> <Button Command="{Binding UninstallCommand}">Uninstall</Button> <Button Command="{Binding CancelCommand}">Cancel</Button> <Label VerticalAlignment="Center">Username:</Label> <TextBox Text="{Binding Username}" Margin="10" MinWidth="150" /> <Label VerticalAlignment="Center">Progress:</Label> <Label Content="{Binding Progress}" /> <ProgressBar Width="200" Height="30" Value="{Binding Progress}" Minimum="0" Maximum="100" /> </StackPanel>--> </Grid> </Window>
小結: 以上就是整個過程,基本走通了,但尚未達到最終目的。要製做想QQ那樣三步的安裝界面,還有2個關鍵比較重要的問題沒有解決。
1.使用了wpf界面後,bundle沒法再先安裝.net . 這意味着要在咱們的wpf界面以前安裝.net(否則wpf界面跑不起來),用wix3.9編譯,會給出提示界面。但安裝.net失敗。
2.用戶選擇路徑問題,本來咱們是經過msi的安裝界面來讓用戶選擇安裝路徑,但如今msi的安裝界面就顯得有些多餘。但這是兩個工程,如何把安裝界面傳遞過去。也是個問題。
這兩個問題解決後,我會在後面的博客中更新。那樣纔算完整。
須要的demo的留郵箱,以爲能夠的就點個贊~
參考資料:
1.書 Wix3.6:A Develop's Guide to Windows Installer XML
2.源碼:wix3.8