How to set CellValueFactory for TableColumn to allow access to entire object?

Viewed 45

I have a list of simple Person objects that I am using a TableView to display:

class Person {

    private final StringProperty name = new SimpleStringProperty();
    private final StringProperty emailAddress = new SimpleStringProperty();
    private final StringProperty phoneNumber = new SimpleStringProperty();

    public Person(String name, String emailAddress, String phoneNumber) {

        this.name.set(name);
        this.emailAddress.set(emailAddress);
        this.phoneNumber.set(phoneNumber);

    }
}

My goal in this example is to display both emailAddress and phoneNumber in the same TableColumn. This goes beyond simply concatenating the values as I am implementing a custom TableCell for this column (real-world application is more complex):

private TableCell<Person, Person> buildContactCell() {

    return new TableCell<Person, Person>() {

        final VBox root = new VBox();
        final Label lblEmailAddress = new Label();
        final Label lblPhoneNumber = new Label();

        {
            root.getChildren().addAll(lblEmailAddress, lblPhoneNumber);
        }

        @Override
        protected void updateItem(Person person, boolean empty) {

            super.updateItem(person, empty);
            if (person != null && !empty) {
                lblEmailAddress.setText(person.getEmailAddress());
                lblPhoneNumber.setText(person.getPhoneNumber());
                setGraphic(root);
            } else {
                setGraphic(null);
            }
        }
    };
}

I am assuming this works fine, but I am unsure on how to configure the CellValueFactory for this TableColumn to accept the entire Person object.

Is there another way of setting up the TableCell to be able to access more than one property of Person? The other questions I've seen on implementing multiple properties only deal with concatenating, which is not what I am looking for.


Full Code:

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TableCellSample extends Application {

    public static void main(String[] args) {

        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        // Simple Interface
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        root.getChildren().add(getTableView());

        // Show the stage
        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("Sample");
        primaryStage.show();
    }

    private TableView<Person> getTableView() {

        TableView<Person> tableView = new TableView<>();

        TableColumn<Person, String> colName = new TableColumn<>("Name");
        colName.setCellValueFactory(cellData -> cellData.getValue().nameProperty());

        TableColumn<Person, Person> colContact = new TableColumn<>("Contact");
        colContact.setCellFactory(cell -> buildContactCell());

        // **********************************************************************************************
        // Need to set the CellValueFactory for colContact here
        // **********************************************************************************************

        tableView.getColumns().addAll(colName, colContact);
        tableView.setItems(getSampleData());
        return tableView;

    }

    private TableCell<Person, Person> buildContactCell() {

        return new TableCell<Person, Person>() {

            final VBox root = new VBox();
            final Label lblEmailAddress = new Label();
            final Label lblPhoneNumber = new Label();

            {
                root.getChildren().addAll(lblEmailAddress, lblPhoneNumber);
            }

            @Override
            protected void updateItem(Person person, boolean empty) {

                super.updateItem(person, empty);
                if (person != null && !empty) {
                    lblEmailAddress.setText(person.getEmailAddress());
                    lblPhoneNumber.setText(person.getPhoneNumber());
                    setGraphic(root);
                } else {
                    setGraphic(null);
                }
            }
        };
    }

    private ObservableList<Person> getSampleData() {

        ObservableList<Person> persons = FXCollections.observableArrayList();
        persons.addAll(
                new Person("Jack", "jack@outlook.com", "123-456-7890"),
                new Person("Jenny", "goodtime@yahoo.com", "555-867-5309"),
                new Person("Jesse", "mygirl@hotmail.com", "846-989-9988"));
        return persons;
    }
}

class Person {

    private final StringProperty name = new SimpleStringProperty();
    private final StringProperty emailAddress = new SimpleStringProperty();
    private final StringProperty phoneNumber = new SimpleStringProperty();

    public Person(String name, String emailAddress, String phoneNumber) {

        this.name.set(name);
        this.emailAddress.set(emailAddress);
        this.phoneNumber.set(phoneNumber);

    }

    public String getName() {

        return name.get();
    }

    public StringProperty nameProperty() {

        return name;
    }

    public String getEmailAddress() {

        return emailAddress.get();
    }

    public StringProperty emailAddressProperty() {

        return emailAddress;
    }

    public String getPhoneNumber() {

        return phoneNumber.get();
    }

    public StringProperty phoneNumberProperty() {

        return phoneNumber;
    }
}
2 Answers

Your cell value factory needs to return an ObjectProperty<Person> containing the element for the row. You can get the row data using cellData.getValue() in the cell value factory, where cellData is the parameter passed to the call() method. So:

colContact.setCellValueFactory(cellData -> 
    new SimpleObjectProperty<>(cellData.getValue()));

The Model should really serve the needs of the View, and having the TableCell peek behind the curtain to look at the whole TableModel is a code smell. It's probably better to organize the Model such that the data is directly available to the TableCell. Try composition, like this:

public class TableCellSample extends Application {

    public static void main(String[] args) {

        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        // Simple Interface
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        root.getChildren().add(getTableView());

        // Show the stage
        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("Sample");
        primaryStage.show();
    }

    private TableView<Person> getTableView() {

        TableView<Person> tableView = new TableView<>();

        TableColumn<Person, String> colName = new TableColumn<>("Name");
        colName.setCellValueFactory(cellData -> cellData.getValue().nameProperty());

        TableColumn<Person, ContactInfo> colContact = new TableColumn<>("Contact");
        colContact.setCellFactory(cell -> buildContactCell());
        colContact.setCellValueFactory(cellData -> cellData.getValue().contactInfoProperty());

        // **********************************************************************************************
        // Need to set the CellValueFactory for colContact here
        // **********************************************************************************************

        tableView.getColumns().addAll(colName, colContact);
        tableView.setItems(getSampleData());
        return tableView;

    }

    private TableCell<Person, ContactInfo> buildContactCell() {

        return new TableCell<Person, ContactInfo>() {

            final VBox root = new VBox();
            final Label lblEmailAddress = new Label();
            final Label lblPhoneNumber = new Label();

            {
                root.getChildren().addAll(lblEmailAddress, lblPhoneNumber);
            }

            @Override
            protected void updateItem(ContactInfo contactInfo, boolean empty) {

                super.updateItem(contactInfo, empty);
                if (contactInfo != null && !empty) {
                    lblEmailAddress.setText(contactInfo.getEmailAddress());
                    lblPhoneNumber.setText(contactInfo.getPhoneNumber());
                    setGraphic(root);
                } else {
                    setGraphic(null);
                }
            }
        };
    }

    private ObservableList<Person> getSampleData() {

        ObservableList<Person> persons = FXCollections.observableArrayList();
        persons.addAll(
                new Person("Jack", "jack@outlook.com", "123-456-7890"),
                new Person("Jenny", "goodtime@yahoo.com", "555-867-5309"),
                new Person("Jesse", "mygirl@hotmail.com", "846-989-9988"));
        return persons;
    }
}

class Person {
    private final StringProperty name = new SimpleStringProperty();
    private final ObjectProperty<ContactInfo> contactInfoProperty = new SimpleObjectProperty<>();

    public Person(String name, String emailAddress, String phoneNumber) {
        this.name.set(name);
        contactInfoProperty.set(new ContactInfo(emailAddress, phoneNumber));
    }

    public String getName() {
        return name.get();
    }

    public StringProperty nameProperty() {
        return name;
    }

    public String getEmailAddress() {
        return contactInfoProperty.get().emailAddressProperty().get();
    }

    public StringProperty emailAddressProperty() {
        return contactInfoProperty.get().emailAddressProperty();
    }

    public String getPhoneNumber() {
        return contactInfoProperty.get().getPhoneNumber();
    }

    public StringProperty phoneNumberProperty() {
        return contactInfoProperty.get().phoneNumberProperty();
    }

    public ObjectProperty<ContactInfo> contactInfoProperty() {
        return contactInfoProperty;
    }
}

class ContactInfo {
    private final StringProperty emailAddress = new SimpleStringProperty();
    private final StringProperty phoneNumber = new SimpleStringProperty();

    public ContactInfo(String emailAddress, String phoneNumber) {
        this.emailAddress.set(emailAddress);
        this.phoneNumber.set(phoneNumber);
    }

    public String getEmailAddress() {
        return emailAddress.get();
    }

    public StringProperty emailAddressProperty() {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress) {
        this.emailAddress.set(emailAddress);
    }

    public String getPhoneNumber() {
        return phoneNumber.get();
    }

    public StringProperty phoneNumberProperty() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber.set(phoneNumber);
    }
}
Related