JavaFX Properties in TableView - javafx

I am teaching myself how to work with JavaFX properties within the TableView and am having trouble with some property types. I have an object Person that contains two properties
public class Person {
private final StringProperty firstName;
private final IntegerProperty age;
public Person(String firstName, Integer age) {
this.firstName = new SimpleStringProperty(firstName);
this.age = new SimpleIntegerProperty(age);
}
public Integer getAge() {
return age.get();
}
public void setAge(Integer age) {
this.age.set(age);
}
public IntegerProperty ageProperty() {
return age;
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public StringProperty firstNameProperty() {
return firstName;
}
}
Once created by goal is to use this object in a TableView. I've created the two table columns like this.
TableColumn<Person, String> firstNameColumn = new TableColumn<Person, String>("First Name");
TableColumn<Person, Integer> ageColumn = new TableColumn<Person, Integer>("Age");
I then want to set the cell value factory using Lambda expressions. This is where the problem arises. The StringProperty firstName works just fine. However, the IntegerProperty gives me the error message "Type mismatch: cannot convert from IntegerProperty to ObservableValue<Integer>"
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
ageColumn.setCellValueFactory(cellData -> cellData.getValue().ageProperty());
Can anyone shed some light on what is going wrong with the ageColumn.setCellValueFactory(...)? Any help would be greatly appreciated.
Thanks!

One somewhat strange part of the property API is that IntegerProperty implements ObservableValue<Number>, not ObservableValue<Integer>. So, somewhat counterintuitively, you need
TableColumn<Person, Number> ageColumn = new TableColumn<Person, Number>("Age");
As an aside, and I'm not sure if this causes problems, but it's more usual to use int instead of Integer for the return type for the get method and the parameter type for the set method in your model class. These match the types for IntegerProperty.get() and IntegerProperty.set(...), so it just avoids some implicit auto (un)boxing.

you can use .asObject Method to make your code work
it doesn't need you to change the TableColumn Paramater to Number
ageColumn.setCellValueFactory(cellData -> cellData.getValue().ageProperty().asObject());
idk which one is better code,
change the parameter to Number or use asObject Method
related Link

For String type :
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
When you want to use IntegerProperty or DoubleProperty, the setCellValueFactory(...) must have an additional asObject():
ageColumn.setCellValueFactory(cellData -> cellData.getValue().ageProperty().asObject());
This is necessary because of a bad design decision of JavaFX.
Source and credits

Related

Populate Tableview with super class fields

I have several classes that all inherit from one super class that need to populate several TableViews related to their class.
The super class is abstract and some of the getters and setters are final but still contains data needed to populate the cells.
Writing a new Callback class for each and every column is doable, but I'm looking for a way to implements this.
sample code
class SuperClass
{
protected String name;
protected double value;
public final void setName(String name)
{
this.name = name;
}
public final void getName()
{
return this.name;
}
public final void setValue(double value)
{
this.value = value;
}
public double getValue()
{
return this.value;
}
}
class SubClass1 extends SuperClass
{
private int id;
public void setId(int id)
{
this.id = id;
}
public int getId()
{
return this.id;
}
}
class SubClass2 extends SuperClass
{
private String location;
public void setLocation(String location)
{
this.location = location;
}
}
class SubClass3 extends SuperClass
{
private ObservableMap<SuperClass> map;
public ObservableMap<SuperClass> map()
{
return this.map;
}
}
TableView
TableColumn<SubClass1, Integer> tc1_id;
TableColumn<SubClass1, String> tc1_name;
TableColumn<SubClass1, Double> tc1_value;
TableColumn<SubClass2, String> tc2_loc;
TableColumn<SubClass2, String> tc2_name;
TableColumn<SubClass2, Double> tc2_value;
TableColumn<SubClass3, String> tc3_name;
TableColumn<SubClass3, Double> tc3_value;
Here's a reference of what I was going to do...
Accessing Subclass properties in a JavaFX TableView ObservableArrayList
But just with the sample code, I'm basically rewriting 2 methods, 3 times each... and there's a bit more than that in the actual program. (Just a smidge more)
I think you are just asking how to reduce the amount of code you have to write. The solution is just the same as any such question: write a method that performs the repetitive part, and parametrize it with the parts that vary. So in this case, you just need to write a generic utility method to generate your table columns, taking the title of the column and the function that produces the property the cell value factory needs.
E.g. you could do something like
private <S,T> TableColumn<S,T> createColumn(String title, Function<S, Property<T>> prop) {
TableColumn<S,T> column = new TableColumn<>(title);
column.setCellValueFactory(cellData -> prop.apply(cellData.getValue()));
return column ;
}
and then if your model classes use JavaFX properties, all you need is
TableColumn<SubClass1, Number> tc1Id = createColumn("Id", SubClass1::idProperty);
etc.
If you are not using JavaFX properties (which is the recommended approach), you can still do
TableColumn<SubClass2, String> tc2Loc =
createColumn("Location", item -> new SimpleStringProperty(item.getLocation()));
or just create a method that accepts a Function<S,T> instead of a Function<S,Property<T>>.

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

Float TableColumn [duplicate]

I am teaching myself how to work with JavaFX properties within the TableView and am having trouble with some property types. I have an object Person that contains two properties
public class Person {
private final StringProperty firstName;
private final IntegerProperty age;
public Person(String firstName, Integer age) {
this.firstName = new SimpleStringProperty(firstName);
this.age = new SimpleIntegerProperty(age);
}
public Integer getAge() {
return age.get();
}
public void setAge(Integer age) {
this.age.set(age);
}
public IntegerProperty ageProperty() {
return age;
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public StringProperty firstNameProperty() {
return firstName;
}
}
Once created by goal is to use this object in a TableView. I've created the two table columns like this.
TableColumn<Person, String> firstNameColumn = new TableColumn<Person, String>("First Name");
TableColumn<Person, Integer> ageColumn = new TableColumn<Person, Integer>("Age");
I then want to set the cell value factory using Lambda expressions. This is where the problem arises. The StringProperty firstName works just fine. However, the IntegerProperty gives me the error message "Type mismatch: cannot convert from IntegerProperty to ObservableValue<Integer>"
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
ageColumn.setCellValueFactory(cellData -> cellData.getValue().ageProperty());
Can anyone shed some light on what is going wrong with the ageColumn.setCellValueFactory(...)? Any help would be greatly appreciated.
Thanks!
One somewhat strange part of the property API is that IntegerProperty implements ObservableValue<Number>, not ObservableValue<Integer>. So, somewhat counterintuitively, you need
TableColumn<Person, Number> ageColumn = new TableColumn<Person, Number>("Age");
As an aside, and I'm not sure if this causes problems, but it's more usual to use int instead of Integer for the return type for the get method and the parameter type for the set method in your model class. These match the types for IntegerProperty.get() and IntegerProperty.set(...), so it just avoids some implicit auto (un)boxing.
you can use .asObject Method to make your code work
it doesn't need you to change the TableColumn Paramater to Number
ageColumn.setCellValueFactory(cellData -> cellData.getValue().ageProperty().asObject());
idk which one is better code,
change the parameter to Number or use asObject Method
related Link
For String type :
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
When you want to use IntegerProperty or DoubleProperty, the setCellValueFactory(...) must have an additional asObject():
ageColumn.setCellValueFactory(cellData -> cellData.getValue().ageProperty().asObject());
This is necessary because of a bad design decision of JavaFX.
Source and credits

How do I share a Observablelist between 2 controllers , I have tried various methods but I get null pointer exceptions

I am absolutely new to coding and am stuck with some "binding" code. I have a Record class with getters and setters, and a records Observable list which together populate a Tableview in a controller called RecordController. I want to populate another tableview on a separate AdminController file both with different view fxml's.
How can I bind the SimpleStringProperties in my Record.java class in such a manner that when they get updated by by the set method( by any UI user interaction) in RecordContoller ..the (editatble) Tableview in the AdminController gets populated with the new values.
Some bits of the code.....
public class Record {
private final SimpleStringProperty stationName;
private final SimpleStringProperty staffName;
private final SimpleStringProperty staffNumber;
private final SimpleStringProperty agencyName;
private final SimpleStringProperty aircraftType;
private final SimpleStringProperty issueDate;
private final SimpleStringProperty validTill;
public Record(String stationName, String staffName,
String staffNumber, String agencyName, String aircraftType,
String issueDate, String validTill) {
this.stationName = new SimpleStringProperty(stationName);
this.staffName = new SimpleStringProperty(staffName);
this.staffNumber = new SimpleStringProperty(staffNumber);
this.aircraftType = new SimpleStringProperty(aircraftType);
this.agencyName = new SimpleStringProperty(agencyName);
this.issueDate = new SimpleStringProperty(issueDate);
this.validTill = new SimpleStringProperty(validTill);
}
public String getStationName() {
return stationName.getValue();
}
public String getStaffName() {
return staffName.getValue();
}
public String getStaffNumber() {
return staffNumber.getValue();
}
public String getAgencyName() {
return agencyName.getValue();
}
public String getAircraftType() {
return aircraftType.getValue();
}
public String getIssueDate() {
return issueDate.getValue();
}
public String getValidTill() {
return validTill.getValue();
}
}
And the RecordController with the first Tableview.....
columnStation.setCellValueFactory(new PropertyValueFactory<>("stationName"));
columnStaffName.setCellValueFactory(new PropertyValueFactory<>("staffName"));
columnStaffNumber.setCellValueFactory(new PropertyValueFactory<>("staffNumber"));
columnAgency.setCellValueFactory(new PropertyValueFactory<>("agencyName"));
columnAircraftType.setCellValueFactory(new PropertyValueFactory<>("aircraftType"));
columnIssueDate.setCellValueFactory(new PropertyValueFactory<>("issueDate"));
columnValidTill.setCellValueFactory(new PropertyValueFactory<>("validTill"));
recordTableView.setItems(null);
recordTableView.setItems(records);
The records observable list being .....
private ObservableList<Record> records=FXCollections.observableArrayList();
The adminController for the other fxml also has another tableview which I want to populate in some way with the property variables in the Record.java (class / bean ? ).
To recover from the -1 my question received...
In case a fellow newbie stumbles on this question. The answer lies in the following post.
Passing Parameters JavaFX FXML
If you use the JavaFX properties pattern, then changes to the properties in your Record instances will be automatically reflected in the table view. So your Record class should look like
public class Record {
private final StringProperty stationName;
private final StringProperty staffName;
private final StringProperty staffNumber;
private final StringProperty agencyName;
private final StringProperty aircraftType;
private final StringProperty issueDate;
private final StringProperty validTill;
public Record(String stationName, String staffName,
String staffNumber, String agencyName, String aircraftType,
String issueDate, String validTill) {
this.stationName = new SimpleStringProperty(stationName);
this.staffName = new SimpleStringProperty(staffName);
this.staffNumber = new SimpleStringProperty(staffNumber);
this.aircraftType = new SimpleStringProperty(aircraftType);
this.agencyName = new SimpleStringProperty(agencyName);
this.issueDate = new SimpleStringProperty(issueDate);
this.validTill = new SimpleStringProperty(validTill);
}
public StringProperty stationNameProperty() {
return stationName ;
}
public final String getStationName() {
return stationNameProperty().get();
}
public final void setStationName(String stationName) {
stationNameProperty().set(stationName);
}
// similarly for other properties...
}
Note that many IDEs (e.g. Netbeans, or Eclipse with the E(fx)clipse plugin) will generate these methods for you.
Now, as long as you are changing the same Record objects that are in the table's items list, changes to those Records will automatically result in changes in the table: there is no need for additional binding.

How to set items for JavaFX TableView - object contains another object?

i want to be short as possible without omitting useful information.
I have the following class:
public class Address{
StringProperty city = new SimpleStringProperty();
StringProperty street = new SimpleStringProperty();
//following the constructor, getters and setters
...
}
I have another class Client, that one has an Address member
public class Client {
StringProperty name = new SimpleStringProperty();
StringProperty id = new SimpleStringProperty();
ObjectProperty<Address> address = new SimpleObjectProperty<>();
//following the constructor, getters and setters
...
}
and a JavaFX interface with a controller that contains a TableView object that should output in 3 column the members of the Client class and the city member of Address class for the given object. My TableView and TableColumn definition are the following code
public class SettingsController {
TableColumn<Client, String> clientNameCol;
TableColumn<Client, String> clientEmailCol;
TableColumn<Client, String> clientCityCol;
private TableView<Client> clientSettingsTableView;
...
...
clientNameCol = new TableColumn<>("Name");
clientNameCol.setCellValueFactory(new PropertyValueFactory<Client, String>("name"));
clientEmailCol = new TableColumn<>("email");
clientEmailCol.setCellValueFactory(new PropertyValueFactory<Client, String>("email"));
clientCityCol = new TableColumn<>("City");
clientCityCol.setCellValueFactory(new PropertyValueFactory<Client, String>("city"));
clientSettingsTableView.setItems(clientData);
clientSettingsTableView.getColumns().clear();
clientSettingsTableView.getColumns().addAll(clientNameCol, clientEmailCol, clientCityCol);
and of course there is an ObservableList clientData that contains an array of Client object.
Everything works fine except the column that should output the city for each client.
How should i define the column for the city (contained by an Address member) of Client object?
#invariant thanks for your help, I googled a little bit more and i ended up with the following solution:
clientCityCol = new TableColumn<>("City");
clientCityCol.setCellValueFactory(new PropertyValueFactory<Client, Address>("address"));
// ======== setting the cell factory for the city column
clientCityCol.setCellFactory(new Callback<TableColumn<Client, Address>, TableCell<Client, Address>>(){
#Override
public TableCell<Client, Address> call(TableColumn<Client, Address> param) {
TableCell<Client, Address> cityCell = new TableCell<Client, Address>(){
#Override
protected void updateItem(Address item, boolean empty) {
if (item != null) {
Label cityLabel = new Label(item.getCity());
setGraphic(cityLabel);
}
}
};
return cityCell;
}
});
Address class has a getter getCity() which returns the city member as a String().
This works in fxml?
Consider the following code:
<TableColumn prefWidth="8" text="City">
<cellValueFactory >
<PropertyValueFactory property="adress.city" />
</cellValueFactory>
</TableColumn>
Working that way i have a blank cell.
you forgot to mention that you have to change the definition of clientCityCol TableColumn<Client, String> clientCityCol; into TableColumn<Client, Address> clientCityCol; otherwise it won't work.

Resources