如何在 WiX 安装时填充 ComboBox?

5

编辑:我已更新下面的代码,感谢Rob的答案。

我找到了几个页面来展示如何实现这个功能 (http://www.cmcrossroads.com/content/view/13160/120/, http://www.mail-archive.com/wix-users@lists.sourceforge.net/msg05103.html) 并查看了WAI的源代码 (http://wai.codeplex.com/),但无论我尝试什么都不能在我的安装程序中使其正常工作。如果有人能发现我的错误,我将非常感激。我的WiX对话框片段如下所示:

<UI>
  <Dialog>

...snip...

    <Control Id="WebsiteName" Type="ComboBox" ComboList="yes" Sorted="yes" Property="IIS_WEBSITENAME" X="20" Y="73" Width="150" Height="17"/>

...snip...

    <!-- We want our custom action to fill in the WebsiteName ComboBox above
         however, if no ComboBox entries exist at compile time then the
         ComboBox table is not created in the MSI and we can't add to it in
         the custom action. So we have this hidden dummy list box to force
         the table to appear. -->
    <Control Id="DummyComboBox" Hidden="yes" Type="ComboBox" Sorted="yes" ComboList="yes" Property="DUMMYPROPERTY" X="65" Y="60" Width="150" Height="18">
      <ComboBox Property="DUMMYPROPERTY">
        <ListItem Text="Dummy" Value="Dummy"/>
      </ComboBox>
    </Control>
  </Dialog>
</UI>

<Property Id="DUMMYPROPERTY">Dummy</Property>
<Property Id="IIS_WEBSITENAME"/>
<CustomAction Id="FillWebsiteNameList" BinaryKey="WiXCustomAction.dll" DllEntry="FillWebsiteNameList" Execute="immediate" />
<InstallUISequence>
  <Custom Action="FillWebsiteNameList" After="CostFinalize"/>
</InstallUISequence>

我的自定义操作代码是:

[CustomAction]
public static ActionResult FillWebsiteNameList(Session xiSession)
{
  xiSession.Log("Begin FillWebsiteNameList");

  xiSession.Log("Opening view");

  View lView = xiSession.Database.OpenView("SELECT * FROM ComboBox");
  lView.Execute();

  xiSession.Log("Creating directory entry");

  DirectoryEntry lIis = new DirectoryEntry("IIS://localhost/w3svc");

  xiSession.Log("Checking each child entry");

  int lIndex = 1;
  foreach (DirectoryEntry lEntry in lIis.Children)
  {
    if (lEntry.SchemaClassName == "IIsWebServer")
    {
      xiSession.Log("Found web server entry: " + lEntry.Name);

      string lWebsiteName = (string)lEntry.Properties["ServerComment"].Value;
      xiSession.Log("Website name: " + lWebsiteName);

      xiSession.Log("Creating record");
      Record lRecord = xiSession.Database.CreateRecord(4);

      xiSession.Log("Setting record details");
      lRecord.SetString(1, "IIS_WEBSITENAME");
      lRecord.SetInteger(2, lIndex);
      lRecord.SetString(3, lEntry.Name); // Use lWebsiteName only if you want to look up the site by name.
      lRecord.SetString(4, lWebsiteName);

      xiSession.Log("Adding record");
      lView.Modify(ViewModifyMode.InsertTemporary, lRecord);

      ++lIndex;
    }
  }

  xiSession.Log("Closing view");

  lView.Close();

  xiSession.Log("Return success");

  return ActionResult.Success;
}

过去存在两个问题:

1)上述代码在运行自定义操作时失败,并显示“执行期间出现功能故障。数据库:表格更新失败。”——这是由于索引问题导致代码试图将字符串写入整数列。

2)如果我更改该行

lRecord.SetString(2, lWebsiteName);

为了

lRecord.SetString(2, lEntry.Name);

当查看跟踪时,操作似乎成功了,但是当安装程序运行时,组合框中没有可供选择的条目。

如果我将组合框更改为硬编码值,则一切正常工作,即使我硬编码等效于 lWebsiteName。


安装程序必须以管理员身份运行吗?还是我该如何避免在尝试获取网站列表时出现访问被拒绝的情况? - Joey V.
必须以管理员身份运行。享受解决这个问题的乐趣。 - Joel McBeth
2个回答

3

我不使用DTF(全部使用自然的C++ CustomActions),但记录是从1开始计数的。你尝试过将所有的SetRecord()调用向后移动一个索引吗?

此外,上面的.wxs代码似乎表明您正在使用“DUMMYPROPERTY”作为ComboBox的控制属性,而不是像.cs代码使用的“IIS_WEBSITENAME”。


谢谢,我曾经尝试过基于 1 的代码,但是我尝试了太多东西,可能没有在这个代码中这么做!我会再试一次。 我在第二个隐藏的组合框控件中使用 DUMMYPROPERTY,以确保 ComboBox 表格被创建,我会尝试将它指向与真实控件相同的属性,看看是否有效。 - Dan
是索引导致了这两个问题。再次感谢。 - Dan

0

这个问题比较老了,但我遇到了类似的问题,想分享一下我找到的解决方法,或许可以帮助节省其他人的时间。

为了确保 ComboBox 表已创建,请使用 EnsureTable,并确保 CA 不会覆盖定义的值:

<EnsureTable Id="ComboBox"/>
<Property Id="RS_INSTANCES" Secure="yes"/>
<CustomAction Id="GetRSintances" BinaryKey="JSCommon" Return="ignore"
              JScriptCall="GetRSintances" Execute="immediate" />

<InstallUISequence>
  <Custom Action="GetRSintances" After="AppSearch">
    <![CDATA[NOT Installed AND NOT RS_INSTANCES]]>
  </Custom>
</InstallUISequence>

<InstallExecuteSequence>
  <Custom Action="GetRSintances" After="AppSearch">
    <![CDATA[NOT Installed AND NOT RS_INSTANCES]]>
  </Custom>
</InstallExecuteSequence>

 <!-- UI part -->
 <Control Id="ComboBox1" Type="ComboBox" X="20" Y="160" Width="100" Height="20" Property="RS_INSTANCES" Sorted="yes" >
    <ComboBox Property="RS_INSTANCES">
      <!-- dynamicly filled during installation -->
    </ComboBox>
  </Control>

我有一个用于填充ListItems的JavaScript函数:(是的,我知道有些人不喜欢在自定义操作中使用JS,但它仍然足够方便)

// Add ListItem to ComboBox or ListView at install time
function AddListItemToMSI(Property, Order, Value, Text, Table) {
  try {
    var controlView = Session.Database.OpenView("SELECT * FROM " + Table);
    controlView.Execute();

    var record = Session.Installer.CreateRecord(4);
    record.StringData(1) = Property;
    record.IntegerData(2) = Order;
    record.StringData(3) = Value;
    record.StringData(4) = Text;

    controlView.Modify(7, record);
    controlView.Close();
  }
  catch (err) {
    ShowMessage('Couldn\'t add ListItem entry, error occured: ' + err.message, msiMessageTypeInfo);
  }

  return 1;
}

我从我的其他函数中调用它(它被称为自定义操作),像这样:

var ComboBoxProperty = 'RS_INSTANCES';
var InstanceFullName;
for (i = 0; i < Names.length; i++) {
    InstanceFullName = GetInstanceName(Names[i]); //this function looks up full name in the registry
    AddListItemToMSI(ComboBoxProperty, i, InstanceFullName, '', 'ComboBox');
    if (i == 0) {
      Session.Property(ComboBoxProperty) = InstanceFullName;
    }
}

注意:我从上一个函数中删除了不相关的代码片段,以使其更易读。 另外,一定要(我是说一定要)使用 null、零长度和错误检查、try/catch,并确保记录类似以下内容的日志:

function ShowMessage(text, options) {
    if (options == null) {
        var options = msiMessageTypeUser;
    }
    var oRecord = Session.Installer.CreateRecord(1);
    oRecord.StringData(1) = text;
    var response = Session.Message(options, oRecord);
    oRecord.ClearData();
    oRecord = null;
    response = null;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接