cback
cback

Reputation: 67

JavaFX custom Nodes in ListCell<Object> not loading correctly

Ok this gets a little complicated with all the various nodes involved. Feel free to give advice on simplifying it if possible.

I'm trying to populate a JavaFX ListView with custom objects, formatted with custom nodes.

I have a custom object called Contact, here's the definition for it:

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.

This custom node (ContactCell) consists of a VBox root with a Label at the top for the name of the Contact, and then a VBox for stacking a couple of phones and a VBox for stacking a couple of emails. The phones and emails get put into the VBoxes via ContactCellField nodes (I'll explain those a little more below.)

Here's the fxml for the ContactCell:

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

So vbox_phones is supposed to programmatically get populated with 0-2 ContactCellField nodes. Here's what an individual ContactCellField should look like.

Here's the fxml for the ContactCellField:

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

As you can see the ContactCellField contains a DetailField which is another custom node that I've made, I don't think that node is related to this problem at all so I won't go into that but basically it's a TextField with a copy button.

So that's all the FXML, now for some java.

Here's the code for the ContactListCell which is an extension of ListCell and also contains the ContactCell node definition:

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

I'll leave out the ContactCellField's java unless requested because I don't think thats where the issue is either.

So, elsewhere in the Controller for the scene the ListView lives in, this is how I populate the LV and set the cell factory:

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

So the goal is for the Cell Factory to set the format of the LV to be ContactListCells, which I'd like to contain the format of a ContactCell. The contact cell then is supposed to take in the phones and emails from the contact object, put their info into ContactCellFields, and then put those ContactCellFields (stacked) in the appropriate VBox (vbox_phones/vbox_emails).

After a ton of trial and error and vague StackOverflow posts, I've ALMOST achieved the desired effect. At this moment the code is error-free and does populate the LV with the correct number of Contact objects passed to it, but it visualizes very wrong.

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. The Label for the contact's name just shows an underlined ellipses and the VBox for phones and the VBox for emails seem to be stacked on top of each other resulting in a bunch of ContactCellFields being stacked on each other.

Oh, I almost forgot to mention, when I first ran it all it showed me was the underlined ellipses, then I randomly went to the ListView's fxml definition and changed fixedCellSize to 100.0, this made the cell taller and revealed the stacked vboxes.

Am I missing some simple thing that's stopping the ListView from properly displaying these nodes? Did I do something really stupid and need to reformat how all this works? Any help appreciated.

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.

Upvotes: 1

Views: 228

Answers (1)

cback
cback

Reputation: 67

Update I put in 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.

Here's that fix:

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

+ removing the fixed cell size preference entirely from the ListView resulted in a working ListView of custom nodes. A little annoying that I took the time to write this whole problem description out just to figure it out myself, but hey that's how it goes sometimes. Hopefully, someone else can learn from this when they're trying to create more complicated nested nodes in ListViews like I did here.

Upvotes: 0

Related Questions