ListCell<Object> 中的 JavaFX 自定义节点未正确加载

JavaFX custom Nodes in ListCell<Object> not loading correctly

好的,涉及到所有不同的节点,这会变得有点复杂。如果可能的话,请随时提供有关简化它的建议。

我正在尝试使用自定义对象填充 JavaFX ListView,并使用自定义节点进行格式化。

我有一个名为 Contact 的自定义对象,这是它的定义:

public class Contact {
    private int id;
    private int clientID;
    private String firstName;
    private String middleInitial;
    private String lastName;
    private Date birthdate;
    private String ssn;
    private Address address;
    private List<Phone> phones;
    private List<Email> emails;
    private int listPosition;
    private ContactTitle title;
}

Here's an example of what I'd like each contact in the ListView to show as.

这个自定义节点 (ContactCell) 包含一个 VBox 根,顶部有一个用于联系人姓名的标签,然后是一个用于堆叠几部电话的 VBox 和一个用于堆叠几封电子邮件的 VBox。电话和电子邮件通过 ContactCellField 节点放入 VBox(我将在下面详细解释这些。)

这是 ContactCell 的 fxml:

<fx:root type="VBox" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Separator prefWidth="200.0" />
      <Label fx:id="lbl_printName" underline="true">
         <VBox.margin>
            <Insets left="10.0" />
         </VBox.margin>
      </Label>
      <VBox fx:id="vbox_phones" />
      <VBox fx:id="vbox_emails" />
   </children>
</fx:root>

因此 vbox_phones 应该以编程方式填充 0-2 个 ContactCellField 节点。 Here's what an individual ContactCellField should look like.

这是 ContactCellField 的 fxml:

<fx:root type="Pane" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <VBox>
         <children>
            <Label fx:id="lbl_fieldLabel" text="Mobile" textFill="#616161" underline="true">
               <font>
                  <Font size="8.0" />
               </font>
               <padding>
                  <Insets left="3.0" />
               </padding>
            </Label>
            <DetailField fx:id="df_fieldContent" text="(817)-555-5555" />
         </children>
      </VBox>
   </children>
</fx:root>

如您所见,ContactCellField 包含一个 DetailField,它是我制作的另一个自定义节点,我认为该节点与此问题完全无关,因此我不会深入探讨,但基本上它是带有复制按钮的 TextField。

这就是所有的 FXML,现在是一些 java。

下面是 ContactListCell 的代码,它是 ListCell 的扩展,还包含 ContactCell 节点定义:

public class ContactListCell extends ListCell<Contact> {
    private ContactCell contactCell;

    public ContactListCell() {
        contactCell = new ContactCell();
    }

    @Override
    protected void updateItem(Contact contact, boolean empty) {
        super.updateItem(contact, empty);
        if (contact != null) {
            contactCell.updateContact(contact);
            getChildren().setAll(contactCell);
        }
    }


    /**
     * the ContactListCell basically just takes the contact it gets
     * and visualizes it using the ContactCell node
     */
    private class ContactCell extends VBox {
        @FXML Label lbl_printName;
        @FXML VBox vbox_phones;
        @FXML VBox vbox_emails;

        public ContactCell() {
            super();
            FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("contactcell.fxml"));
            fxmlLoader.setRoot(this);
            fxmlLoader.setController(this);
            fxmlLoader.setClassLoader(getClass().getClassLoader());

            try {
                fxmlLoader.load();
            } catch (IOException exception) {
                throw new RuntimeException(exception);
            }
        }

        public void updateContact(Contact contact) {
            String printName = String.join(" ", contact.getFirstName(), contact.getLastName());
            lbl_printName.setText(printName);

            // Takes a list of phone objects and turns them into a list of ContactCellField objects
            // Then fills the phones list box with those objects
            List<ContactCellField> phones = contact.getPhones().stream().map(p -> new ContactCellField(p.getName(), p.getNumber())).collect(Collectors.toList());
            vbox_phones.getChildren().setAll(phones);
            // Same thing that phones do but for emails
            List<ContactCellField> emails = contact.getEmails().stream().map(e -> new ContactCellField(e.getName(), e.getAddress())).collect(Collectors.toList());
            vbox_emails.getChildren().setAll(emails);
        }
    }
}

我将省略 ContactCellField 的 java 除非有要求,因为我认为这也不是问题所在。

因此,在 ListView 所在场景的控制器的其他地方,这就是我填充 LV 和设置单元工厂的方式:

public void initContactsLV(List<Contact> contacts) {
        // sorts the contacts by their list position and then passes that ordered list through to the list view
        contacts.sort(Comparator.comparing(Contact::getListPosition));
        ObservableList<Contact> contactList = FXCollections.observableArrayList(contacts);
        lv_contacts.setPlaceholder(new Label("No contacts found."));
        lv_contacts.setCellFactory(lv -> new ContactListCell());
        lv_contacts.setItems(contactList);
    }

所以细胞工厂的目标是将 LV 的格式设置为 ContactListCells,我希望它包含 ContactCell 的格式。然后联系人单元格应该从联系人对象接收电话和电子邮件,将它们的信息放入 ContactCellFields,然后将这些 ContactCellFields(堆叠)放入适当的 VBox (vbox_phones/vbox_emails)。

经过大量的反复试验和模糊的 Whosebug 帖子,我几乎达到了预期的效果。此时代码没有错误,并且确实使用传递给它的正确数量的 Contact 对象填充 LV,但它的可视化非常错误。

Here's the behavior when I run the program. It doesn't show anything until I click on the cell, and then it visualizes completely wrong. 联系人姓名的标签仅显示带下划线的省略号,电话的 VBox 和电子邮件的 VBox 似乎堆叠在一起,导致一堆 ContactCellFields 堆叠在每个其他。

哦,我差点忘了说,当我第一次 运行 时,它只显示带下划线的省略号,然后我 运行domly 转到 ListView 的 fxml 定义并将 fixedCellSize 更改为 100.0 ,这使单元格更高并显示堆叠的 vboxes。

我是否遗漏了一些阻止 ListView 正确显示这些节点的简单内容?我是否做了一些非常愚蠢的事情并且需要重新格式化这一切的工作方式?任何帮助表示赞赏。

EDIT/UPDATE:我将 ContactListCell 中的行从 getting/setting the children 更改为 ContactCell 到 setGraphic(contactCell) 现在 it loads like this. 这更接近最终目标但不是相当。我将继续尝试,但仍然乐于接受建议,让我越过终点线或让事情 better/easier 在长期 运行.

中发展

我在 OP 中更新:

EDIT/UPDATE: I changed the line in ContactListCell from getting/setting the children to the ContactCell to setGraphic(contactCell) and now it loads like this. This is MUCH closer to the end goal but not quite. I'm going to keep trying things but still open to advice to get me over the finish line or to make things better/easier to develop in the long run.

修复方法如下:

@Override
protected void updateItem(Contact contact, boolean empty) {
    super.updateItem(contact, empty);
    if (contact != null) {
        contactCell.updateContact(contact);
        setGraphic(contactCell);
    } else {
        setGraphic(null);
    }
}

+ 完全从 ListView 中删除固定单元格大小首选项导致工作 ListView of custom nodes. 有点烦人,我花时间写了整个问题描述只是为了自己弄清楚,但是嘿,那是有时情况如何。希望其他人在尝试像我在这里所做的那样在 ListView 中创建更复杂的嵌套节点时可以从中学习。