Search code examples
javajavafx-8

How to populate TableView data when parent class property is passed into PropertyValueFactory in JavaFX?


I was trying to add TableColumns to my TableView when an error popped up because my PropertyValueFactory could not read the property inside it. This was because the property that I was passing to it was from the parent class. Is there anyway for PropertyValueFactory to accept the parent class data through the child class because of the super function? Code is posted below. I looked at other SO posts but they don't address what to do when you use an inherited class property.

Parent Class

public abstract class User {
    private String username;
    private String password;
    
    public User(String username, String password){
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    
}

Child Class

public class Customer extends User {
    private int points;
    
    public Customer(String username, String password, int points){
        super(username,password);
        this.points = points;
    }

    public int getPoints() {
        return points;
    }

    public void setPoints(int points) {
        this.points = points;
    }
}

TableColumn Code

TableColumn<Customer,String> customerUsernameCol = new TableColumn<>("Customer Username");
customerUsernameCol.setMinWidth(100);
customerUsernameCol.setCellValueFactory(new PropertyValueFactory<>("username")); //Does not work as intended

TableColumn<Customer,String> customerPasswordCol = new TableColumn<>("Customer Password");
customerPasswordCol.setMinWidth(100);
customerPasswordCol.setCellValueFactory(new PropertyValueFactory<>("password")); //Does not work as intended

TableColumn<Customer,Integer> customerPointsCol = new TableColumn<>("Customer Points");
customerUsernameCol.setMinWidth(100);
customerUsernameCol.setCellValueFactory(new PropertyValueFactory<>("points"));

Solution

  • Don't use PropertyValueFactory. Define your own CellValueFactory. You just need to implement the appropriate Callback.
    (Notes after the code.)

    import javafx.application.Application;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.value.ObservableValue;
    import javafx.collections.ObservableList;
    import javafx.scene.Scene;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableView;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    import javafx.util.Callback;
    
    public class TblVwTst extends Application {
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            Customer customer1 = new Customer("George", "Best", 8);
            TableView<Customer> tblVw = new TableView<>();
            ObservableList<Customer> items = tblVw.getItems();
            items.add(customer1);
            TableColumn<Customer, String> usernameColumn = new TableColumn<>("User");
            Callback<TableColumn.CellDataFeatures<Customer, String>, ObservableValue<String>> usernameColumnCellValue;
            usernameColumnCellValue = cellDataFeatures -> {
                Customer customer = cellDataFeatures.getValue();
                String username = customer.getUsername();
                ObservableValue<String> usernameObservableValue = new SimpleStringProperty(username);
                return usernameObservableValue;
            };
            usernameColumn.setCellValueFactory(usernameColumnCellValue);
            TableColumn<Customer, String> passwordColumn = new TableColumn<>("Password");
            Callback<TableColumn.CellDataFeatures<Customer, String>, ObservableValue<String>> passwordColumnCellValue;
            passwordColumnCellValue = cellDataFeatures -> {
                Customer customer = cellDataFeatures.getValue();
                String password = customer.getPassword();
                ObservableValue<String> passwordObservableValue = new SimpleStringProperty(password);
                return passwordObservableValue;
            };
            passwordColumn.setCellValueFactory(passwordColumnCellValue);
            TableColumn<Customer, Number> pointsColumn = new TableColumn<>("Points");
            Callback<TableColumn.CellDataFeatures<Customer, Number>, ObservableValue<Number>> pointsColumnCellValue;
            pointsColumnCellValue = cellDataFeatures -> {
                Customer customer = cellDataFeatures.getValue();
                int points = customer.getPoints();
                ObservableValue<Number> pointsCellValue = new SimpleIntegerProperty(points);
                return pointsCellValue;
            };
            pointsColumn.setCellValueFactory(pointsColumnCellValue);
            ObservableList<TableColumn<Customer, ?>> tblVwColumns = tblVw.getColumns();
            tblVwColumns.add(usernameColumn);
            tblVwColumns.add(passwordColumn);
            tblVwColumns.add(pointsColumn);
            BorderPane root = new BorderPane(tblVw);
            Scene scene = new Scene(root);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    
    abstract class User {
        private String username;
        private String password;
    
        public User(String username, String password){
            this.username = username;
            this.password = password;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    
    class Customer extends User {
        private int points;
    
        public Customer(String username, String password, int points){
            super(username,password);
            this.points = points;
        }
    
        public int getPoints() {
            return points;
        }
    
        public void setPoints(int points) {
            this.points = points;
        }
    }
    

    The Callback in the above code is implemented by a lambda expression since interface Callback contains a single, abstract method named call which takes a single parameter and returns a value. Callback is a generic interface. In the above code, the actual type of the parameter to method call is TableColumn.CellDataFeatures<Customer, String> and the type of the returned value is ObservableValue<String>. The JavaFX infrastructure will call your method and pass it the appropriate argument. You need to extract the relevant value from the supplied method argument and return that value as an ObservableValue. Since the members of your Customer class are not ObservableValues, you need to create one. Hence, in the above code, I return SimpleStringProperty, which is a concrete (i.e. not abstract) class that implements interface ObservableValue, as the cellValueFactory property for the usernameColumn and passwordColumn. Similarly, I return SimpleIntegerProperty for pointsColumn.

    In the above code, I also create a sample Customer and add it to the TableView.

    This is how the GUI looks when I run the above code.

    screen capture of running app