如何在 WiX 中安装时填充组合框?

发布于 2024-08-03 23:11:53 字数 3713 浏览 0 评论 0 原文

编辑:由于 Rob 的回答,我已经更新了下面的代码,现在它可以工作了。

我找到了几个页面,展示了如何执行此操作(http://www.cmcrossroads.com/content/view/13160/120/, http://www.mail-archive.com/[email protected]/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) 上面的代码在自定义操作运行期间失败,并显示“执行期间函数失败。数据库:表更新”失败的。” - 这是因为索引问题导致代码尝试将字符串写入 int 列。

2)如果我将行更改

lRecord.SetString(2, lWebsiteName);

lRecord.SetString(2, lEntry.Name);

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

如果我将组合框更改为具有硬编码值,则一切正常,即使我对 lWebsiteName 的等效项进行硬编码。

Edit: I've updated the code below so that it now works, thanks to Rob's answer.

I've found a couple of pages that show how to do this (http://www.cmcrossroads.com/content/view/13160/120/, http://www.mail-archive.com/[email protected]/msg05103.html) and looked through the source code for WAI (http://wai.codeplex.com/), but I can't seem to get it to work in my installer no matter what I try. If anyone can spot what I'm doing wrong I'd be very grateful. My WiX fragment for the dialogue looks like this:

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

My custom action code is:

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

There used to be two problems:

1) The code above failed during the running of the custom action with "Function failed during execution. Database: Table(s) Update failed." - This was because of the indexing problem causing the code to try and write a string to an int column.

2) If I change the line

lRecord.SetString(2, lWebsiteName);

to

lRecord.SetString(2, lEntry.Name);

then looking at trace the action appears to succeed but when the installer run the combobox has no entries to chose from.

If I change the combobox to have hardcoded values everything works fine, even if I hardcode the equivalent of lWebsiteName.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

绿光 2024-08-10 23:11:53

我不使用 DTF(对我​​来说都是自然的 C++ CustomActions),但 Record 是基于 1 的。您是否尝试过将所有 SetRecord() 调用移动一个索引?

另外,上面的 .wxs 代码似乎建议您使用“DUMMYPROPERTY”作为 ComboBox 的控件属性,而不是像 .cs 代码使用的“IIS_WEBSITENAME”。

I don't use DTF (all natural C++ CustomActions for me) but Record's are 1 based. Have you tried shifting all of your SetRecord() calls over by one index?

Also, the .wxs code above seems to suggest that you are using "DUMMYPROPERTY" as the control Property for the ComboBox not "IIS_WEBSITENAME" like the .cs code is using.

牵你手 2024-08-10 23:11:53

这个已经很旧了,但是我有类似的问题,想分享我发现的内容,也许这可以节省某人的时间。

要确保创建 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;
    }
}

注意:我从最后一个函数中删除了不相关的代码片段以使其可读。
PS 总是(我的意思是总是)使用 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;
}

This one is pretty old, however I had similar issue and would like to share what I've found, maybe this saves someone's time.

to Make sure ComboBox table is created use EnsureTable, ensure CA doesn't overwrite defined value:

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

I have a JavaScript function for filling ListItems: (yes, I know some of you don't like JS for custom actions, but it still is convenient enough)

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

I call it from my other function (it is called as custom action) like this:

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

NOTE: I removed non-relevant pieces of code from last function to make it readable.
P.S. always (I mean ALWAYS) use null, zero length and error checking, try/catch and ensure logging with something like this:

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文