WiX tutorial

Lesson 8 User Interface Revisited

As we have already discussed in Lesson 2, WiX comes with its own standard user interface library. Although this library caters for most standard needs, a time might come when you might need to craft your own user interface. But even if you only want to customize and modify the standard one, you need to learn how WiX does it in the background. To accomplish this, we start again from scratch.

8.1 A Single Dialog

InstallDlg screenshot

We start with a single, simple interface—nothing but a title and a single button to start the installation with.

Dialogs must always have a unique identifier. The dialog size is standard because the wizard we use has a standard size. The Title is straightforward but note that here too, you can reference properties using the square bracket notation. Quite handy as you don't have to re-edit the whole UI part when you create an installer package for a new product. ProductName is automatically defined to denote the product name you have defined in the Product tag at the very beginning of the source file.

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

Everything we add to the dialog will be a control. A Type attribute describes the kind of control (Billboard, Bitmap, CheckBox, ComboBox, DirectoryCombo, DirectoryList, Edit, GroupBox, Icon, Line, ListBox, ListView, MaskedEdit, PathEdit, ProgressBar, PushButton, RadioButtonGroup, ScrollableText, SelectionTree, Text, VolumeCostList or VolumeSelectCombo). For simple text (called static text in usual Windows parlance because it doesn't do anything, you can't click on it, it is just there), we use the type Text. We specify position and dimensions.

Title text elements are marked Transparent, for the benefit of the general case. These texts overlay the banner bitmap at the top. As they are written in black over a white background now, transparency doesn't do any difference but if you would provide a full banner picture extending below the title text and colored text, it would make for nice visual effects. NoPrefix only controls whether ampersand characters are displayed verbatim or used as shortcut specifiers, as usual in the Windows GUI.

  <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
    <Text>{\DlgTitleFont}Ready to Install</Text>
  </Control>

You have two ways of specifying the text of the control: either the Text child tag inside the control or the Text attribute:

  <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes"
    Text="{\DlgTitleFont}Ready to Install"/>

We can refer to font styles using TextStyle tags. We also need to include a standard property, DefaultUIFont and associate a font with it because the installer needs it to determine the default font.

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

Our only active control will be a push button (of type PushButton). Again, we specify position and dimensions. As the only one, it will be our default button too (sensitive to pressing the Enter key). Active user interface elements that do something when the user activates them will also have a Publish tag nested inside to define what to do when the activation happens. There is a long list of possible standard events to select from, EndDialog is one of them). So, the action will now be EndDialog with a value of Return, meaning to dismiss the dialog the normal way, without any error to be signaled:

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

So, we already have our dialog but we have to schedule it into the normal course of events. Go to the part just before the closing UI tag and make the following changes: clear AdminUISequence as we are not worrying about administrative install now.

InstallUISequence has already been mentioned but now it's time to discuss its interaction with InstallExecuteSequence.

The course of standard actions is broken into two at the middle, the InstallValidate action, which is responsible for the verification of available disk space and also for notifying the user if one or more files to be overwritten by the installation are currently in use. Actions up to InstallValidate (including various dialog boxes) are listed in InstallUISequence while those following this action can be found in InstallExecuteSentence. There are exceptions, though, to this rule. InstallUISequence is not consulted during an installation with basic UI or no UI at all, so InstallExecuteSentence has to be standalone even in this case. To this end, it duplicates some actions from the other table as well. If we check out our current sample with Orca, InstallUISequence will contain:

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

These are actions we already know, except for the last one. During installation, both tables will be consulted initially and the actions executed in order of their sequence number (as the duplicated actions have the same number in both tables, this won't create any ambiguity). ExecuteAction is scheduled to the point where all information collection necessary to begin the actual installation has already been completed. Execution then passes to InstallExecuteSentence to perform the actions responsible for the actual process of installation.

Consequently, we have to schedule our single dialog box after the last action in the preparation process, CostFinalize but still before ExecuteAction:

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

Build this sample (all our custom UI samples are combined into a single download package) and run it. You will be presented a dialog and when you press the Install button, our usual three files will be installed. Note that there will be no progress dialog, just a silent install. Also, you can't cancel the installation, if you start it, you have to finish it.

Note that the built-in validator of WiX will emit ICE 20 warnings about some missing features of the standard user interface. Because the starting versions of our custom UI lesson are far from being a full standard interface, these warnings can be suppressed using a command line switch:

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

8.2 Tuning Up

In our SampleCustomUI2, we stay with the same single dialog but tune it up a little bit.

InstallDlg screenshot

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

This is only a small change: instead of specifying the button text directly, we use a property. This will make localization easier at a later stage:

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

A simple banner bitmap at the top of the dialog. Note that we use a property to refer to the binary attachment. Although it is specified in the Text attribute, this is not a text but the Id of the bitmap stored in the package.

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

Two text lines, one transparent over the banner bitmap and another one in the actual dialog work area:

  <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15"
    Transparent="yes" NoPrefix="yes">
    <Text>The [Wizard] is ready to begin the installation</Text>
  </Control>

  <Control Id="Text" Type="Text" X="25" Y="70" Width="320" Height="20">
    <Text>Click Install to begin the installation.</Text>
  </Control>

A horizontal line to mark the bottom of the dialog area—aesthetics, nothing else:

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

Finally, the title and the embossed line just below the banner bitmap:

  <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15"
    Transparent="yes" NoPrefix="yes">
    <Text>{\DlgTitleFont}Ready to Install</Text>
  </Control>

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

Lets not forget that we referenced a banner bitmap so we have to include it in the package:

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

8.3 Interactions

Building dialogs is rather simple (although there are many more control types we can stuff our pages with), it's making them interact that's really interesting. In SampleCustomUI3, we add a second dialog, allowing the user to cancel the installation process:

CancelDlg screenshot

To achieve this, we need the dialog itself. It will have two push buttons but note the difference in terminology. A message box like this can have two outcomes, cancelling it and okaying it: going on with what it says. The question will be worded differently here: when we cancel the cancellation dialog, we vote for going on with the installation. Hence, we call or first, default button, with the text No our cancel button:

<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>

If the user clicks on this button, we signal the EndDialog event with a value of Return. As its name implies, this simply dismisses the dialog and resumes the original operation. The Publish tag has a condition, a number one that always evaluates to true (a zero would evaluate to false), thus, the event will be signaled unconditionally.

Our second button, with the text Yes will trigger the same EndDialog event but with a different value, Exit. This is used to abort the whole installation process:

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

The rest is now simple, text and icon. Don't forget to add the Binary tag to include the icon in the package:

  <Control Id="Text" Type="Text" X="48" Y="15" Width="194" Height="30">
    <Text>Are you sure you want to cancel [ProductName] installation?</Text>
  </Control>

  <Control Id="Icon" Type="Icon" X="15" Y="15" Width="24" Height="24"
    ToolTip="Information icon" FixedSize="yes" IconSize="32" Text="[InfoIcon]" />
</Dialog>

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

We also need some modification in our InstallDlg dialog. We moved our Install button to the left to make room for the Cancel button. This one will fire a SpawnDialog event, specifying the name of the dialog to be launched. As already mentioned in the previous lesson, SpawnDialog starts a new child dialog without removing the current one and proceeds when the user dismisses this second dialog:

  <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 Customizations Galore

Now we'll do something much more ambitious. We will allow the user to customize the setup, to decide which features to install and to tell the installer where to install the files to.

CustomizeDlg screenshot

First of all, to reduce the complexity of our samples, we send all textual items into a separate fragment file UI_Texts.wxs. The actual texts come from the WiX source and list all possible entries eventually needed by our UI experiments. We will simply call it with a reference and use the following build process:

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

We start a new dialog that will offer the necessary controls—first of all, a tree view of the features. It will be quite simple because the controls of the SelectionTree type are not simple treeview controls but they are linked by the installer to the available features and their selection status:

<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="Tree of selections" />

The dialog will have several push buttons. Browse will be enabled by the installer automatically if we used the ConfigurableDirectory attribute in the Feature tag. There are several control events to be used in relation to selection trees, SelectionBrowse will launch the specified browse dialog so that the user can modify the path to install to. We'll return to this browse dialog in a short while:

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

The Reset uses a prefabricated event, called Reset to return all controls in the dialog to the status they were in when the dialog was created. This undoes all feature customizations made by the user.

This button not only sends event messages but makes itself a recipient for similar messages by subscribing to an event. SelectionNoItems will disable buttons that subscribe to it if the selection tree has no nodes:

  <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>

There will be other buttons and simple controls in the dialog that are already familiar to us. We don't detail them any more here, check out the source file for reference. What we check in detail is the box at the right of the dialog. A Text control will be used to display information about the item the user has currently selected in the selection tree. Although the control has an initial text ("Multiline description of the currently selected item"), this will not appear but will be replaced by the actual selection information. This happens because the control subscribes to the SelectionDescription event. As soon as there is a change in the selection, the installer will report the new description to any control subscribing to this event:

  <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>Multiline description of the currently selected item.</Text>
    <Subscribe Event="SelectionDescription" Attribute="Text" />
  </Control>

The same happens to the other controls. ItemSize subscribes to receive the size of the current selection, Location to learn the path selected by the user. The last two also check whether there is a path to be set. If there is none, both the label and the path display will be supressed. This is what makes the path selection disappear when you travel the selection tree and go to the subnodes instead of the main one:

  <Control Id="ItemSize" Type="Text" X="215" Y="130" Width="131" Height="45">
    <Text>The size of the currently selected item.</Text>
    <Subscribe Event="SelectionSize" Attribute="Text" />
  </Control>

  <Control Id="Location" Type="Text" X="75" Y="200" Width="215" Height="20">
    <Text><The selections path></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="Location:">
    <Subscribe Event="SelectionPathOn" Attribute="Visible" />
  </Control>
</Dialog>

We told the installer to call our BrowseDlg dialog when the user clicks on the Browse button, so we have to provide this dialog as well:

BrowseDlg screenshot

If you go back and check out the previous Dialog tag, you'll see a Property reference. We specify it here, in our path edit control again—this is what creates the link between the dialog asking for the path and this one providing it. Setting Indirect tells the installer to modify not the property directly mentioned (_BrowseProperty) but the property that this property holds the name of.

<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" />

When the user is satisfied with the newly selected path and hits OK, the property value will be set by the SetTargetPath event. The installer also checks whether the selected path is valid. If the user decides not to set the path after all, we use the Reset event to return everything to the initial settings and will the dialog without actually setting the path:

  <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>

A DirectoryCombo control displays the path stored in the referenced property in a hierarchical, tree-like view.

  <Control Id="ComboLabel" Type="Text" X="25" Y="58" Width="44" Height="10"
    TabSkip="no" Text="&Look in:" />

  <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>

Two buttons with icons will be associated with the directory selection controls and will send the appropriate events when pressed:

  <Control Id="Up" Type="PushButton" X="298" Y="55" Width="19" Height="19"
    ToolTip="Up One Level" 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="Create A New Folder" Icon="yes" FixedSize="yes" IconSize="16" Text="New">
    <Publish Event="DirectoryListNew" Value="0">1</Publish>
  </Control>

And finally, a large DirectoryList in the middle, linked to the other directory control elements by the fact that it references the same property. All these controls will interact automatically as expected:

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

The rest of the dialog is of no particular interest any more, check out the source if you feel the need.

There are only a couple of minor things left to do before we can build our SampleCustomUI4. We modify our InstallDlg dialog by adding a Back button so that the user can go back to the customization dialog:

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

We have to modify the scheduling of our first dialog to make it appear in a phase where the customization should take place. Although there are alternatives to do that, we now schedule to the MigrateFeatureStates event. This event only happens during upgrading and installation, not during product removal or maintenance, right when it reads the feature selection of the previously installed product (if such a product exists). So, it will be the perfect place to show our customization dialog with the appropriate feature selection even in installation packages much more complicated than our current sample:

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

And finally, we don't forget to include the two new icons we needed in the browse dialog:

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

Customizing Customizations

If we need to select the features to be installed automatically or based on other conditions, or maybe we want to create a completely new interface for the user to select them (for instance, checkboxes instead of SelectionTree), we can link the following events to the appropriate control (the Next button, for instance) to enable or disable the installation of a given feature:

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

8.5 Is This Progress?

Something still missing from our growing user interface is a page showing how the installation process progresses.

ProgressDlg screenshot

We define this dialog to be modeless because we need the control to return to the installer. This will in turn send the dialog progress messages to be processed.

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

The Back and Next buttons will be disabled by default—there's no reason to keep them active, the user can't use them, anyway. If necessary, Cancel can be used to abort the installation:

  <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]" />

The text control just above the progress bar subscribes to the ActionText event, thus the installer keeps publishing the name of the current installation action to it:

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

If you are not satisfied with a description of the major steps but would like to see the details of the individual files as they are deployed, you can also use the ActionData event instead:

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

In addition to some uninteresting controls, we finally have the workhorse of this dialog, a control of ProgressBar type. Just like with SelectionTree, the control provided by the installer is not just a generic progress bar but one linked directly to the installation process. By subscribing to the SetProgress event with an attribute Progress the installer will keep sending progresss messages for display. ProgressBlocks = yes calls for the newer type progress bar with blocks. Setting it to no will revert to the old-style continuous bar from the Windows 95 era:

  <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>

To integrate our progress page with the rest of the installation package, we only have to modify our InstallDlg dialog to call this dialog. The rest will be done automagically:

<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 Well Done

There is only one thing missing, a way to launch the application after the installation. This will also show us how to use a checkbox and make decisions depending on its state.

ExitDlg screenshot

The newly added dialog looks like this:

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

The Finish button will have two tasks to carry out: first, to dismiss the dialog itself (and with this, to end the installation package itself) and second, to launch the application if the user chose to do so:

  <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>
  ...

The checkbox control in the dialog box has both its initial setting (CheckBoxValue) and an associated property (LAUNCHPRODUCT) that will be used to read its state:

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

The action published by the Finish button is an already familiar custom action. Don't forget the Return attribute to make sure the installer can close while the application stays running:

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

The Exit dialog will be scheduled upon successful completion (see the next section for details):

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

Chechboxes and properties are linked but only when events occur, when the user sets or clears them. Initially, without user interaction, nothing makes the property receive the default value we've set in the Control tag. We have to make sure we initialize the property ourselves:

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

Before you build the SampleCustomUI6, make sure you replace the dummy .exe file with something that will actually run.

And a common complaint: no, the checkbox can't have a transparent background. If you have a bitmap in the background, it will be ugly, just like in our example above. The only workaround is to reduce the width of the checkbox to the actual box itself and to place an additional static text (these can be made transparent) adjacent to it.

8.7 Legalese

There are a few other niceties left, like a license agreement page:

LicenseAgreementDlg screenshot

    <UI>
      ...
      <Dialog Id="LicenseAgreementDlg" Width="370" Height="270"
        Title="[ProductName] License Agreement" NoMinimize="yes">

For a radio button group, we'll have a separate description later in the source, the link is established by the Property attribute:

        <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]">

The selection of the next dialog is a bit trickier now. In some installations, we don't want to ask for the user name, company name and registration key. It would also be possible to modify the UI accordingly (simply not to call that page in the sequence of pages) but this solution is much more elegant. Somewhere among the properties we have one called ShowUserRegistrationDlg. If we set it to one, the User Registration page will be shown. If set to zero, this page will be skipped. This means two NewDialog events, one for each possible case:

          <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 and SpawnWaitDialog events don't replace the previous page but start a new, child dialog box. The first will wait for user interaction to be dismissed but the second will only be visible until the conditional expression remains false. In our case, it is a wait dialog only visible while the installer is calculating the disk requirement. For a small installation package like our sample, this takes no time at all, so you aren't likely to have the chance of seeing this dialog in action. However, let's have it there just in case. CostingComplete is a predefined property that is set to 1 when the disk requirement calculations are finished.

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

And, finally, the well known nuisance: the Next button will stay disabled until the user has signaled their acceptance of the license agreement. We already used Condition tags at the upper level (launch conditions to determine whether the whole installation process should run) or inside Feature tags (to conditionally disable the installation of various features). This is their third use, inside Control tags. Using their Action attribute, they can disable, enable, hide or show the control, or to revert it to is default state), if the condition inside the tag evaluates to true:

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

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

One small item the attentive reader might have noted: for some conditions, we used an ugly <![CDATA[...]]> wrapper, to make sure the parser in the compiler doesn't get confused by special characters like < or > appearing between the XML tags. The safest approach would be to wrap everything (this is what the WiX decompiler, Dark does) but—at least in my humble opinion—this makes the source far too illegible. Or, you have to take care of which expressions are misleading and which are not (the compiler will give an error message if it doesn't understand something) and only use the wrapper where really necessary. It's your choice...

The following parts are already familiar:

        <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]" />

The text of the license agreement comes next. We open up a sunken text with scrollable contents. The actual text comes into the inner Text tag. You can use RTF-style text here, so the best idea is to author your license agreement in a word processor and export it to RTF (Wordpad is probably the best word processor for this purpose, more sophisicated ones might create much more verbose RTF files; even if you use those, consider resaving the final version from Wordpad):

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

You could also specify the agreement text right here in the source file but the previous solution seems much easier to maintain:

          <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
            }

And the remaining part is really simple and deserves no detailed description. A few title lines here, a horizontal line there make up the rest of the dialog.

        <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15"
          Transparent="yes" NoPrefix="yes">
          <Text>Please read the following license agreement carefully</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}End-User License Agreement</Text>
        </Control>
        <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="374" Height="0" />
      </Dialog>

We still have a little debt to take care of. In the first control of the dialog, we referred to a radio button group description and left it pending. So, here it is. Note the property replacement in the Text attribute, this is how you can define font, size and color for your texts:

      <RadioButtonGroup Property="IAgree">
        <RadioButton Text="{\DlgFont8}I &accept the terms in the License Agreement"
          Value="Yes" X="5" Y="0" Width="250" Height="15" />
        <RadioButton Text="{\DlgFont8}I &do not accept the terms in the License Agreement"
          Value="No" X="5" Y="20" Width="250" Height="15" />
      </RadioButtonGroup>

But where these font specifiers come from, you might ask? OK, here they are. A nice, centralized way to specify various text styles and simply refer to them from everywhere throughout the user interface. For color, your can use the Red, Green and Blue attributes (a value between 0 and 255 for each), for additional decoration, Bold, Italic, Underline and 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" />

We end our source file with the properties—basically, our variables with their initialization values. Some are used as shorthand notation for user interface element texts, you need to localize them as well. Take care of the ampersand and angle bracket characters: you either wrap the whole value into a CDATA wrapper or you have to use XML entities for these tricky characters (compare ButtonText_Next and ButtonText_Back):

    <Property Id="ALLUSERS">2</Property>
    <Property Id="ROOTDRIVE"><![CDATA[C:\]]></Property>
    <Property Id="Setup">Setup</Property>
    <Property Id="ButtonText_Next">&amp;Next &gt;</Property>
    <Property Id="ButtonText_Back"><![CDATA[< &Back]]></Property>

We had files coming with us in the installation package we don't want to install with our product but are needed for the user interface: bitmaps and icons. We mention them at the end of the source file. We've referred to them eariler using their Id identifier but now we specify the actual filenames. These files will be placed into the .msi rather than the .cab file, even if we ask for a separate cabinet:

    <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="Foobar10.exe" SourceFile="FoobarAppl10.exe" />
  </Product>
</Wix>

If you want to change the bitmaps or icons, just do so in the Binary directory. The front page bitmap (named Dialog.bmp here) is a 503 by 314 pixel BMP while the top banner bitmap has 500 by 63 pixels. But note that Windows Installer might stretch or shrink these bitmaps if the system font and display resolution settings of the user ask for a scaling of the whole interface. To avoid ugly scaling artefacts, make sure you use appropriate bitmaps. Avoid thin lines supposed to look uniform across the picture, never incorporate text into the bitmaps (letters in logos are OK but small text meant to be read is not). And, above all, avoid dithered areas and checkered backgrounds, those might look very awkward when scaled. To get an idea, experiment with various scaling factors in your picture editor, making sure you turn off sophisticated algorithms like bicubic sampling; Windows will only use plain stretching and shrinking.

8.8 Out of Order

There are a few dialog boxes that don't fit into the normal sequence of wizard pages but represent errors or similar out-of-sequence conditions. We can specify them using the OnExit attribute in the Show tag used for scheduling. The value can be success, cancel, error or suspend:

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

8.9 Do You Speak English?

Localized screenshot

Installation packages have a large amount of text visible to the user: dialog text, informative or error messages. We moved them out into a separate fragment. To localize the installer, we could simply create parallel copies of this file translated to other languages. However, WiX offers an even better approach to localization that allows the systematic collection of all localizable strings and their replacement as late as during the linking phase. For each textual data in your source (including filenames), you can use preprocessor style references:

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

  <Product Name='Foobar 1.0' Id='YOURGUID-86C7-4D14-AEC0-86416A69ABDE'
    Language='!(loc.LANG)' Codepage='1252' Version='1.0.0' Manufacturer='Acme Ltd.'>

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

The actual meaning of all these variables will be listed in a localization file with an extension of .wxl:

<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization">
  <String Id="LANG" Overridable="yes">1033</String>
  <String Id="Keywords" Overridable="yes">Installer</String>
  <String Id="Description" Overridable="yes">Acme's Foobar 1.0 Installer</String>
  <String Id="Comments" Overridable="yes">Foobar is a registered trademark of Acme Ltd.</String>
</WixLocalization>

To compile the package with a specific language file, you simply pass its name to the WiX linker:

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

This concludes our user interface tour. We examined most of the controls provided by the installer. For additional details and possible attributes of the controls, consult the WiX and MSDN documentations. Armed with this knowledge, you can analyze the full interface library of the toolset (remember, it is open source). Although it has quite a few additional pages, their structure and interaction is just the same.

Copyright © 2004-2010, Gábor DEÁK JAHN, Tramontána
Comments and contributions are most welcome                   Comments and contributions are most welcome
A note: some people use a very braindead spam filtering idea actually generating extra amounts of spam: asking the sender to reply to an acknowledgment message in order to be placed on a whitelist and allowed through. All those who do so, unfortunately, will never receive a reply from me: I refuse this idea both in theory and practice.