Search code examples
javafxtreetableview

JavaFX Application fails to compile on lambda expression


I am trying to build a JavaFX Application to display a TreeTableView. Still setting up this whole thing. I got it to work with only one column without the Product class but i am struggling to make it work with the Product class and two columns. The following piece of code fails to compile:

 col1.setCellValueFactory(
            (TreeTableColumn.CellDataFeatures<Product, String> param) -> param.getValue().getValue().getNameProperty());

and spits out this error:

Error:(38, 121) java: incompatible types: bad return type in lambda expression
java.lang.String cannot be converted to javafx.beans.value.ObservableValue<java.lang.String>

This is the entire code:

public class Controller implements Initializable {

    @FXML
    private TreeTableView<Product> tableView;
    @FXML
    private TreeTableColumn<Product, String> col1;
    @FXML
    private TreeTableColumn<Product, String> col2;

    TreeItem<Product> product1 = new TreeItem<>(new Product("Bread", "300g"));
    TreeItem<Product> product2 = new TreeItem<>(new Product("Eggs", "5"));
    TreeItem<Product> product3 = new TreeItem<>(new Product("Brad Pitt", "One and Only one"));
    TreeItem<Product> product4 = new TreeItem<>(new Product("Moisturizer", "20"));
    TreeItem<Product> product5 = new TreeItem<>(new Product("Horse Lubricant", "4"));

    TreeItem<Product> root = new TreeItem<>(new Product("Name", "Quantity"));

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        root.getChildren().setAll(product1, product2, product3, product4, product5);

        col1.setCellValueFactory(
                (TreeTableColumn.CellDataFeatures<Product, String> param) -> param.getValue().getValue().getNameProperty());
        col2.setCellValueFactory(
                (TreeTableColumn.CellDataFeatures<Product, String> param) -> param.getValue().getValue().getQuantityProperty());

        tableView.setRoot(root);
        tableView.setShowRoot(false);
    }

    public class Product{
        SimpleStringProperty nameProperty;
        SimpleStringProperty quantityProperty;

        public Product(String name, String quantity){
            this.nameProperty = new SimpleStringProperty(name);
            this.quantityProperty = new SimpleStringProperty(quantity);
        }

        public String getNameProperty() {
            return nameProperty.get();
        }

        public SimpleStringProperty namePropertyProperty() {
            return nameProperty;
        }

        public void setNameProperty(String nameProperty) {
            this.nameProperty.set(nameProperty);
        }

        public String getQuantityProperty() {
            return quantityProperty.get();
        }

        public SimpleStringProperty quantityPropertyProperty() {
            return quantityProperty;
        }

        public void setQuantityProperty(String quantityProperty) {
            this.quantityProperty.set(quantityProperty);
        }
    }
}

Solution

  • First, your Product class is not conventional. Typically the field name matches the property name (e.g. name, not nameProperty). Then you name your getter, setter, and property getter after the name of the property. For instance:

    import javafx.beans.property.StringProperty;
    import javafx.beans.property.SimpleStringProperty;
    
    public class Product {
    
      private final StringProperty name = new SimpleStringProperty(this, "name");
      public final void setName(String name) { this.name.set(name); }
      public final String getName() { return name.get(); }
      public final StringProperty nameProperty() { return name; }
    
      private final StringProperty quantity = new SimpleStringProperty(this, "quantity");
      public final void setQuantity(String quantity) { this.quantity.set(quantity); }
      public final String getQuantity() { return quantity.get(); }
      public final StringProperty quantityProperty() { return quantity; }
    
      public Product() {} // typically Java(FX)Beans provide no-arg constructors as well
    
      public Product(String name, String quantity) {
        setName(name);
        setQuantity(quantity);
      }
    }
    

    Note: Your class is a non-static nested (i.e. inner) class. This means each Product instance requires an instance of the enclosing class. If you want to keep Product a nested class, consider making it static. My example above assumes Product is in its own source file.

    With that class, you would define your cell value factories like so:

    TreeTableColumn<Product, String> nameCol = ...;
    nameCol.setCellValueFactory(data -> data.getValue().getValue().nameProperty());
    
    TreeTableColumn<Product, String> quantityCol = ...;
    quantityCol.setCellValueFactory(data -> data.getValue().getValue().quantityProperty());
    

    Notice the factories return the appropriate property of the Product instance. This solves your compilation error since StringProperty is an instance of ObservableValue<String>. It also means your table has direct access to the backing model's property, which helps with keeping the table up-to-date and also with implementing inline editing.


    In case it helps, here's setting the cell value factory of nameCol using an anonymous class which explicitly shows all the types used:

    nameCol.setCellValueFactory(new Callback<>() { // may have to explicitly define type arguments, depending on version of Java
    
      @Override
      public ObservableValue<String> call(TreeTableColumn.CellDataFeatures<Product, String> data) {
        TreeItem<Product> treeItem = data.getValue();
        Product product = treeItem.getValue();
        return product.nameProperty();
      }
    });