Reputation: 125
I have read several threads on using Properties to display data in a JavaFX TableView and I can get all of these to work. Up until now I have been displaying data without problem in my TableView without Properties. However, moving forward I think I need to start using Properties, particularly as I want my TableView to be editable. I suspect that the problem is because all the examples I have seen use one object to display 1 row of data, whereas in my model I have one object (a Field
object) per cell, though I can't think why this should be a problem.
Below is a VERY cut-down version of my Field
class, enough for this test, but not enough to show its real use. I also have a test class TableViewDemo
. Any help with this irksome problem would be much appreciated. Either it will be a silly mistake, or a fundamental problem with my design.
package javafxtest;
import java.sql.SQLException;
import javafx.beans.property.SimpleObjectProperty;
public class Field {
private final SimpleObjectProperty objectValue; //All the data is stored as an object, then cast to appropriate data type
public Field(Object objValue) throws SQLException {
if (objValue == null) {
throw new SQLException("Field value is null");
}
this.objectValue = new SimpleObjectProperty(objValue);
}
public final String getStringValue() {
return (String) this.objectValue.get();
}
public final int getIntValue() {
return (Integer) this.objectValue.get();
}
public final double getDoubleValue() {
return (Double) this.objectValue.get();
}
public final boolean getBooleanValue() {
return (Boolean) this.objectValue.get();
}
}
package javafxtest;
import java.sql.SQLException;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class TableViewDemo extends Application {
@Override
public void start(Stage myStage) {
// Set up the stage
myStage.setTitle("Demonstrate TableView");
FlowPane rootNode = new FlowPane(10, 10);
rootNode.setAlignment(Pos.CENTER);
Scene myScene = new Scene(rootNode, 600, 450);
myStage.setScene(myScene);
//Set up some test data - 4 rows of 4 columns
ObservableList<Field[]> dataModel = FXCollections.observableArrayList();
ObservableList<Field[]> dataModel2 = FXCollections.observableArrayList();
Field[] fields1;
Field[] fields2;
Field[] fields3;
Field[] fields4;
try {
fields1 = new Field[4];
fields1[0] = new Field("R1C1");
fields1[1] = new Field(25);
fields1[2] = new Field(100.00);
fields1[3] = new Field(true);
fields2 = new Field[4];
fields2[0] = new Field("R2C1");
fields2[1] = new Field(30);
fields2[2] = new Field(150.00);
fields2[3] = new Field(false);
fields3 = new Field[4];
fields3[0] = new Field("R3C1");
fields3[1] = new Field(397);
fields3[2] = new Field(1005.99);
fields3[3] = new Field(true);
fields4 = new Field[4];
fields4[0] = new Field("R4C1");
fields4[1] = new Field(1085);
fields4[2] = new Field(1.00);
fields4[3] = new Field(false);
//Add the data to the dataModel
dataModel.addAll(fields1, fields2, fields3, fields4);
//Set up the columns ===== This is where the likely problem is, or at least part of the problem ====
TableColumn<Field[], String> colString = new TableColumn<>("String");
colString.setCellValueFactory(new PropertyValueFactory<>("stringValue"));
colString.setPrefWidth(150);
TableColumn<Field[], Integer> colInt = new TableColumn<>("int");
colInt.setCellValueFactory(new PropertyValueFactory<>("intValue"));
colInt.setPrefWidth(100);
TableColumn<Field[], Double> colDouble = new TableColumn<>("double");
colDouble.setCellValueFactory(new PropertyValueFactory<>("doubleValue"));
colDouble.setPrefWidth(100);
TableColumn<Field[], Boolean> colBoolean = new TableColumn<>("boolean");
colBoolean.setCellValueFactory(new PropertyValueFactory<>("booleanValue"));
colBoolean.setPrefWidth(75);
//Set up the TableView - it takes an Array of Field objects. Add the data and columns to it
TableView<Field[]> tableView = new TableView<Field[]>();
tableView.setItems(dataModel);
tableView.getColumns().addAll(colString, colInt, colDouble, colBoolean);
//Print out the datamodel
dataModel2 = tableView.getItems();
for (int i = 0; i < dataModel2.size(); i++) {
Field[] fields = new Field[4];
fields = dataModel2.get(i);
for (int j = 0; j < fields.length; j++) {
Field field = fields[j];
switch (j) {
case 0: //Column is a String data type
System.out.println(field.getStringValue());
break;
case 1: //Column is a int data type
System.out.println(field.getIntValue());
break;
case 2: //Column is a double data type
System.out.println(field.getDoubleValue());
break;
case 3: //Column is a boolean data type
System.out.println(field.getBooleanValue());
break;
default:
break;
}
}
}
// Set the tableView size
tableView.setPrefWidth(500);
tableView.setPrefHeight(300);
rootNode.getChildren().add(tableView);
} catch (SQLException e) {
System.out.println(e.getLocalizedMessage());
}
myStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This is what I currently use to populate a cell with a String value:
final int x = i;
tableColumn<Field[], String> columnString = (TableColumn<Field[], String>) this.columnModel.get(i);
columnString.setCellValueFactory((TableColumn.CellDataFeatures<Field[], String> fieldArray) -> {
Field arrayField = fieldArray.getValue()[x];
return new ReadOnlyObjectWrapper(arrayField.getStringValue());
});
Upvotes: 0
Views: 2277
Reputation: 209225
This simply isn't the way PropertyValueFactory
works. From the documentation:
An example of how to use this class is:
TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name"); firstNameCol.setCellValueFactory(new PropertyValueFactory<Person,String>("firstName"));
In this example,
Person
is the class type of theTableView
items list. The classPerson
must be declared public.PropertyValueFactory
uses the constructor argument,"firstName"
, to assume thatPerson
has a public methodfirstNameProperty
with no formal parameters and a return type ofObservableValue<String>
.If such a method exists, then it is invoked, and additionally assumed to return an instance of
Property<String>
. The return value is used to populate theTableCell
. In addition, theTableView
adds an observer to the return value, such that any changes fired will be observed by theTableView
, resulting in the cell immediately updating.If no such method exists, then
PropertyValueFactory
assumes thatPerson
has a public methodgetFirstName
orisFirstName
with no formal parameters and a return type ofString
. If such a method exists, then it is invoked, and its return value is wrapped in aReadOnlyObjectWrapper
and returned to theTableCell
.
So, basically, the PropertyValueFactory
retrieves the object representing the row, and uses reflection to look for a method xxxProperty()
, where xxx
is the property name assigned to the PropertyValueFactory
. If no such method exists, then as a fall-back it looks for a method getXxx()
instead, and wraps the result, if it exists, in a ReadOnlyObjectWrapper
.
In your case, the value representing the row is a Field[]
, i.e. the object is an array of Field
s. An array does not have a stringValueProperty()
or getStringValue()
method (etc), so this fails. (The individual elements of the array have those methods, of course, but the PropertyValueFactory
isn't going to look at the array elements, it's just going to look at the value for the row, i.e. the array itself.)
It's hard to know how to "solve" this problem, as there's no really obvious reason you'd want to approach things this way anyway. You could, of course, just do
colString.setCellValueFactory(cellData ->
new SimpleStringProperty(cellData.getValue()[0].getStringValue()));
etc., but at that point it's not clear why you wouldn't just define a class with four regular fields - or even just use an Object[]
and get rid of your Field
class - anyway.
Upvotes: 1