2017年11月12日日曜日

WiX toolsetのBootstarpperを試してみた(4)UI追加

さて、前回まででmsi側のUIを使うBootstapperが一応できたが、アンインストール時になにも表示せずに実行、終了してしまうので、UIの追加を行ってみる。

次のようなコードで、OnDetectPackageCompleteで実行/キャンセルの確認ダイアログを表示し、OnApplyCompleteで終了メッセージが表示される。

参照に System.WIndows.Forms を追加。

using System.Windows.Forms;

private void OnDetectPackageComplete(object sender, DetectPackageCompleteEventArgs e)
{
    if (e.PackageId == "MyAppSetup.msi")
    {
        if (e.State == PackageState.Absent)
        {
            Engine.Plan(LaunchAction.Install);
        }
        else if (e.State == PackageState.Present)
        {
            DialogResult res = MessageBox.Show("MyAppSetup",
                "アンインストールを実行しますか?", MessageBoxButtons.OKCancel);
            if (res == DialogResult.Cancel)
            {
                Engine.Quit(0);
            }
            else
            {
                Engine.Plan(LaunchAction.Uninstall);
            }
        }
    }
}

private void OnApplyComplete(object sender, ApplyCompleteEventArgs e)
{
    if (IsUninstall)
    {
        MessageBox.Show(ClassLibrary1.Properties.Resources.UnistallDone);
    }
    Engine.Quit(0);
}

せっかくmsiを多国語化したのに、Bootstarpperが未対応になってしまった。しかし、BootstarpperはC#のクラスライブラリなので、プロジェクトのProperties に String の Resouces.resx を追加することで多国語対応できる。

msiと同様、ベースを英語とし日本語のResources.ja.resxを追加した場合は次のようにBundle.wxs の BootstrapperApplicationRef にresources.dllを追加する。

<BootstrapperApplicationRef Id='ManagedBootstrapperApplicationHost'>
    <Payload SourceFile="..\ClassLibrary1\bin\Release\ClassLibrary1.dll" />
    <Payload SourceFile="..\ClassLibrary1\bin\Release\ja\ClassLibrary1.resources.dll"
        Name="ja\ClassLibrary1.resources.dll" />
    <Payload SourceFile="BootstrapperCore.config" />
</BootstrapperApplicationRef>

Name属性で ja サブフォルダを追加している。Name属性は展開先のパスで、デフォルト値はファイル名であるため、これがないと同じフォルダにファイルがフラットに展開され、resources.dllがロードされずベース言語のみとなる。

ここまでで、Cultureに従ってパラメータを切り替えて多国語化した msi を起動するBootstrapper作るという目的は一応実現した。しかし、これだけのためならdotNetInstallerを使ったBootstrapperのほうが目的に適っている。BurnをするならUIをBootstrapperに移し、msiはサイレントで実行した方がよい。

上記の方法ではインストール実行中、常時ウインドウを表示しておくことはできない。Runは独立したスレッドで呼ばれ、Runからリターンするとスレッドも終了し、ここで表示したFormも消えるか、下手をするとゾンビで残る。また Engine.Quit(0) を呼ぶとプロセスが終了しUIも終了してしまうので、UIが終了してから Engine.Quit(0) を呼ぶ必要がある。

そのためにはUI用のイベントループを作ればよい。ここではFormアプリによる実装例を紹介しておく。

Run メソッドで Detect を呼んだあと、Application によるループを開始する。
このループ終了後、Engine.Quit(0) を呼ぶ。
BootstapperとFormで相互に互いのインスタンスの参照を持ち、適宜メソッド呼び出し、変数設定などを行う。

/クラスライブラリにForm1を追加
private Form1 form;

protected override void Run()
{
    this.DetectPackageComplete += this.OnDetectPackageComplete;
    this.PlanComplete += this.OnPlanComplete;
    this.ApplyComplete += this.OnApplyComplete;
    this.ExecuteProgress += this.OnExecuteProgress;

    if (CultureInfo.CurrentUICulture.TwoLetterISOLanguageName == "ja")
    {
        Engine.StringVariables["TRANSFORMS"] = ":ja-JP.mst";
    }
    else
    {
        Engine.StringVariables["TRANSFORMS"] = "";
    }
    Engine.Detect();

    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    form = new Form1();
    form.Bootstrapper = this;
    Application.Run(form);

    //formがクローズされると Application.Run が終了し、ここにくる。
    this.Engine.Quit(0);
}

また、ExecuteProgressイベントで進行状態が通知される。ここでFormに通知を転送することができ、また ExecuteProgressEventArgs にResult.Cancelをセットするとインストールを中断することができる。

private void OnExecuteProgress(object sender, ExecuteProgressEventArgs e)
{
    if ( someEroorCoccured )
    {
        e.Result = Result.Cancel;
    }
    else
    {
        form.SetProgress(e.OverallPercentage.ToString());
    }
}

関連ブログ
VisualStudio 2017 Community & WiX tools & WiX Edit によるWindowsアプリ インストーラ(msi)作成
WiX toolset msi ダイアログ多国語対応
dotNetInstallerによるシンプルBootstarpper作成
WiX toolsetのBootstarpperを試してみた(1)
WiX toolsetのBootstarpperを試してみた(2)Detect追加
WiX toolsetのBootstarpperを試してみた(3)msiexecパラメータ設定

0 件のコメント: