Javafx TableView not showing data - javafx

I used ObservableList to populate the TableView but the problem is that the data is not showing in the table I don't know what is the problem because the number of rows is exactly like I added them capture but there is nothing in the cells!
here is the code of the controller:
public class EnlistDim {
private static final String DEFAULT="-fx-text-background-color: black; -fx-background-color: steelblue;-fx-fill: red ;";
#FXML
private TableView<Parameter> tab;
#FXML
public void initialize() {
final ObservableList<Parameter> data = FXCollections.observableArrayList(
new Parameter("Query","Access method","Sequential scan"),
new Parameter("Query","Access method","in memory"),
new Parameter("Query","Operation","join"),
new Parameter("Query","Operation","Scan"),
new Parameter("Query","Operation","Sort"),
new Parameter("Database","Buffer management","Without buffer"),
new Parameter("Database","Buffer management","FIFO"),
new Parameter("Database","Buffer management","LIFO"),
new Parameter("Database","Buffer management","LRU"),
new Parameter("Database","Buffer management","Other"),
new Parameter("Database","Optimization structure","Not used"),
new Parameter("Database","Optimization structure","Partionning"),
new Parameter("Database","Optimization structure","Materialized View"),
new Parameter("Database","Optimization structure","compresssion"),
new Parameter("Database","System storage type","Database SQL"),
new Parameter("Database","System storage type","New SQL"),
new Parameter("Database","System storage type","Document"),
new Parameter("Database","System storage type","Graph"),
new Parameter("Database","System storage type","NVRAM"),
new Parameter("Database","System storage type","key value store"),
new Parameter("Database","Data storage type","Row Oriented"),
new Parameter("Database","Data storage type","Column Oriented"),
new Parameter("Database","Data storage type","Hybrid Oriented"),
new Parameter("Hardware","Processing device","CPU"),
new Parameter("Hardware","Processing device","GPU"),
new Parameter("Hardware","Processing device","FPGA"),
new Parameter("Hardware","Storage device","RAM"),
new Parameter("Hardware","Storage device","SSD"),
new Parameter("Hardware","Storage device","NVRAM"),
new Parameter("Hardware","Communication device","Modem"),
new Parameter("Hardware","Communication device","Cable"),
new Parameter("Hardware","Communication device","FaxModem"),
new Parameter("Hardware","Communication device","Router")
);
tab.setEditable(true);
tab.setItems(data);
tab.setStyle(DEFAULT);
}
}
and the code of Parameter class:
class Parameter {
SimpleStringProperty cat;
SimpleStringProperty subCat;
SimpleStringProperty subSubCat;
Parameter(String cat, String subCat, String subSubCat) {
this.cat = new SimpleStringProperty(cat);
this.subCat = new SimpleStringProperty(subCat);
this.subSubCat = new SimpleStringProperty(subSubCat);
}
public String getCat() {
return cat.get();
}
public void setCat(String c) {
cat.set(c);
}
public String getSubCat() {
return subCat.get();
}
public void setSubCat(String sc) {
subCat.set(sc);
}
public String getSubSubCat() {
return subSubCat.get();
}
public void setSubSubCat(String ssc) {
subSubCat.set(ssc);
}
}

You need to actually tell the TableView HOW to display the data. This is done using a CellValueFactory. Basically, you need to tell each column of the table what type of data it holds and where it gets that data from.
You need to start by defining your columns (give them an fx:id either in the FXML file or in SceneBuilder):
#FXML
TableColumn<Parameter, String> colCategory;
#FXML
TableColumn<Parameter, String> colSubCategory;
#FXML
TableColumn<Parameter, String> colSubSubCategory;
Each TableColumn takes two Type parameters. The first defines the object being displayed (Parameter). The second is the data type for this column (all yours are String).
Once the columns are defined, you need to set their CellValueFactory in your initialize() method:
colCategory.setCellValueFactory(new PropertyValueFactory<Parameter, String>("cat"));
colSubCategory.setCellValueFactory(new PropertyValueFactory<Parameter, String>("subCat"));
colSubSubCategory.setCellValueFactory(new PropertyValueFactory<Parameter, String>("subSubCat"));
Here you are telling each column where to find the data to be displayed. The last argument on the line, in the quotes, is the name of your property within the Parameter object.
So, when JavaFX populates your table, it will takes these steps to populate each column (colCategory, for example):
Get the CellValueFactory for colCategory.
The factory is a PropertyValueFactory, so determine which class holds the property (in this case it is the Parameter class)
Look in the Parameter class for a String property by the name of "cat"
Populate the column's cell with the value of the cat property.

Related

How to get the column title and first column row value for a cell in javafx

I have JavaFX tableview that I have created dynamically. When one double-clicks on a cell on the tableview, I need to get the name of the column the cell is in and the value of the first cell in the row this cell is in. I have tried searching over google and found no particular solution to this problem. Kindly show me some sample code.
Ok, so first, let's assume your TableView is attached to a model:
public TableView<MyModel> myTable;
where MyModel is something like:
public class MyModel {
private Integer id;
private String name;
// ... etc.
}
so, MyModel is a common POJO. You can have columns of your TableView like:
TableColumn<MyModel, Integer> id = new TableColumn<>("ID");
id.setCellValueFactory(new PropertyValueFactory<>("id"));
TableColumn<MyModel, String> name = new TableColumn<>("Name");
name.setCellValueFactory(new PropertyValueFactory<>("name"));
and then, to add the columns to your table:
myTable.getColumns().addAll(id, name);
Now, let's listen to the click event using a rowFactory:
myTable.setRowFactory(tv -> {
TableRow<MyModel> row = new TableRow<>();
row.setOnMouseClicked(event -> {
// check for non-empty rows, double-click with the primary button of the mouse
if (!row.isEmpty() && event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
MyModel element = row.getItem();
// now you can do whatever you want with the myModel variable.
System.out.println(element);
}
});
return row ;
});
That should do the work.

Bind TableView to ObservableList

I have an observable List saving Author objects. The gui is able to add an author to my database. The observable list contains all the objects of the database. I want my table to update automatically if I add an Author in the databse.
I have already tried to refresh the list with table.refresh(). I am also thinking about using a change listener for the observable list.
Here the Code for creating the table. authorList is an observable list. I think I don't quite understand how to use an observable list. My suggestion was that by using "table.setItem(authorList)", my table automatically updates its entries if something is changed in the list. Obviously this is not the case.
private void createAuthorsTablePane() {
// TODO: Layout ändern
GridPane authorGridPane = new GridPane();
// create table
TableView<Author> table = new TableView<>();
// Create columns with title
TableColumn<Author, String> idColumn = new TableColumn<>("ID");
TableColumn<Author, String> nameColumn = new TableColumn<>("Name");
TableColumn<Author, String> emailColumn = new TableColumn<>("Email");
TableColumn<Author, String> publicationsColumn = new TableColumn<>("Publications");
// Add columns to table node
table.getColumns().add(idColumn);
table.getColumns().add(nameColumn);
table.getColumns().add(emailColumn);
table.getColumns().add(publicationsColumn);
// Bindings
PropertyValueFactory<Author, String> idColumnFactory = new PropertyValueFactory<>("id");
PropertyValueFactory<Author, String> nameColumnFactory = new PropertyValueFactory<>("name");
PropertyValueFactory<Author, String> emailColumnFactory = new PropertyValueFactory<>("email");
PropertyValueFactory<Author, String> publicationsColumnFactory = new PropertyValueFactory<>("publications");
idColumn.setCellValueFactory(idColumnFactory);
nameColumn.setCellValueFactory(nameColumnFactory);
emailColumn.setCellValueFactory(emailColumnFactory);
publicationsColumn.setCellValueFactory(publicationsColumnFactory);
table.setItems(authorList);
// Create Buttons
createAuthorButton = new Button("Create author");
createAuthorButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
mainController.createAuthorController();
}
});
deleteAuthorButton = new Button("Delete selected author");
// Add Nodes to Pane
authorGridPane.add(new Label("Authors"), 0, 0);
authorGridPane.add(table, 0, 1);
authorGridPane.add(deleteAuthorButton, 0, 2);
authorGridPane.add(createAuthorButton, 1, 2);
authorPane = authorGridPane;
}
Here is the class, where I create my authorList. I am registering the list in the class where I create the table by using a controller.
public class ObservableModel {
private ObservableList<Publication> publicationList;
private ObservableList<Author> authorList;
public ObservableModel(DatabaseService database) {
publicationList = FXCollections.observableList(database.getPublications());
authorList = FXCollections.observableList(database.getAuthors());
}
public ObservableList<Publication> getPublicationList() {
return publicationList;
}
public ObservableList<Author> getAuthorList() {
return authorList;
}
}
TableView<Author> table = new TableView<>();
private ObservableList<Author> authorList = FXCollections.observableArrayList();
private Property<ObservableList<Author>> authorListProperty = new SimpleObjectProperty<>(authorList);
table.itemsProperty().bind(authorListProperty); // The Binding
Every time you change authorList the tableview will be updated as well
idcolumnFactory.setCellValueFactory(cellData -> cellData.getValue().getIdProperty());
or if you declare simple bean
idcolumnFactory.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getId()));
don't forget
table.setItems(your_observale_list);

Map integer to string of custom class in combobox

I have a tabelview that displays a list of appointees. Each appointe has a group assigned to it, the id of that group is saved in the appointe class.
I want to display a combobox inside a tablecell that displays the selected group and all other groups that exist. I can set the items of the combobox in the cell factory but i cant set the selected value of the respective appointee.
I have a method that returns the Group from the observable list when i provide it with the id. Thats means i need the id in the cellfactory but i didnt find a way to do this. I also need to display the name of the group and not the refernce to the clas. Is there a way to do this, or should i change my approach?
The Appointee class
public class Appointee {
private SimpleIntegerProperty id;
private SimpleStringProperty firstname;
private SimpleStringProperty lastname;
private SimpleIntegerProperty group;
private SimpleIntegerProperty assigned;
public Appointee(int id, String firstname, String lastname, int group, int assigned){
this.id = new SimpleIntegerProperty(id);
this.firstname = new SimpleStringProperty(firstname);
this.lastname = new SimpleStringProperty(lastname);
this.group = new SimpleIntegerProperty(group);
this.assigned = new SimpleIntegerProperty(assigned);
}
The Group class
public class Group {
private IntegerProperty id;
private StringProperty name;
private IntegerProperty members;
private IntegerProperty assigned;
public Group(int id, String name, int members, int assigned) {
this.id = new SimpleIntegerProperty(id);
this.name = new SimpleStringProperty(name);
this.members = new SimpleIntegerProperty(members);
this.assigned = new SimpleIntegerProperty(assigned);
}
The appointe table view
public AppointeeTableView() {
// define table view
this.setPrefHeight(800);
this.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
this.setItems(MainController.appointeeObervableList);
this.setEditable(true);
// define columns
...
TableColumn groupCol = new TableColumn("Group"); // group
groupCol.setCellFactory(col -> {
TableCell<Group, StringProperty> c = new TableCell<>();
final ComboBox<String> comboBox = new ComboBox(MainController.groupObservableList);
c.graphicProperty().bind(Bindings.when(c.emptyProperty()).then((Node) null).otherwise(comboBox));
return c;
});
groupCol.setEditable(false);
...
}
Override the updateItem method of the TableCell to update the cell, make sure the new value is saved on a change of the TableCell value and use a cellValueFactory.
final Map<Integer, Group> groupById = ...
final ObservableList<Integer> groupIds = ...
TableColumn<Group, Number> groupCol = new TableColumn<>("Group");
groupCol.setCellValueFactory(cd -> cd.getValue().groupProperty());
class GroupCell extends ListCell<Integer> {
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
Group group = groupById.get(item);
if (empty || group == null) {
setText("");
} else {
setText(group.getName());
}
}
}
groupCol.setCellFactory(col -> new TableCell<Group, Integer>() {
private final ComboBox<Integer> comboBox = new ComboBox<>(groupIds);
private final ChangeListener<Integer> listener = (o, oldValue, newValue) -> {
Group group = (Group) getTableView().getItems().get(getIndex());
group.setGroup(newValue);
};
{
comboBox.setCellFactory(lv -> new GroupCell());
comboBox.setButtonCell(new GroupCell());
}
#Override
protected void updateItem(Number item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
comboBox.valueProperty().removeListener(listener);
setGraphic(comboBox);
comboBox.setValue((Integer) item);
comboBox.valueProperty().addListener(listener);
}
}
});
It's a bit hard to tell from only some small code snippets, but my general recommendation when working with frontends is to distinguish between the model and the rendering on each level. This applies to JavaFX, Swing and Angular applications alike.
The appointee TableView should likely be TableView<Appointee>.
For the appointee.group property you have two options: either use Group or (e.g. when this would generate too many duplicate data when de-/serializing from/ to JSON) then use a business key. The first option is usually easier to implement and work with. With the second option you'll need some service / code to convert back to a Group and have to think about where/ at what level exactly you want to do the conversion.
Let's go on here with the second option as you currently have specified appointee.group to be an integer.
In this case the group column should be TableColum<Appointee, Integer>.
The group cell then should be TableCell<Appointee, Integer>.
So far we've only talked about the model, not about rendering except that we want to display the appointees in a table.
I recommend to do this also on the next level.
Don't use a ComboBox<String> for a groups comboBox but a ComboBox<Group>. String is how you want to render the group inside the comboBox but the Group is the model. Also ComboBox<Integer>, the type of the business key, is a bit misleading (as you want a Groups comboBox, not an integer comboBox) and limits the flexibility of your code.
Use the converting service / code I've mentioned when pre-selecting a value in the comboBox.
The group cell should have the type ListCell<Group> and in the updateItem method, which concerns about how to render a Group, you could e.g. use the name property to get the String representation.
Of course there are variations of this approach, but make sure that on each level you know what the model of the control is and what the renderer of the control is. Always design your code using the model and use the rendering types only at the lowest rendering level.

Populate TableView with Map<String, Map<String, String>> JavaFX

my brain is burning already and I cannot find correct way to populate TableView in JavaFX. My data map is Map<String, Map<String, String>> . First key is a state name, value is map that has key as variable and value as variable value. I need a table like
| States | x | y | ...
| state 1 | 5 | 6 | ...
etc.
EDIT: This is my last solution that populate only one column and other are populated by same data. This can be in another foreach with values.
for (TableColumn<ObservableList<String>, ?> column : table.getColumns()) {
TableColumn<ObservableList<String>, String> col = (TableColumn<ObservableList<String>, String>) column;
col.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(someValue));
}
I think about solution with something like this, but it populates rows by last value only:
ObservableList<ObservableList<String>> tableData = FXCollections.observableArrayList();
for (Map<String, String> map : map.values()) {
for (Map.Entry<String, String> entry : map.entrySet()) {
if (Utils.getTableColumnByName(table, entry.getKey()) != null) {
TableColumn<ObservableList<String>, String> column = (TableColumn<ObservableList<String>, String>) Utils.getTableColumnByName(table, entry.getKey());
column.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(entry.getValue()));
}
}
}
for (Integer stateIndex : states) {
tableData.add(FXCollections.observableArrayList("state " + stateIndex));
}
table.setItems(tableData);
I am looking for only any suggestions, no complete solutions :)
EDIT 2: With this I populate only first row at beginning of execution. I don't know how populate another rows after complete of execution. This is in foreach:
TableColumn<ObservableList<String>, String> varColumn = new TableColumn();
varColumn.setText(variable.getText());
varColumn.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(value.getText()));
table.getColumns().add(varColumn);
And this after foreach:
table.setItems(getTableData());
And getTableData():
ObservableList<ObservableList<String>> data = FXCollections.observableArrayList();
for (String row : map.keySet()) {
data.add(FXCollections.observableArrayList(row));
}
return data;
I hope that is clear... thanks!
The data structure for a TableView is an ObservableList<SomeObject>, which is different from the data structure of your model, which is Map<String, Map<String, String>>. So you need some way to transform the model data structure into an ObservableList which can be used in the TableView.
A couple of ways I can think of doing this are:
Create a set of dummy objects which go in the list, one for each row which will correspond to a real item in your model and provide cell value factories which dynamically pull the data you require out of your model.
Create a parallel ObservableList data structure and sync the underlying data between your model and your ObservableList as required.
Option 2 of the above is the sample which I provide here. It is a kind of MVVM (model, view, view model) architecture approach. The model is your underlying map-based structure, the view model is the observable list that is consumed by the view which is the TableView.
Here is a sample.
import javafx.application.Application;
import javafx.collections.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
public class StateView extends Application {
#Override
public void start(Stage stage) {
ObservableMap<String, ObservableMap<String, String>> states = populateStates();
final TableView<StateItem> tableView = new TableView<>();
tableView.setItems(extractItems(states));
final TableColumn<StateItem, String> stateCol = new TableColumn<>("State");
final TableColumn<StateItem, String> variableCol = new TableColumn<>("Variable");
final TableColumn<StateItem, String> valueCol = new TableColumn<>("Value");
stateCol.setCellValueFactory(new PropertyValueFactory<>("stateName"));
variableCol.setCellValueFactory(new PropertyValueFactory<>("variableName"));
valueCol.setCellValueFactory(new PropertyValueFactory<>("variableValue"));
tableView.getColumns().setAll(stateCol, variableCol, valueCol);
states.addListener((MapChangeListener<String, ObservableMap<String, String>>) change ->
tableView.setItems(extractItems(states))
);
Scene scene = new Scene(tableView);
stage.setScene(scene);
stage.show();
}
private ObservableList<StateItem> extractItems(ObservableMap<String, ObservableMap<String, String>> states) {
return FXCollections.observableArrayList(
states.keySet().stream().sorted().flatMap(state -> {
Map<String, String> variables = states.get(state);
return variables.keySet().stream().sorted().map(
variableName -> {
String variableValue = variables.get(variableName);
return new StateItem(state, variableName, variableValue);
}
);
}).collect(Collectors.toList())
);
}
private static final Random random = new Random(42);
private static final String[] variableNames = { "red", "green", "blue", "yellow" };
private ObservableMap<String, ObservableMap<String, String>> populateStates() {
ObservableMap<String, ObservableMap<String, String>> states = FXCollections.observableHashMap();
for (int i = 0; i < 5; i ++) {
ObservableMap<String, String> variables = FXCollections.observableHashMap();
for (String variableName: variableNames) {
variables.put(variableName, random.nextInt(255) + "");
}
states.put("state " + i, variables);
}
return states;
}
public static void main(String[] args) {
launch(args);
}
public static class StateItem {
private String stateName;
private String variableName;
private String variableValue;
public StateItem(String stateName, String variableName, String variableValue) {
this.stateName = stateName;
this.variableName = variableName;
this.variableValue = variableValue;
}
public String getStateName() {
return stateName;
}
public void setStateName(String stateName) {
this.stateName = stateName;
}
public String getVariableName() {
return variableName;
}
public void setVariableName(String variableName) {
this.variableName = variableName;
}
public String getVariableValue() {
return variableValue;
}
public void setVariableValue(String variableValue) {
this.variableValue = variableValue;
}
}
}
What I do is provide a new StateItem class which feeds into the observable list for the view model and contains the stateName, variableName and variableValue values used for each row of the table. There is a separate extraction function which extracts data from the model map and populates the view model observable list as needed.
What "as needed" means for you will depend upon what you need to accomplish. If you only need to populate the data up-front at initialization, a single call to extract the data to the view model is all that is required.
If you need the view model to change dynamically based on changes to the underlying data, then you need to either:
Perform some binding of values from the view model to the model OR
Add some listeners for changes to the model which you then use to update the view model OR
Make sure you make a direct call to update the view model whenever the underlying model changes.
For the sample, I have provided an example of a listener based approach. I changed the underlying model class from Map<String, Map<String, String> to ObservableMap<String, ObservableMap<String, String>> and then use a MapChangeListener to listen for changes of the outermost ObservableMap (in your case this is would correspond to the addition of an entirely new state or removal of an existing state).
If you need to maintain additional synchronicity between the two structures, for instance reflecting dynamically that variables are added or removed, or variables or states are renamed or variable values are updated, then you would need to apply additional listeners for the inner-most ObservableMap which is maintaining your variable list. You would likely also change the types from String to StringProperty so that you could bind values in the model view StateItem class to values in your model, and you would also add property accessors to the StateItem class.
Anyway, the above code is unlikely to completely solve your problem but may assist in better understanding potential approaches you might wish to evaluate to solve it.
As an aside, perhaps using a TreeTableView, might be a better control for your implementation than a TableView. Just depends on your needs.
Thanks guys! I did it! Tables in JavaFX are so annoying, but my solution is here for anyone who will need it :)
public class Row {
private String state;
private String[] values;
public Row(String state, String... values) {
this.state = state;
this.values = values;
}
public String getState() {
return state;
}
public List<String> getValues() {
return Arrays.asList(values);
}
Set columns:
column.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(data.getValue().getValues().get(index)));
Get data from map:
public ObservableList<Row> getTableData() {
ObservableList<Row> data = FXCollections.observableArrayList();
for (Map.Entry<String, TreeMap<String, String>> entry : map.entrySet()) {
String[] values = new String[entry.getValue().values().size()];
int index = 0;
for (String value : entry.getValue().values()) {
values[index] = value;
index++;
}
Row row = new Row(entry.getKey(), values);
data.add(row);
}
return data;
}
And at last:
table.setItems(getTableData());
Table with expected output
Of course it wants some fixes with undefined values and so on but it works finally :)

JavaFx dynamic column values

I have a TreeTableView<MyCustomRow> and I want to add columns dynamically. In MyCustomRow i have a Map<Integer, SimpleBooleanProperty> with the values in the row. I'm adding the new column this way:
private TreeTableColumn<MyCustomRow, Boolean> newColumn() {
TreeTableColumn<MyCustomRow, Boolean> column = new TreeTableColumn<>();
column.setId(String.valueOf(colNr));
column.setPrefWidth(150);
column.setCellValueFactory(data -> data.getValue().getValue().getValue(colNr));
column.setCellFactory(factory -> new CheckBoxTreeTableCell());
column.setEditable(true);
colNr++;
return column;
}
Then table.getColumns().add(newColumn()).
The problem is when I check a CheckBox in a row, all CheckBoxes in that row become checked. Here is the code for my row:
public class MyCustomRow {
private Map<Integer, SimpleBooleanProperty> values = new HashMap<>();
public MyCustomRow(Map<Integer, Boolean> values) {
values.entrySet().forEach(entry -> this.values
.put(entry.getKey(), new SimpleBooleanProperty(entry.getValue())));
}
public SimpleBooleanProperty getValue(Integer colNr) {
if (!values.containsKey(colNr)) {
values.put(colNr, new SimpleBooleanProperty(false));
}
return values.get(colNr);
}
}
So I set the value of the cell depending on the colNr, i also tried to debug and it seems the values are different in the values map, so I have no idea why all the checkBoxes are checked when I check just one.
In this line,
column.setCellValueFactory(data -> data.getValue().getValue().getValue(colNr));
The handler is called when the cells are shown. Hence all of colNr are the newest value, the boolean property of the last index is associated with all cells.
To call the handler with the value at the time of newColumn() is called, for example:
final Integer colNrFixed = colNr;
column.setCellValueFactory(data -> data.getValue().getValue().getValue(colNrFixed));
// ...
colNr++;

Resources