0

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);
        }
    }
}
Rebellinho
  • 33
  • 5

1 Answers1

3

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();
  }
});
Slaw
  • 37,820
  • 8
  • 53
  • 80
  • Thank you Slaw for taking the time and answering my question so thoroughly. Unfortunately when i implement the latter part of your code '.quantityProperty' and '.nameProperty' remain red underlined. The following error: Error:(18, 23) java: constructor Product in class sample.Product cannot be applied to given types; required: java.lang.String,java.lang.String found: no arguments reason: actual and formal argument lists differ in length Error:(39, 60) java: cannot find symbol symbol: method nameProperty() location: class javafx.scene.control.TreeItem – Rebellinho Feb 21 '20 at 22:32
  • Sorry about that. I forgot this was for a `TreeTableColumn` and not a `TableColumn`, the former requires two `getValue()` calls. Fixed that. As for the constructor problem, I added a no-arg constructor to the `Product` class. I didn't add it before because I didn't see (and still don't) you attempting to use a no-arg constructor, nor did _your_ `Product` class provide one. – Slaw Feb 22 '20 at 06:13
  • Also added an example of setting the cell value factory using an anonymous class. This example explicitly shows all the types used, so it may help in understanding exactly what's happening in the lambda. – Slaw Feb 22 '20 at 06:20