WiX チュートリアル (日本語訳)

Lesson 8 ユーザー・インタフェイス再び

既に Lesson 2 で述べたように、WiX にはそれ自身の標準的なユーザー・インタフェイス・ライブラリが付属しています。このライブラリはほとんどの標準的な要求を満たすものですが、あなたが自分自身のユーザー・インタフェイスを作成しなければならない時が来るかも知れません。また、たとえ標準的なユーザー・インタフェイスのカスタマイズや修正がしたいだけである場合でも、 WiX が背後で何をしているかを知ることは必要です。このことを達成するために、もう一度、最初から始めましょう。

8.1 一つだけのダイアログ

SampleCustomUI1 InstallDlg のスクリーンショット

最初は、一つだけの簡単なインタフェイスから始めましょう。すなわち、タイトルとインストールを開始するボタンしかないダイアログです。

ダイアログは全て一意の識別子を持たなければなりません。ダイアログのサイズは、私たちが使うウィザードの標準サイズに合せます。Title の意味はそのものずばりですが、ここでも角括弧の表記法を使ってプロパティを参照することが出来ることに注意して下さい。こうしておくと、新しい製品のインストーラ・パッケージを作成するときに、UI の部分をまるごと編集し直す必要が無くなるので、非常に便利です。ProductName は、ソース・ファイルの一番最初の Product タグで定義した製品名を指し示すように、自動的に定義されます。

<Dialog Id="InstallDlg" Width="370" Height="270"
        Title="[ProductName] [Setup]" NoMinimize="yes">

ダイアログに追加する全てのものはコントロールになります。Type 属性がコントロールの種類を示します(コントロールの種類は、Billboard, Bitmap, CheckBox, ComboBox, DirectoryCombo, DirectoryList, Edit, GroupBox, Icon, Line, ListBox, ListView, MaskedEdit, PathEdit, ProgressBar, PushButton, RadioButtonGroup, ScrollableText, SelectionTree, Text, VolumeCostList または VolumeSelectCombo です)。単純なテキスト(通常の Windows 用語では、スタティック・テキストと呼ばれるもの。何もせず、クリックしても反応せず、ただそこにあるだけのテキスト)として、Text のタイプを使用します。そして、位置とサイズを指定します。

タイトル・テキストの要素は、一般的な場合のために、Transparent と指定されています。これらのテキストは上部のバナー・ビットマップの上に重ねられます。今のところは白い背景に黒で文字が描画されていますので、文字の背景を透明にしても何も違いは生じませんが、タイトル・テキストの下にまで延びるフルサイズのバナー画像と色の付いたテキストを提供すれば、素敵な視覚効果を生むことが出来るでしょう。NoPrefix は、アンパサンド(&)が文字通りに表示されるのか、それとも、Windows の GUI の通例に従って、ショートカットを指定するものとして使われるのか、ということを制御しているだけです。

  <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15"
           Transparent="yes" NoPrefix="yes">
    <Text>{\DlgTitleFont}インストール準備完了</Text>
  </Control>

コントロールのテキストを指定するためには二つの方法があります。コントロールの中で Text という子のタグを使うか、または Text 属性を使うかです。

  <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15"
           Transparent="yes" NoPrefix="yes"
           Text="{\DlgTitleFont}インストール準備完了"/>

TextStyle タグを使ってフォントのスタイルを参照することが出来ます。また、インストーラはデフォルトのフォントを決めるために DefaultUIFont という標準のプロパティを必要としますので、このプロパティをソースに含めてフォントを関連付けなければなりません。

      <Property Id="DefaultUIFont">DlgFont8</Property>
      <TextStyle Id="DlgFont8" FaceName="Tahoma" Size="8" />
      <TextStyle Id="DlgTitleFont" FaceName="Tahoma" Size="8" Bold="yes" />

唯一のアクティブなコントロールはプッシュ・ボタン(タイプは PushButton)になります。ここでも、位置とサイズを指定します。一つだけのボタンなので、デフォルトのボタンにもします(エンター・キーの押下に反応します)。ユーザーが操作した時に何かをするアクティブなユーザー・インタフェイス要素は、操作された時に何をするかを定義する Publish タグを内部に入れ子にして持っていなければなりません。選択できる標準的なイベントは数多くあって、長いリストになります(EndDialog はその中の一つです)。ということで、アクションは Return という値を持った EndDialog にします。これは、ダイアログを通常の方法で終了し、何もエラーを発生させない、という意味です。

  <Control Id="Install" Type="PushButton"
           X="304" Y="243" Width="56" Height="17"
           Default="yes" Text="インストール">
    <Publish Event="EndDialog" Value="Return" />
  </Control>
</Dialog>

ということで、ダイアログが出来ましたが、これを通常のイベント進行の中にスケジュールしなければなりません。UI の終了タグの直前の部分に移動して、下記のような変更を加えます。管理者インストールについては、今回は考慮しませんので、AdminUISequence はクリアします。

InstallUISequence については既に言及しましたが、ここで、InstallExecuteSequence との相互作用について述べるときが来ました。

標準的なアクションの進行は、真ん中の InstallValidate アクションで二つに分かれます。このアクションは、利用可能なディスク・スペースを検証したり、インストールによって上書きされるファイルが現在使用中である場合にユーザーに通知したりする役割を持っているものです。InstallValidate とそれ以前のアクションは(さまざまなダイアログ・ボックスを含めて)InstallUISequence のリストに含まれ、一方、このアクションに続くものは InstallExecuteSequence に含まれます。ただし、この規則には、例外があります。基本 UI モードまたは UI 無しのモードでのインストールにおいては、InstallUISequence は参照されませんが、InstallExecuteSentence はこの場合でも単独で動作出来なければなりません。そのために、InstallExecuteSequence は他のテーブルからいくつかのアクションを複製して持っています。現在のサンプルを Orca でチェックしてみると、InstallUISequence には下記のアクションが含まれています。

  1. ValidateProductID
  2. CostInitialize
  3. FileCost
  4. CostFinalize
  5. ExecuteAction

これらのアクションは、最後の一つを除いて、既に知っているものです。インストールの際には、両方のテーブルが最初に参照されて、アクションがシーケンス番号の順に実行されます(複製されたアクションは両方のテーブルで同じ番号を持っていますので、曖昧さは何も生じません)。ExecuteAction は、実際のインストールを開始するのに必要な全ての情報収集が完了した時点にスケジュールされています。そして、インストールの実行は InstallExecuteSequence へと移行して、実際のインストールの過程を受け持つアクションを実行します。

結果として、私たちの一つだけのダイアログ・ボックスは、準備過程の最後のアクションである CostFinalize の後、それでも ExecuteAction よりは前、という順位にスケジュールしなければならないことになります。

    <InstallUISequence>
      <Show Dialog="InstallDlg" After="CostFinalize" />
    </InstallUISequence>
  </UI>

このサンプルをビルドして走らせてみて下さい(カスタム UI のサンプルは 単一のダウンロード・パッケージにまとめられています)。ダイアログが表示され、「インストール」ボタンを押すと、いつもの三つのファイルがインストールされます。進捗ダイアログは無く、サイレント・インストールが行われるだけです。また、インストールのキャンセルは出来ず、開始したら、完了しなくてはなりません。

† 訳註:カスタム UI のサンプルの日本語版は Sample-8-1-CustomUI.zip です。

WiX 内蔵の検証プログラムが、標準的なユーザー・インタフェイスの機能がいくつか欠けていることに関して、ICE 20 の警告を発することに注目して下さい。カスタム UI のレッスンの始めの方のバージョンは、完全な標準的インタフェイスからは程遠いものですから、コマンド・ライン・スイッチを使ってそういう警告を抑止しても構わないでしょう。

candle.exe SampleCustomUI1.wxs
light.exe -sice:ICE20 SampleCustomUI1.wixobj

8.2 チューニング・アップ

SampleCustomUI2 は、引き続いて一つだけのダイアログですが、ほんの少しチューン・アップします。

SampleCustomUI2 InstallDlg のスクリーンショット

<Dialog Id="InstallDlg" Width="370" Height="270"
        Title="[ProductName] [Setup]" NoMinimize="yes">

これは、ほんの小さな変更ですが、ボタンのテキストを直接指定せずに、プロパティを使います。これによって、後の段階で地域化することが容易になります。

  <Control Id="Install" Type="PushButton"
           X="304" Y="243" Width="56" Height="17"
           Default="yes" Text="[ButtonText_Install]">
    <Publish Event="EndDialog" Value="Return" />
  </Control>

ダイアログの上部に簡単なバナー・ビットマップを配置します。バイナリの添付データを参照するのにプロパティを使うことを覚えておいて下さい。Text 属性として指定されていますが、これはテキストではなく、パッケージに保存されているビットマップの Id です。

  <Control Id="BannerBitmap" Type="Bitmap"
           X="0" Y="0" Width="370" Height="44"
           TabSkip="no" Text="[BannerBitmap]" />

テキストを二行追加します。一つは、バナー・ビットマップの上に置く、背景が透明なもの、もう一つは、実際のダイアログ・ワーク・エリアの上に置くものです。

  <Control Id="Description" Type="Text"
           X="25" Y="23" Width="280" Height="15"
           Transparent="yes" NoPrefix="yes">
    <Text>[Wizard] がインストールを開始する準備が完了しました。</Text>
  </Control>

  <Control Id="Text" Type="Text"
           X="25" Y="70" Width="320" Height="20">
    <Text>[\[]インストール[\]] をクリックして、インストールを開始して下さい。</Text>
  </Control>

ダイアログ・ワーク・エリアの下端を示す水平線を置きます — 見た目だけのものに過ぎません。

  <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />

最後に、タイトルを置き、バナー・ビットマップのすぐ下にエンボス・ラインを置きます。

  <Control Id="Title" Type="Text"
           X="15" Y="6" Width="200" Height="15"
           Transparent="yes" NoPrefix="yes">
    <Text>{\DlgTitleFont}インストール準備完了</Text>
  </Control>

  <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
</Dialog>

バナー・ビットマップを参照したことを忘れずに、それをパッケージに含めましょう。

<Binary Id="bannrbmp" SourceFile="Binary\Banner.bmp" />

8.3 相互作用

ダイアログを作成することは、どちらかと言えば単純です(ページに配置できるコントロールのタイプはもっと沢山ありますが)。本当に面白いのは、ダイアログ同士を相互作用させることです。SampleCustomUI3 では、第二のダイアログを追加して、ユーザーがインストールのプロセスをキャンセルすることが出来るようにします。

CancelDlg のスクリーンショット

この目的を達するためには、ダイアログそのものが必要です。ダイアログは二つのプッシュ・ボタンを持ちますが、用語の違いに気を付けて下さい。このようなダイアログ・ボックスは二つの出力を持ち得ます。すなわち、ダイアログが言う通りにするのをキャンセルするか、オーケーするかです。このダイアログでの質問は違う言い方になります。キャンセルするダイアログをキャンセルするという事は、インストールを続けることに賛成するということです。従って、第一の、デフォルトのボタン(いいえ というテキストを持ったボタン)を、キャンセル・ボタンと呼びます。

<Dialog Id="CancelDlg" Width="260" Height="85"
        Title="[ProductName] [Setup]" NoMinimize="yes">
  <Control Id="No" Type="PushButton"
           X="132" Y="57" Width="56" Height="17"
           Default="yes" Cancel="yes" Text="[ButtonText_No]">
    <Publish Event="EndDialog" Value="Return">1</Publish>
  </Control>

ユーザーがこのボタンをクリックすると、Return の値を持った EndDialog イベントが発生します。その名前が示すように、このイベントは単純にダイアログを終了して、元の操作を再開します。Publish タグは、条件式として、1 という数値を持っています。1 は常にと評価されます(0 はと評価されます)ので、イベントは無条件に発行されます。

第二の、Yes というテキストを持ったボタンは、同じ EndDialog イベントを発生させますが、Exit という別の値を伴います。この値は、インストールの操作全体を中止するために使われます。

  <Control Id="Yes" Type="PushButton"
           X="72" Y="57" Width="56" Height="17" Text="[ButtonText_Yes]">
    <Publish Event="EndDialog" Value="Exit">1</Publish>
  </Control>

残りは簡単です。テキストとアイコンです。Binary タグを追加してパッケージにアイコンを入れることを忘れないで下さい。

  <Control Id="Text" Type="Text"
           X="48" Y="15" Width="194" Height="30">
    <Text>[ProductName] のインストールを中止してよろしいですか?</Text>
  </Control>

  <Control Id="Icon" Type="Icon"
           X="15" Y="15" Width="24" Height="24" ToolTip="情報アイコン"
           FixedSize="yes" IconSize="32" Text="[InfoIcon]" />
</Dialog>

<Binary Id="info" SourceFile="Binary\Info.ico" />

InstallDlg ダイアログにも、いくらか修正が必要です。Install ボタンを左へ移動して、Cancel ボタンを置く場所を作りました。この Cancel ボタンが、起動するダイアログの名前を指定して、SpawnDialog イベントを発生させます。前のレッスンで既に述べたように、SpawnDialog は新しい子ダイアログを開始しますが、現在のダイアログは削除せず、ユーザーがその第二のダイアログを終了すると動作を続行します。

  <Control Id="Cancel" Type="PushButton"
           X="304" Y="243" Width="56" Height="17"
           Cancel="yes" Text="[ButtonText_Cancel]">
    <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
  </Control>

8.4 カスタマイズがいっぱい

さて、今度は、もっと野心的なことをやりましょう。ユーザーにセットアップのカスタマイズを許して、どの機能をインストールするかを決定したり、ファイルをインストールする場所をインストーラに指示したり出来るようにします。

CustomizeDlg のスクリーン・ショット

まず最初に、サンプルの複雑さを緩和するために、全てのテキスト項目を UI_Texts.wxs という独立したフラグメント・ファイルに送り込んでしまいます。実際のテキストは WiX のソースから取ってきたもので、私たちの UI 実験で最終的に必要になるかもしれない項目を全部記載したものです。これを参照として呼び出すだけにします。ビルド・プロセスは以下のようになります。

† 訳註:サンプルの日本語版(Sample-8-1-CustomUI.zip)では、UI_Texts.wxs は目立つところしか日本語に翻訳していません。UI_Texts.wxs は、内容としては、WixUI_ja-jp.wxl と同じものです。

candle.exe SampleCustomUI4.wxs UI_Texts.wxs
light.exe -out SampleCustomUI4.msi SampleCustomUI4.wixobj UI_Texts.wixobj

必要なコントロールを提供する新しいダイアログを作りましょう — まず最初に、機能のツリー・ビューです。これは非常に簡単です。と言うのは、SelectionTree タイプのコントロールは単なるツリー・ビュー・コントロールではなくて、インストーラによって有効な機能とその選択状態にリンクされるものだからです。

<Dialog Id="CustomizeDlg" Width="370" Height="270"
        Title="[ProductName] [Setup]" NoMinimize="yes" TrackDiskSpace="yes">
  <Control Id="Tree" Type="SelectionTree"
           X="25" Y="85" Width="175" Height="95"
           Property="_BrowseProperty" Sunken="yes" TabSkip="no"
           Text="選択ツリー" />

ダイアログはいくつかのプッシュ・ボタンを持ちます。Browse ボタンは、Feature タグで ConfigurableDirectory 属性を使っていると、インストーラによって自動的に有効化されます。選択ツリーとの関係で使用されるコントロール・イベントがいくつかあります。SelectionBrowse イベントは、ユーザーがインストール先のパスを修正出来るように、指定された参照ダイアログを開きます。この参照ダイアログについては、すぐ後で述べます。

  <Control Id="Browse" Type="PushButton"
           X="304" Y="200" Width="56" Height="17" Text="[ButtonText_Browse]">
    <Publish Event="SelectionBrowse" Value="BrowseDlg">1</Publish>
  </Control>

Reset ボタンは、Reset という作成済みのイベントを使用します。このイベントは、ダイアログの全てのコントロールを作成時の状態に戻します。つまり、ユーザーによって為された機能のカスタマイズをすべてアンドゥーします。

このボタンはイベント・メッセージを送信するだけでなく、イベントをサブスクライブする(subscribe, 予約する、引き受ける)ことによって、同じようなメッセージの受信者にもなっています。SelectionNoItems イベントは、選択ツリーがノードを持っていないときに、このイベントを予約しているボタンを無効化します。

  <Control Id="Reset" Type="PushButton"
           X="42" Y="243" Width="56" Height="17" Text="[ButtonText_Reset]">
    <Publish Event="Reset" Value="0">1</Publish>
    <Subscribe Event="SelectionNoItems" Attribute="Enabled" />
  </Control>

このダイアログには、既に私たちがよく知っているボタンや単純なコントロールが他にもあります。ここではもう詳しく説明することはしませんので、ソース・ファイルを参照して下さい。しかし、ダイアログの右側にあるボックスについては、詳しく見ていきます。Text コントロールが、ユーザーが選択ツリーで現在選んでいる項目についての情報を表示するのに使われます。このコントロールは初期値のテキスト("現在選ばれている項目の複数行の説明。")を持っていますが、このテキストは表示されず、実際に選択されている項目の情報によって置き換えられます。この事は、このコントロールが SelectionDescription イベントを予約していることによって生じます。選択状態に変化があるや否や、インストーラは、このイベントを予約している全てのコントロールに対して、選択された項目の説明を知らます。

  <Control Id="Box" Type="GroupBox" X="210" Y="81" Width="140" Height="98" />

  <Control Id="ItemDescription" Type="Text" X="215" Y="90" Width="131" Height="30">
    <Text>現在選ばれている項目の複数行の説明。</Text>
    <Subscribe Event="SelectionDescription" Attribute="Text" />
  </Control>

同じ事が他のコントロールにも発生します。ItemSize は現在選択されている項目のサイズを受け取ることを予約していますし、Location はユーザーによって選択されたパスを教えてもらうように予約しています。LocationLocationLabel は、設定すべきパスがあるかどうか、ということもチェックしています。設定すべきパスが無い場合は、ラベルもパスも表示を抑止されます。選択ツリーでメインのノードからサブ・ノードへ移動するとパスの選択の表示が消えるのは、このことによっています。

  <Control Id="ItemSize" Type="Text" X="215" Y="130" Width="131" Height="45">
    <Text>現在選ばれている項目のサイズ。</Text>
    <Subscribe Event="SelectionSize" Attribute="Text" />
  </Control>

  <Control Id="Location" Type="Text" X="75" Y="200" Width="215" Height="20">
    <Text>現在選ばれている項目のパス</Text>
    <Subscribe Event="SelectionPath" Attribute="Text" />
    <Subscribe Event="SelectionPathOn" Attribute="Visible" />
  </Control>

  <Control Id="LocationLabel" Type="Text" X="25" Y="200" Width="50" Height="10"
           Text="場所:">
    <Subscribe Event="SelectionPathOn" Attribute="Visible" />
  </Control>
</Dialog>

私たちはユーザーが Browseボタンをクリックしたときに BrowseDlg ダイアログを呼ぶべきことをインストーラに指示しましたので、このダイアログも提供しなければなりません。

BrowseDlg のスクリーン・ショット

前に戻って、以前のDialog タグを調べると、Property への参照があることに気付くでしょう。ここでも、パス・エディット・コントロールでプロパティへの参照を定義します — このプロパティへの参照が、パスを要求する親ダイアログと、パスを提供するこのダイアログの間のリンクを作成します。Indirect をセットして、パスの受け渡しに使用するプロパティが直接に言及されているプロパティ(_BrowseProperty)ではなく、このプロパティが指し示す別のプロパティであることを、インストーラに対して指示しています。

<Dialog Id="BrowseDlg" Width="370" Height="270"
        Title="[ProductName] [Setup]" NoMinimize="yes">
  <Control Id="PathEdit" Type="PathEdit"
           X="84" Y="202" Width="261" Height="18"
           Property="_BrowseProperty" Indirect="yes" />

† 訳註:Indirect="yes" の場合、コントロールが表示および更新すべきパスは、指定されているプロパティそのものが保持するのではなく、そのプロパティによって間接参照されているプロパティが保持することになります。どのプロパティが間接参照されることになるかは、動的に変化します。

このサンプルの場合は、親ダイアログから SelectionBrowse イベントによって BrowseDlg ダイアログを呼び出す際に、親ダイアログの SelectionTree に関連づけられたプロパティ(_BrowseProperty)に、変更対象のパスを保持するプロパティ(例えば INSTALLDIR)がセットされます。パスを保持するプロパティは、SelectionTree の選択状態に応じて、違うものになり得ます。サンプルでは、変更可能なパスは一つしかありませんが、いつもそうだとは限りません。

すなわち、パスを保持するプロパティを間接参照するのは、SelectionBrowse イベントの仕様がそのようにすることを要求するからです。なお、SelectionBrowse イベントは Browse ボタンによって発行されますが、実際には、SelectionTree に属するイベントであると言って良いものです。

ユーザーが新しく選んだパスに満足して OK を押すと、SetTargetPath イベントによってプロパティの値が設定されます。インストーラは選択されたパスの妥当性もチェックします。ユーザーが結局パスを設定しないことに決めた場合は、Reset イベントを使って全てを初期設定の状態に戻し、ダイアログがパスを実際には設定しないようにします。

  <Control Id="OK" Type="PushButton"
           X="304" Y="243" Width="56" Height="17" Default="yes"
           Text="[ButtonText_OK]">
    <Publish Event="SetTargetPath" Value="[_BrowseProperty]">1</Publish>
    <Publish Event="EndDialog" Value="Return">1</Publish>
  </Control>

  <Control Id="Cancel" Type="PushButton"
           X="240" Y="243" Width="56" Height="17" Cancel="yes"
           Text="[ButtonText_Cancel]">
    <Publish Event="Reset" Value="0">1</Publish>
    <Publish Event="EndDialog" Value="Return">1</Publish>
  </Control>

† 訳註:ここでも、SetTargetPath イベントに対する引数として、プロパティが間接参照で渡されています。

DirectoryCombo コントロールは、参照するプロパティに保存されているパスを、階層構造を持ったツリーのビューで表示します。

  <Control Id="ComboLabel" Type="Text"
          X="25" Y="58" Width="44" Height="10" TabSkip="no"
          Text="場所(&L)" />

  <Control Id="DirectoryCombo" Type="DirectoryCombo"
          X="70" Y="55" Width="220" Height="80"
          Property="_BrowseProperty" Indirect="yes" Fixed="yes" Remote="yes">
    <Subscribe Event="IgnoreChange" Attribute="IgnoreChange" />
  </Control>

アイコンを持った二つのボタンは、ディレクトリ選択コントロールと関連づけられて、押された時に適切なイベントを発行します。

  <Control Id="Up" Type="PushButton"
           X="298" Y="55" Width="19" Height="19"
           ToolTip="一つ上のフォルダへ"
           Icon="yes" FixedSize="yes" IconSize="16" Text="Up">
    <Publish Event="DirectoryListUp" Value="0">1</Publish>
  </Control>

  <Control Id="NewFolder" Type="PushButton"
           X="325" Y="55" Width="19" Height="19"
           ToolTip="新しいフォルダを作成する"
           Icon="yes" FixedSize="yes" IconSize="16" Text="New">
    <Publish Event="DirectoryListNew" Value="0">1</Publish>
  </Control>

そして最後に、真ん中に大きな DirectoryList を置きます。このコントロールは、同じプロパティを参照しているという事実によって、他のディレクトリを制御する要素とリンクされています。これらすべてのコントロールが自動的に期待している通りの相互作用をします。

  <Control Id="DirectoryList" Type="DirectoryList"
           X="25" Y="83" Width="320" Height="110"
           Property="_BrowseProperty" Sunken="yes" Indirect="yes" TabSkip="no" />

ダイアログの残りの要素は、もはや特別に興味深いものではありませんので、必要に応じてソースを参照して下さい。

SampleCustomUI4 をビルド出来るようにするために残っている仕事は二~三の小さな修正だけです。InstallDlg ダイアログに Back ボタンを追加して、ユーザーがカスタマイズ・ダイアログに戻ることが出来るように修正します。

<Control Id="Back" Type="PushButton"
         X="180" Y="243" Width="56" Height="17"
         Text="[ButtonText_Back]">
  <Publish Event="NewDialog" Value="CustomizeDlg">1</Publish>
</Control>

CustomizeDlg ダイアログのスケジューリングを変更して、カスタマイズを実行すべき段階でそれを表示するようにしなければなりません。それにはいくつかの選択肢がありますが、ここでは MigrateFeatureStates イベントの後にスケジュールします。このイベントはアップグレードとインストールの場合にだけ発生し、製品の削除またはメンテナンスの際には発生しません。このイベントが既にインストールされている製品(もし有れば)の機能の選択状態を読み込みます。従って、適切な機能の選択状態をもってカスタマイズ・ダイアログを表示するためには、現在のサンプルよりももっと複雑なインストーラ・パッケージの場合でも、この位置が最適の選択肢です。

<InstallUISequence>
  <Show Dialog="CustomizeDlg" After="MigrateFeatureStates">NOT Installed</Show>
</InstallUISequence>

そして最後に、ブラウズ・ダイアログで必要になる二つの新しいアイコンを忘れずに入れておきます。

<Binary Id="Up" SourceFile="Binary\Up.ico" />
<Binary Id="New" SourceFile="Binary\New.ico" />

カスタマイズをカスタマイズする

インストールされる機能を自動的に選択したり、別の条件によって選択したりする必要がある場合、もしくは、ユーザーが機能を選択するための全く新しいインタフェイス(例えば、SelectionTree の代りにチェックボックスを使うなど)を作成したい場合は、下記のイベントを適切なコントロール(例えば「次へ」ボタン)にリンクさせて、所定の機能のインストールを有効化または無効化することが出来ます。

<Publish Event="AddLocal" Value="FeatureId">...condition...</Publish>
<Publish Event="Remove" Value="FeatureId">...condition...</Publish>

8.5 これが進捗ですか?

成長している私たちのユーザー・インタフェイスにまだ欠けているものがあります。インストール・プロセスがどのように進捗しているかを示すページです。

ProgressDlg のスクリーン・ショット

私たちはこのダイアログをモードレスであると定義します。というのは、制御をインストーラに戻す必要があるからです。そうやって、インストーラからダイアログに対して処理すべき進捗メッセージを送るようにします。

<Dialog Id="ProgressDlg" Width="370" Height="270"
        Title="[ProductName] [Setup]" Modeless="yes">

Back ボタンと Next ボタンはデフォルトで無効化されます — 有効にしておく理由はありません。どっちみち、ユーザーはこれらのボタンを使うことが出来ません。必要であれば、Cancel ボタンを使ってインストールを中止することが出来ます。

  <Control Id="Cancel" Type="PushButton"
           X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes"
           Text="[ButtonText_Cancel]">
    <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
  </Control>
  <Control Id="BannerBitmap" Type="Bitmap"
           X="0" Y="0" Width="370" Height="44"
           TabSkip="no" Text="[BannerBitmap]" />
  <Control Id="Back" Type="PushButton"
           X="180" Y="243" Width="56" Height="17" Disabled="yes"
           Text="[ButtonText_Back]" />
  <Control Id="Next" Type="PushButton"
           X="236" Y="243" Width="56" Height="17" Disabled="yes"
           Text="[ButtonText_Next]" />

プログレス・バーのすぐ上にあるテキストは ActionText イベントを予約していますので、インストーラは現在実行しているインストールのアクション名をテキストに対して継続的に発行します。

  <Control Id="ActionText" Type="Text"
           X="70" Y="100" Width="265" Height="10">
    <Subscribe Event="ActionText" Attribute="Text" />
  </Control>

主要なステップの説明だけでは満足できず、個別のファイルが配置されるときにその詳細な情報を見たいという場合は、ActionData イベントを代りに使うことも出来ます。

  <Control Id="ActionData" Type="Text"
           X="70" Y="100" Width="265" Height="30">
    <Subscribe Event="ActionData" Attribute="Text" />
  </Control>

興味を引かないいくつかのコントロールに加えて、最後にこのダイアログの主役である ProgressBar タイプのコントロールを追加します。SelectionTree と同じように、インストーラによって提供されるこのコントロールは単なる標準のプログレス・バーではなくて、インストールの過程と直接にリンクされたものです。SetProgress イベントを Progress 属性で予約することによって、表示する進捗メッセージをインストーラから受け取り続けます。ProgressBlocks = yes はブロック化された新しいタイプのプログレス・バーを指定しています。これを no の設定にすると、Windows 95 時代からの古いスタイルの連続的なバーに立ち帰ります。

  <Control Id="ProgressBar" Type="ProgressBar"
           X="35" Y="115" Width="300" Height="10"
           ProgressBlocks="yes" Text="Progress done">
    <Subscribe Event="SetProgress" Attribute="Progress" />
  </Control>

  <Control Id="StatusLabel" Type="Text"
           X="35" Y="100" Width="35" Height="10"
           Text="Status:" />
</Dialog>

進捗ページをインストーラ・パッケージの残りの部分と結合するために必要なことは、InstallDlg ダイアログを修正してこのダイアログを呼ぶようにすることだけです。残りの作業は魔法のごとく自動的に行われます。

<Dialog Id="InstallDlg" Width="370" Height="270"
        Title="[ProductName] [Setup]" NoMinimize="yes">
  <Control Id="Install" Type="PushButton"
           X="236" Y="243" Width="56" Height="17" Default="yes"
           Text="[ButtonText_Install]">
    <Publish Event="NewDialog" Value="ProgressDlg" />
  </Control>

8.6 よく出来ました

足りないものが一つだけあります。インストールの後にアプリケーションを起動する方法です。ここでは、チェックボックスの使い方と、チェックボックスの状態に従って決定を行う方法も示します。

ExitDlg のスクリーン・ショット

新しく追加されたダイアログはこのような外観です。

<Dialog Id="ExitDlg" Width="370" Height="270"
        Title="[ProductName] [Setup]" NoMinimize="yes">

Finish ボタンは二つの仕事を実行します。第一に、ダイアログ自身を終了します(そして、それによって、インストーラ・パッケージ自身を終了します)。第二に、ユーザーがそうすることを選んだ場合に、アプリケーションを起動します。

  <Control Id="Finish" Type="PushButton"
           X="236" Y="243" Width="56" Height="17" Default="yes" Cancel="yes"
           Text="[ButtonText_Finish]">
    <Publish Event="EndDialog" Value="Return">1</Publish>
    <Publish Event='DoAction' Value='LaunchFile'>
      (NOT Installed) AND (LAUNCHPRODUCT = 1)
    </Publish>
  </Control>
  ...

ダイアログ・ボックスの中のチェックボックス・コントロールは、初期設定値(CheckBoxValue)と、チェックボックスの状態を読むために使われる関連づけられたプロパティ(LAUNCHPRODUCT)の両方を持ちます。

  <Control Id="Launch" Type="CheckBox"
           X="135" Y="120" Width="150" Height="17"
           Property='LAUNCHPRODUCT' CheckBoxValue='1'>
    <Text>[ProductName] を起動する</Text>
  </Control>
  ...
</Dialog>

Finish ボタンによって発行されるアクションは既によく知っているカスタム・アクションです。アプリケーションが走り続けてもインストーラが閉じることが出来るように、Return 属性を忘れないようにして下さい。

<CustomAction Id='LaunchFile' FileKey='HogeEXE' ExeCommand='' Return="asyncNoWait" />

この終了ダイアログは、インストールが成功して完了した場合に表示されるようにスケジュールされます(詳細は次のセクションを見て下さい)。

<InstallUISequence>
  ...
  <Show Dialog="ExitDlg" OnExit="success" />
</InstallUISequence>

チェックボックスとプロパティがリンクされるのはイベントが発生する時、すなわちユーザーがチェックを入れたり外したりする時だけです。初期状態では、ユーザーとの相互作用がまだ無いため、プロパティが Control タグで設定したデフォルト値を受け取る機縁となるものはありません。従って、必ず私たち自身がプロパティを初期化しなければなりません。

<Property Id="LAUNCHPRODUCT">1</Property>

SampleCustomUI6 をビルドする前に、ダミーの .exe ファイルを何か実際に起動するものに必ず置き換えて下さい。

† 訳註:サンプルの日本語版(Sample-8-1-CustomUI.zip)には起動するプログラムが同梱されています(実は最初のレッスンの日本語版サンプルから、ずっと、そうしています)。

そして、よくある不満について。そう、チェックボックスは透明の背景を持つことが出来ません。ちょうど上に示した例のように、背景にビットマップがあると不格好な表示になります。唯一の回避策は、チェックボックスの幅をボックス自体の実際の幅まで縮小して、その隣に追加のスタティック・テキスト(これは背景を透明に出来ます)を置くことです。

8.7 法律用語

いくつか細かい所が残っています。例えば使用許諾契約書のページです。

LicenseAgreementDlg のスクリーン・ショット

    <UI>
      ...
      <Dialog Id="LicenseAgreementDlg" Width="370" Height="270"
              Title="[ProductName] 使用許諾契約書" NoMinimize="yes">

ラジオ・ボタン・グループについては、ソースの後の方で独立した記述をします。リンクは Property 属性によって確立されます。

        <Control Id="Buttons" Type="RadioButtonGroup"
                 X="20" Y="187" Width="330" Height="40"
                 Property="IAgree" />

        <Control Id="Back" Type="PushButton"
                 X="180" Y="243" Width="56" Height="17"
                 Text="[ButtonText_Back]">
          <Publish Event="NewDialog" Value="WelcomeDlg">1</Publish>
        </Control>

        <Control Id="Next" Type="PushButton"
                 X="236" Y="243" Width="56" Height="17" Default="yes"
                 Text="[ButtonText_Next]">

今回、次に表示するダイアログの選び方は、ほんの少し手が込んでいます。インストールによっては、ユーザー名、会社名、および、登録キーの入力をユーザーに求めたくないことがあります。それに応じて UI を修正する(ページの進行の中で単純にそのページを呼ばないようにする)ことも可能ですが、ここではもっとエレガントな解法を用います。ShowUserRegistrationDlg というプロパティをどこかで定義しておきます。このプロパティを 1 に設定すれば、ユーザー登録ページが表示されます。0 に設定すれば、ユーザー登録ページはスキップされます。このことは、それぞれの場合に対応する二つの NewDialog イベントを用意することを意味します。

          <Publish Event="NewDialog" Value="UserRegistrationDlg">
            <![CDATA[IAgree = "Yes" AND ShowUserRegistrationDlg = 1]]>
          </Publish>

          <Publish Event="NewDialog" Value="SetupTypeDlg">
            <![CDATA[IAgree = "Yes" AND ShowUserRegistrationDlg <> 1]]>
          </Publish>

SpawnDialog イベントと SpawnWaitDialog イベントは前のページを置き換えずに、新しい子ダイアログ・ボックスを開始します。前者はユーザーのアクションによって終了されるのを待ちますが、後者は条件式がである間だけ表示されます。私たちの場合では、インストーラが必要なディスク容量を計算している間だけ表示される「お待ち下さい」ダイアログが後者の例です。サンプルのような小さなインストーラ・パッケージでは、計算には全く時間がかかりませんので、このダイアログが動いているのを見る機会はまず有りません。それでも、念のために、ダイアログを用意しておきましょう。CostingComplete は、必要なディスク容量の計算が完了した時に 1 に設定される定義済みのプロパティです。

          <Publish Event="SpawnWaitDialog" Value="WaitForCostingDlg">
            CostingComplete = 1
          </Publish>

そして、最後に、おなじみの嫌がらせです。「次へ」ボタンはユーザーが使用許諾契約への同意を示すまでは無効化された状態に留まります。私たちは既に Condition タグを上位のレベルで使ったり(インストールのプロセス全体を走らせるべきかどうかを決める起動条件)、Feature タグの中で使ったりしました(さまざまな機能のインストールを条件によって無効化する)。ここで第三の使い方、Control タグの中での使い方を説明します。Action 属性を使うと、Condition タグの内側の条件がと評価される場合に、コントロールを disable, enable, hide または show する(あるいは default の状態に戻す)ことが出来ます。

          <Condition Action="disable">
            <![CDATA[IAgree <> "Yes"]]>
          </Condition>

          <Condition Action="enable">
            IAgree = "Yes"
          </Condition>
        </Control>

小さな事ですが、注意深い読者は気付いているでしょう。条件式のいくつかに対して、私たちは格好悪い <![CDATA[...]]> ラッパーを使いました。コンパイラのパーサーが XML タグの間に出現する < や > のような特殊な文字によって混乱することが無いようにするためです。最も安全な方法は条件式を全てラップすることでしょう(WiX のデコンパイラ、Dark はまさしくそうします)。けれども — 少なくとも私の愚見では — それではソースがあまりにも読みにくくなります。それが嫌なら、本当に必要な所だけにラッパーを使うために、どの式が曖昧でどの式がそうでないかについて自分で配慮しなければなりません(何か理解できない所があれば、コンパイラがエラー・メッセージを出してくれます)。どちらを選ぶかは、あなた次第です ...

次の部分は既によく知っているところです。

        <Control Id="Cancel" Type="PushButton"
                 X="304" Y="243" Width="56" Height="17" Cancel="yes"
                 Text="[ButtonText_Cancel]">
          <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
        </Control>

        <Control Id="BannerBitmap" Type="Bitmap"
                 X="0" Y="0" Width="370" Height="44" TabSkip="no"
                 Text="[BannerBitmap]" />

次に使用許諾契約書のテキストが来ます。スクロール可能なコンテンツを持った凹んだテキストのコントロールを開きます。実際のテキストは内部の Text タグに入ります。ここでは RTF 形式のテキスト・ファイルを指定することが出来ます。ですから、使用許諾契約書をワード・プロセッサで書いて RTF としてエクスポートするというのが、最も良い考えです(この目的のためには、ワードパッドがおそらく最善のワード・プロセッサです。高機能なものを使うと、RTF ファイルがひどく冗長なものになります。高機能なワード・プロセッサを使うとしても、最終版をワードパッドで保存し直すことを考慮して下さい)。

        <Control Id="AgreementText" Type="ScrollableText"
                 X="20" Y="60" Width="330" Height="120" Sunken="yes" TabSkip="no">
          <Text SourceFile="Binary\License.rtf" />
        </Control>

契約書のテキストは、ソース・ファイルのこの箇所に直接に書き込むことも出来ます。しかし、既に述べた方法の方が、はるかに保守が容易であると思われます。

          <Text>{\rtf1\ansi\ansicpg1252\deff0\deftab720
            {\fonttbl{\f0\froman\fprq2 Times New Roman;}}
            {\colortbl\red0\green0\blue0;}
            \deflang1033\horzdoc{\*\fchars }{\*\lchars }
            \pard\plain\f0\fs20
            This End User License Agreement is a legal agreement between you
            (either an individual or a single entity) and ...\par
            }

残されている部分は本当に簡単で、詳細な説明には値しません。あっちやこっちに、タイトル行と水平線を付けて、ダイアログの残りの部分を作ります。

        <Control Id="Description" Type="Text"
                X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
          <Text>以下の使用許諾契約書を注意深く読んで下さい</Text>
        </Control>
        <Control Id="BottomLine" Type="Line"
                X="0" Y="234" Width="370" Height="0" />
        <Control Id="Title" Type="Text"
                X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
          <Text>{\DlgTitleFont}エンド・ユーザー使用許諾契約書</Text>
        </Control>
        <Control Id="BannerLine" Type="Line"
                X="0" Y="44" Width="374" Height="0" />
      </Dialog>

返済すべき借りがまだ有ります。ダイアログの最初のコントロールで、ラジオ・ボタン・グループの記述に言及しましたが、それが保留されたまま残っています。ですので、以下にそれを示します。Text 属性の中のプロパティ置換に注目して下さい。このようにすると、テキストのフォント、サイズ、および色を指定する事が出来ます。

      <RadioButtonGroup Property="IAgree">
        <RadioButton Text="{\DlgFont8}使用許諾契約書の条項に同意します(&A)"
                     Value="Yes" X="5" Y="0" Width="250" Height="15" />
        <RadioButton Text="{\DlgFont8}使用許諾契約書の条項に同意しません(&D)"
                     Value="No" X="5" Y="20" Width="250" Height="15" />
      </RadioButtonGroup>

しかしこれらのフォントの定義はどこにあるのか、と質問されますか? オーケー、次に示します。これは、さまざまなテキスト・スタイルを集中管理的に定義する方法で、ユーザー・インタフェイスのどこからでもこれらのテキスト・スタイルを簡単に参照できます。色については、Red, Green および Blue の属性を使うことが出来ます(それぞれ 0 から 255 の間の値を指定します)。追加の装飾として、Bold, Italic, Underline および Strike を指定する事が出来ます。

      <TextStyle Id="DlgFont8" FaceName="Tahoma" Size="8" />
      <TextStyle Id="DlgTitleFont" FaceName="Tahoma" Size="8" Bold="yes" />
      <TextStyle Id="VerdanaBold13" FaceName="Verdana" Size="13" Bold="yes" />

ソース・ファイルの最後には、プロパティの定義が来ます — 基本的には、私たちが使う変数とその初期値です。プロパティの中には、インタフェイス要素のテキストの略記として使われているものもあります。地域化するときは、これらも修正する必要があります。アンパサンドと角括弧の文字に注意して下さい。値全体をCDATA ラッパーで包むか、または、こういう扱いにくい文字に XML 実体参照を使うかしなければなりません(ButtonText_NextButtonText_Back を比較して下さい)。

    <Property Id="ALLUSERS">2</Property>
    <Property Id="ROOTDRIVE"><![CDATA[C:\]]></Property>
    <Property Id="Setup">セットアップ</Property>
    <Property Id="ButtonText_Next">次へ(&amp;N) &gt;</Property>
    <Property Id="ButtonText_Back"><![CDATA[< 戻る(&B)]]></Property>

インストーラ・パッケージには、製品と一緒にインストールしたくないけれど、ユーザー・インタフェイスには必要になるファイルが付いています。つまり、ビットマップとアイコンです。これらについて、ソース・ファイルの末尾で記述します。これまで、ビットマップとアイコンは Id 識別子を使って参照してきましたが、ここで実際のファイル名を定義します。これらのファイルは、独立したキャビネットを要求した場合でも、.cab ファイルではなく .msi ファイルに格納されます。

    <Binary Id="Up" SourceFile="Binary\Up.ico" />
    <Binary Id="New" SourceFile="Binary\New.ico" />
    <Binary Id="custicon" SourceFile="Binary\Custom.ico" />
    <Binary Id="repairic" SourceFile="Binary\Repair.ico" />
    <Binary Id="exclamic" SourceFile="Binary\Exclam.ico" />
    <Binary Id="removico" SourceFile="Binary\Remove.ico" />
    <Binary Id="completi" SourceFile="Binary\Complete.ico" />
    <Binary Id="insticon" SourceFile="Binary\Typical.ico" />
    <Binary Id="info" SourceFile="Binary\Info.ico" />
    <Binary Id="bannrbmp" SourceFile="Binary\Banner.bmp" />
    <Binary Id="dlgbmp" SourceFile="Binary\Dialog.bmp" />
    <Icon Id="Hoge10.exe" SourceFile="HogeAppl10.exe" />
  </Product>
</Wix>

ビットマップやアイコンを変更したいときは、Binary ディレクトリの中だけで変更して下さい。表紙のビットマップ(ここでは Dialog.bmp という名前です)は、503 × 314 ピクセルの BMP ファイルで、上部のバナー・ビットマップは 500 × 63 ピクセルです。しかし、ユーザーのシステム・フォントと表示解像度の設定によってインタフェイス全体の拡大縮小が要求される場合には、Windows Installer がこれらのビットマップを拡大または縮小する可能性があることに注意して下さい。拡大縮小に伴う醜くて不自然な結果を避けるために、適切なビットマップを使わなければなりません。均一に見えることを想定した画像を横切る細線のストライプ模様は避けて下さい。ビットマップにテキストを組み入れてはいけません(ロゴの文字は OK ですが、読んで貰うことを意図した小さなテキストは駄目です)。そして、何よりも、ディザ処理した領域とチェッカー模様の背景は禁物です。これらは拡大縮小されるとひどく不自然に見えます。画像エディタを使って、いろんな倍率で拡大縮小の実験をしてみれば、何を言っているのか分るでしょう。ただし、バイキュービック・サンプリングなどの洗練されたアルゴリズムは必ず無効にしてください。Windows は単純な拡大と縮小しか使用しません。

† 訳註:500 × 63 ピクセル、および、503 × 314 ピクセルというビットマップのサイズは、WiX 2 時代のライブラリが内蔵していたビットマップのサイズに依拠するものです。現在の WiX 3 のライブラリが内蔵しているビットマップのピクセル・サイズは、それぞれ、493 × 58 ピクセル、493 × 312 ピクセルです。

ただし、これらのビットマップのサイズは、本文の記述からも分るように、絶対にそうでなければならない、というものではありません。それぞれの時代において、その時の標準的な Windows のシステム・フォントと表示解像度のもとでは、そのようなピクセル・サイズのビットマップなら拡大縮小されることなく等倍で表示される、ということです。

8.8 順番外

ウィザード・ページの通常の進行には入らず、エラーなど、順番から外れた条件が発生したことを知らせるダイアログがいくつかあります。それらのダイアログは、OnExit 属性を使って Show タグでスケジュールすることが出来ます。OnExit 属性の値は、success, cancel, error または suspend です。

<InstallUISequence>
  <Show Dialog="FatalError" OnExit="error" />
  <Show Dialog="UserExit" OnExit="cancel" />
  <Show Dialog="ExitDialog" OnExit="success" />
  ...
</InstallUISequence>

8.9 アナタハ 英語ヲ 話シマスカ?

Localized screenshot

インストーラ・パッケージは、ダイアログのテキストや、情報メッセージ、エラー・メッセージなど、ユーザーに対して表示する大量のテキストを持っています。私たちはそれらを独立したフラグメントに移動しました。インストーラを地域化(ローカライズ)することは、このフラグメント・ファイルの並列コピーを作成して、他の言語に翻訳するだけでも可能です。しかし、WiX は地域化に対しては、もっと良い手法を提供しており、それに従えば、地域化すべき全ての文字列を体系的に取りまとめる事が出来て、文字列の置き換えも後のリンクの段階で行うことが出来ます。ソース中の全てのテキスト・データ(ファイル名も含む)に対して、プリプロセッサ・スタイルの参照を使うことが出来ます。

<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>

  <Product Name='ほげ 1.0' Id='YOURGUID-86C7-4D14-AEC0-86416A69ABDE'
           Language='!(loc.LANG)' Codepage='932' Version='1.0.0'
           Manufacturer='ぴよソフト'>

    <Package Id='*' Keywords='!(loc.Keywords)'
             Description='!(loc.Description)' Comments='!(loc.Comments)'
             InstallerVersion='100' Languages='1041' Compressed='yes'
             SummaryCodepage='932' />

これらの変数の実際の意味は、.wxl という拡張子を持った地域化ファイルの中にリスト・アップされます。

<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="ja-jp"
                 xmlns="http://schemas.microsoft.com/wix/2006/localization">
  <String Id="LANG" Overridable="yes">1041</String>
  <String Id="Keywords" Overridable="yes">インストーラ</String>
  <String Id="Description" Overridable="yes">ぴよソフト's ほげ 1.0 インストーラ</String>
  <String Id="Comments" Overridable="yes">ほげはぴよソフトの登録商標です。</String>
</WixLocalization>

特定の言語ファイルとともにパッケージをコンパイルするために必要なことは、言語ファイルの名前を WiX のリンカに渡してやることだけです。

candle.exe Sample.wxs
light.exe Sample.wixobj -loc Language.wxl

これで私たちのユーザー・インタフェイスのツアーは終りです。インストーラによって提供されているコントロールはほとんど調べました。追加の詳細な情報や使用出来る属性については、WiX および MSDN の文書を参照して下さい。ここで身に着けた知識があれば、ツールセットのインタフェイス・ライブラリ全体を分析することが出来ます(オープン・ソースであることを思い出して下さい)。ここで説明しなかったページも結構たくさんありますが、構造と相互作用はまったく同じです。

Copyright © 2004-2012, Gábor DEÁK JAHN, Tramontána
何よりもコメントと寄稿を歓迎します。
日本語翻訳 Copyright © 2010, 2013, Nobuo Kihara, softark
読みやすくて正確な翻訳を目指しましたが、解釈の誤りと技術的な間違いが含まれていないという保証は出来ません。間違いの指摘や修正案の提示を歓迎します。github のリポジトリ ( https://github.com/softark/wix-j ) をご利用下さい。