JavaFx dynamic column values - javafx

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++;

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.

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.

Javafx TableView not showing data

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.

Spinner inside TableCell not updating value

I've created a simple TableView that is fed with data from a database, and what I want is just to be able to easily change the value of a numeric column of that table with JavaFx.
But... since I have some mental issue or something, I can't make it work.
Below it's the "SpinnerCell" component, and the issue I've been having is that even after the commitEdit is fired, when I get the items from the TableView, no values were altered. What am I missing from this update lifecycle?
import javafx.scene.control.Spinner;
import javafx.scene.control.TableCell;
public class SpinnerTableCell<S, T extends Number> extends TableCell<S, T> {
private final Spinner<T> spinner;
public SpinnerTableCell() {
this(1);
}
public SpinnerTableCell(int step) {
this.spinner = new Spinner<>(0, 100, step);
this.spinner.valueProperty().addListener((observable, oldValue, newValue) -> commitEdit(newValue));
}
#Override
protected void updateItem(T c, boolean empty) {
super.updateItem(c, empty);
if (empty || c == null) {
setText(null);
setGraphic(null);
return;
}
this.spinner.getValueFactory().setValue(c);
setGraphic(spinner);
}
}
Because your table cell is always showing the editing control (the Spinner), you bypass the usual table cell mechanism for beginning an edit. For example, in the TextFieldTableCell, if the cell is not in an editing state, then a label is shown. When the user double-clicks the cell, it enters an editing state: the cell's editingProperty() is set to true, and the enclosing TableView's editingCellProperty() is set to the position of the current cell, etc.
In your case, since this never happens, isEditing() is always false for the cell, and as a consequence, commitEdit() becomes a no-op.
Note that the CheckBoxTableCell is implemented similarly: its documentation highlights this fact. (The check box table cell implements its own direct update of properties via the selectedStateCallback.)
So there are two options here: one would be to enter an editing state when the spinner gains focus. You can do this by adding the following to the cell's constructor:
this.spinner.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
getTableView().edit(getIndex(), getTableColumn());
}
});
Another option would be to provide a callback for "direct updates". So you could do something like:
public SpinnerTableCell(BiConsumer<S,T> update, int step) {
this.spinner = new Spinner<>(0, 100, step);
this.spinner.valueProperty().addListener((observable, oldValue, newValue) ->
update.accept(getTableView().getItems().get(getIndex()), newValue));
}
and then given a model class for the table, say
public class Item {
private int value ;
public int getValue() { return value ;}
public void setValue(int value) { this.value = value ;}
// ...
}
You could do
TableView<Item> table = ... ;
TableColumn<Item, Integer> valueCol = new TableColumn<>("Value");
valueCol.setCellValueFactory(cellData -> new SimpleIntegerProperty(cellData.getValue().getValue()).asObject());
valueCol.setCellFactory(tc -> new SpinnerTableCell<>(Item::setValue, 1));

Can't select different cells in same row of tableview JavaFx

I have a TableView, where I need to enable the selection of any cells(one at a time). For now, I use this code:
tableView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observableValue, Object oldValue, Object newValue) {
if(tableView.getSelectionModel().getSelectedItem() != null)
{
TableView.TableViewSelectionModel selectionModel = tableView.getSelectionModel();
ObservableList selectedCells = selectionModel.getSelectedCells();
TablePosition tablePosition = (TablePosition) selectedCells.get(0);
String val = (String)tablePosition.getTableColumn().getCellData(newValue);
System.out.println("Selected Value: " + val);
selectionTextField.appendText(val);
}
}
});
The problem is, I can't get the values of different cells in the same row after each other. I think it's because it's the same row, the selection listener is not triggered. I tried clearSelection(), but then I get out of bounds exception, and I read somewhere that I cant change the table model in the same listener. Any tips?
Thank you.
If you are using cell selection instead of row selection (in other words, if you have called tableView.getSelectionMode().setCellSelectionEnabled(true);), then you should observe the list of selected cells instead of the selectedItem property. The selectedItem property only indicates the row that is selected, so it only changes if you select a new row.
ObservableList<TablePosition> selectedCells = table.getSelectionModel().getSelectedCells() ;
selectedCells.addListener((ListChangeListener.Change<? extends TablePosition> change) -> {
if (selectedCells.size() > 0) {
TablePosition selectedCell = selectedCells.get(0);
TableColumn column = selectedCell.getTableColumn();
int rowIndex = selectedCell.getRow();
Object data = column.getCellObservableValue(rowIndex).getValue();
}
});

Resources