Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PropertyRef to require ComboBox

In one of my dialogs, I have the following control:

<Control Id="EnvironmentComboBox" Type="ComboBox" Sorted="yes" ComboList="yes" Property="ENVIRONMENT" X="25" Y="110" Width="200" Height="15" />

I fill the ComboBox in elsewhere like so:

<UI>
  <ComboBox Property="ENVIRONMENT">
    <ListItem Text="Development" Value="Development" />
    <ListItem Text="SIT" Value="SIT" />
    <ListItem Text="UAT" Value="UAT" />
    <ListItem Text="Production" Value="Production" />
  </ComboBox>
</UI>

However, if I don't have the ComboBox bit created, the MSI will still build, and it will fail during install (2205). Thus, I would like to enforce the requirement to have a property named ENVIRONMENT. I've tried adding a PropertyRef like below to my dialog:

<PropertyRef Id="ENVIRONMENT" />

However, this doesn't seem to pick up the <ComboBox Proeprty="ENVIRONMENT">. It will pick up a regular property (<Property Id="ENVIRONMENT" Value="test" />), but that doesn't really help much.

Is there any way to require a ComboBox to be defined?

EDIT: For clarification, I intend to keep the ComboBox definition separate from the Control definition so that the dialog can be reused.

like image 416
zimdanen Avatar asked Nov 12 '12 14:11

zimdanen


2 Answers

MSI can exist without Combobox items declared for the property, referenced by a element (you have the ability to type any text you need in the combobox text field in case ComboList='no').

*The element doesn't have any corresponding object in the MSI tables* (only it's child elements gets written to the MSI's ComboBox table). So I suppose this element is completely optional from WIX point.

Your error #2205 is caused by non-existance of the 'ComboBox' table at all. I suppose you have only one combobox in the installer. Theoretically it is impossible possible to detect this at compile-time as error (tables can be created in custom actions as well). The most WIX team can do is generate warning.

To avoid this error I declared a dummy combobox element in my reusable project:

  <Fragment><!--This fragment is intended to fill MSI tables with dummy items so that these tables became created. Tables without items aren't created-->
    <UI Id="Dummy">
      <Dialog Id="DummyDlg" Width="370" Height="270" Title="Dummy" NoMinimize="yes">
        <Control Id="DummyDlgComboBox" Type="ComboBox" Property="DummyComboboxProperty" Width="200" Height="17" X="100" Y="80">
          <ComboBox Property="DummyComboboxProperty">
            <ListItem Text="Dummy" Value="Dummy" />
          </ComboBox>
        </Control>
      </Dialog>
    </UI>
  </Fragment>

and referenced this UI from my commonly-used UI sequences.

Now I don't need to worry about existance of the combobox table in any of my installers.

As for a workaround to your problem - how to enforce users not to forget to declare list items, I would do a tepmlate wix file instead of the wixlib. Something like this:

<Control Id="EnvironmentComboBox" Type="ComboBox" Sorted="yes" ComboList="yes" Property="ENVIRONMENT" X="25" Y="110" Width="200" Height="15">
  <ComboBox Property="ENVIRONMENT">
    <Placeholder Id="EnvironmentComboBoxItems" />
  </ComboBox>
</Combobox>

and give users the only ability to reuse this code using template transformation tool you provide. It will validate if all template placeholders are provided with content.

The transformation may look like this:

<TemplateSubstitutions>
  <PlaceholderContent PlaceholderId='EnvironmentComboBoxItems'>
    <ListItem Text="Development" Value="Development" />
    <ListItem Text="SIT" Value="SIT" />
    <ListItem Text="UAT" Value="UAT" />
    <ListItem Text="Production" Value="Production" />
  </PlaceholderContent>
</TemplateSubstitutions>

The tool for merging them:

static void Main(string[] args)
{
    var templatePath = args[0];
    var templateTransformPath = args[1];
    var resultPath = args[2];

    var templateDoc = XDocument.Load(templatePath);
    var transformationDoc = XDocument.Parse(templateTransformPath);

    Dictionary<string, XElement> contents = transformationDoc.Element("TemplateSubstitutions").Elements("PlaceholderContent").ToDictionary(e => e.Attribute("PlaceholderId").Value, e => e);

    var planceHolders = templateDoc.Descendants("Placeholder").ToArray();
    foreach (var ph in planceHolders)
    {
        ph.ReplaceWith(new XElement(contents[ph.Attribute("Id").Value]).Nodes());
    }
    templateDoc.Save(resultPath);
}

Of course this tool isn't release yet - you may want to add some meaningful error messages to your clients and validation of provided transformations. But to avoid code complication I didn't implement that.

I use this approach in few installer projects I have currently. All templates are stored in the common location and client installers can take any file they need and transform it.

My release tool is a bit more advanced of course. It can substitute values in the attributes using wildcards - I consider it extreemely useful when reusing components. I use then template like this:

<Component Guid="{StrToGuid({ProductName}_7A51C3FD-CBE9-4EB1-8739-A8F45D46DCF5)}">

and user should provide all template properties (such as 'ProductName') in command-line of my template transformation tool. It ensures components will have unique GUIDs between different products, so they will not conflict when installed on the same machine.

NOTE: Be careful with figure brackets though - they have special meaning for WIX and MSI: http://msdn.microsoft.com/library/aa368609.aspx. But as for me it is not clear enough and I don't use them regularly. But you might need another wildcard prefix.

As for organizing the installer build process, you can add template transformation calls on pre-build of the project. But I decided to use separate build scripts instead of native Visual studio build. It looks much more simple and flexible. And I don't get nasty errors like "command 'xxx' exited with code yyy" - I always see the template transformation log, error messages etc.

Hope my reinvention of the wheel will help anybody =).

like image 66
Sasha Avatar answered Sep 28 '22 05:09

Sasha


I suspect <PropertyRef> was designed the way to pick up only the "direct" definitions of properties, that is, <Property> elements. The <ComboBox> just mentions the property name in its attribute, and this is not treated as a property definition.

Add a "direct" property definition to your sample, and it should work:

<UI>
  <Property Name="ENVIRONMENT" Value="" />
  <ComboBox Property="ENVIRONMENT">
    <ListItem Text="Development" Value="Development" />
    <ListItem Text="SIT" Value="SIT" />
    <ListItem Text="UAT" Value="UAT" />
    <ListItem Text="Production" Value="Production" />
  </ComboBox>
</UI>

And reference it with <PropertyRef> element in another place - just the way you tried. As far as I know, such a definition won't harm the combobox part, and you'll be on the safe side with proper fragment inclusion.

Alternatively, you can reference the entire <UI> element with <UIRef> element - it should have the same effect.

like image 40
Yan Sklyarenko Avatar answered Sep 28 '22 03:09

Yan Sklyarenko