JavaFX retrieve TableCells of selected row - javafx

In my JavaFX TableView, I am trying to retrieve TableCells from a selected row to mark them
with custom colors.
Simply changing the colors of the entire row does not work in this case, as I use different color shadings in each cell depending
on the value of each cell
The example below shows two approaches I tried I to solve the problem
1) Use a listener to retrieve cells in the selected row. Printing the row index and content already works
However, I could not find how to retrieve a TableCell from table.getSelectionModel().
2) Try a dirty workaround to add the TableCells to a global data structure in the columnCellFactory.
However, the TableCells do not get added to the tableCells ArrayList for some reason.
To obtain a short example, the imports and the Classes defining the EditingCell (custom TableCell) and CellEditEvent were omitted.
package TableViewColExample;
public class TableViewExample extends Application {
private Callback<TableColumn, TableCell> columnCellFactory ;
final TableView<String[]> table = new TableView<String[]>();
ObservableSet<Integer> selectedRowIndexes = FXCollections.observableSet();
ObservableSet<String> selectedRows = FXCollections.observableSet();
ArrayList<ArrayList<EditingCell>> tableColumns = new ArrayList<ArrayList<EditingCell>>();
#Override
public void start(Stage stage) {
String[][] dat = new String[][]{
{"C1","C2","C3"},{"a","b","c"},{"d","e","f"},{"g","i","h"}};
ObservableList<String []> data = FXCollections.observableArrayList();
data.addAll(Arrays.asList(dat));
data.remove(0);
table.setItems(data);
for (int i = 0; i < dat[0].length; i++) {
TableColumn tc = new TableColumn(dat[0][i]);
final int colNo = i;
tc.setCellValueFactory(new Callback<CellDataFeatures<String[], String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<String[], String> p) {
return new SimpleStringProperty((p.getValue()[colNo]));
}
});
ArrayList<EditingCell> tableCells = new ArrayList<EditingCell>();
columnCellFactory =
new Callback<TableColumn, TableCell>() {
public TableCell call(TableColumn p) {
EditingCell tcell = new EditingCell();
//For some reason, the EditingCell is never added to the list
tableCells.add(tcell);
return tcell;
}
};
tc.setCellFactory(columnCellFactory);
tableColumns.add(tableCells);
//The printed value here is 0, which means that the Factory does not add the Editing Cell to the List
System.out.println(" Column rows "+tableCells.size());
table.getColumns().add(tc);
}
//Output: TableColumns 3, TableRows 0
System.out.println("TableColumns "+ tableColumns.size() + " Table rows "+tableColumns.get(0).size());
table.setItems(data);
table.getSelectionModel().getSelectedCells().addListener((Change<? extends TablePosition> change) -> {
selectedRows.clear();
table.getSelectionModel().getSelectedCells().stream().map(TablePosition::getRow).f orEach(row -> {
selectedRowIndexes.add(row);
System.out.println(selectedRowIndexes.toString());
});
table.getSelectionModel().getSelectedItems().forEach(row -> {
selectedRows.add(Arrays.toString(row));
System.out.println(selectedRows.toString());
});
});
stage.setScene(new Scene(table));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Related

Using filteredList in an editable ListView

I'm using a FilteredList for my ListView to enable searching. The problem is that FilteredList does not allow mutating the data in any way, it only responds to changes in underlying ObservableList.
Also, it is declared final, so I can't simply extend it to forward the edit requests to the source.
So, how can I use it in an Editable ListView?
Here is the code to reproduce the problem
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
//problematic code
var observableList = FXCollections.observableArrayList("name", "name 2", "name 3");
FilteredList<String> filteredList = new FilteredList<>(observableList);
var list = new ListView<>(filteredList);
list.setEditable(true);
list.setCellFactory(TextFieldListCell.forListView());
//boilerplate code
VBox wrapper = new VBox(list);
primaryStage.setScene(new Scene(wrapper));
primaryStage.show();
}
}
Edit: added an minimal reproducible example
The problem - as you noticed - is that none of the concrete implementations of TransformationList (Sorted/FilteredList) is modifiable. So the default commit handler fails (with UnsupportedOperationException) while trying to set the newValue:
private EventHandler<ListView.EditEvent<T>> DEFAULT_EDIT_COMMIT_HANDLER = t -> {
int index = t.getIndex();
List<T> list = getItems();
if (index < 0 || index >= list.size()) return;
list.set(index, t.getNewValue());
};
The way out is a custom commit handler. Its implementation depends on context, it can
set a new item in the underlying source list
modify a property of the item
Code snippet for setting an item:
// monolithic items
ObservableList<String> data = FXCollections.observableArrayList("afirst", "abString", "other");
FilteredList<String> filteredData = new FilteredList<>(data);
filteredData.setPredicate(text -> text.contains("a"));
// set up an editable listView
ListView<String> list = new ListView<>(filteredData);
list.setEditable(true);
list.setCellFactory(TextFieldListCell.forListView());
// commitHandler resetting the underlying data element
list.setOnEditCommit(v -> {
ObservableList<String> items = list.getItems();
int index = v.getIndex();
if (items instanceof TransformationList<?, ?>) {
TransformationList transformed = (TransformationList) items;
items = transformed.getSource();
index = transformed.getSourceIndex(index);
}
items.set(index, v.getNewValue());
});
Code snippet for changing a property of an item:
// items with properties
ObservableList<MenuItem> data = FXCollections.observableArrayList(
new MenuItem("afirst"), new MenuItem("abString"), new MenuItem("other"));
FilteredList<MenuItem> filteredData = new FilteredList<>(data);
// filter on text property
filteredData.setPredicate(menuItem -> menuItem.getText().contains("a"));
// set up an editable listView
ListView<MenuItem> list = new ListView<>(filteredData);
list.setEditable(true);
// converter for use in TextFieldListCell
StringConverter<MenuItem> converter = new StringConverter<>() {
#Override
public String toString(MenuItem menuItem) {
return menuItem != null ? menuItem.getText() : null;
}
#Override
public MenuItem fromString(String text) {
return new MenuItem(text);
}
};
list.setCellFactory(TextFieldListCell.forListView(converter));
// commitHandler changing a property of the item
list.setOnEditCommit(v -> {
ObservableList<MenuItem> items = list.getItems();
MenuItem column = items.get(v.getIndex());
MenuItem standIn = v.getNewValue();
column.setText(standIn.getText());
});

Setting a style class only for first-level nodes in JavaFX treeTableView

I have a restaurant menu with dishes and categories implemented as a treeTableView in javaFX.
I want to make the the category rows appear different with CSS but I just can't find a way to filter them out and apply a class. Moving the images a bit to the left would also be nice. I also had no luck using a rowFactory. I've seen this answer but I don't understand it.
This is how I fill the table. I've left out the column- and cellfactories.
private void fillDishes(List<Dish> dishes){
root.getChildren().clear();
Map<String,TreeItem<Dish>> categoryMap = new HashMap<>();
for (Category c: allCats) {
TreeItem<Dish> newCat = new TreeItem<>(new Dish(c.getName(),null,null,null));
//newCat.getGraphic().getStyleClass().add("category");
categoryMap.put(c.getName(),newCat);
root.getChildren().add(newCat);
}
for (Dish d: dishes) {
categoryMap.get(d.getCategory()).getChildren().add(new TreeItem<>(d));
}
}
TreeTableView uses the rowFactory to create the TreeTableRows. At some time later it assigns a TreeItem to a TreeTableRow. This may happen again with different TreeItems for the same row. For this reason you need to handle changes those changes which can be done by adding a ChangeHandler to the TreeTableRow.treeItem property. If a new TreeItem is assigned to the row, you can check for top-level nodes by checking the children of the (invisible) root item for the row item.
I prefer the approach that does not require searching the child list though. It's possible to compare the parent of the item with the root.
public static class Item {
private final String value1;
private final String value2;
public Item(String value1, String value2) {
this.value1 = value1;
this.value2 = value2;
}
public String getValue1() {
return value1;
}
public String getValue2() {
return value2;
}
}
#Override
public void start(Stage primaryStage) {
final TreeItem<Item> root = new TreeItem<>(null);
TreeTableView<Item> ttv = new TreeTableView<>(root);
ttv.setShowRoot(false);
TreeTableColumn<Item, String> column1 = new TreeTableColumn<>();
column1.setCellValueFactory(new TreeItemPropertyValueFactory<>("value1"));
TreeTableColumn<Item, String> column2 = new TreeTableColumn<>();
column2.setCellValueFactory(new TreeItemPropertyValueFactory<>("value2"));
ttv.getColumns().addAll(column1, column2);
final PseudoClass topNode = PseudoClass.getPseudoClass("top-node");
ttv.setRowFactory(t -> {
final TreeTableRow<Item> row = new TreeTableRow<>();
// every time the TreeItem changes, check, if the new item is a
// child of the root and set the pseudoclass accordingly
row.treeItemProperty().addListener((o, oldValue, newValue) -> {
boolean tn = false;
if (newValue != null) {
tn = newValue.getParent() == root;
}
row.pseudoClassStateChanged(topNode, tn);
});
return row;
});
// fill tree structure
TreeItem<Item> c1 = new TreeItem<>(new Item("category 1", null));
c1.getChildren().addAll(
new TreeItem<>(new Item("sub1.1", "foo")),
new TreeItem<>(new Item("sub1.2", "bar")));
TreeItem<Item> c2 = new TreeItem<>(new Item("category 2", null));
c2.getChildren().addAll(
new TreeItem<>(new Item("sub2.1", "answer")),
new TreeItem<>(new Item("sub2.2", "42")));
root.getChildren().addAll(c1, c2);
Scene scene = new Scene(ttv);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
style.css
.tree-table-row-cell:top-node {
-fx-background: orange;
}
Moving the images a bit to the left would also be nice.
Usually you do this from a custom TreeTableCell returned by a TreeTableColumn.cellFactory. Depending on the behavior you want to implement setting fitWidth/fitHeight may be sufficient, but in other cases dynamically modifying those values based on the cell size may be required.

JavaFX copying over a object from one tableview to another with a button

So I´m building a warband calculator for a tabletop game and currently it looks like this
What I wanna do is that when I click on a "add" button it copies that unit/creature over to the tableview on the right side(So if I click on the "add" button on the Zombie row, a zombie gets copied over the tableview on the right).
The problem is I can only make it work if you select the row THEN click the button, but I want to be able to rely solely on the button. I think the problem is that I use "getSelectionModel().getSelectedItem()" in the button class to get the object to be copied over, But I cant find any other way to do it.
The relevant code parts from the main class
//Creates the lists
ObservableList<Unit> rightSideList = FXCollections.observableArrayList();
ObservableList<Unit> leftSideList = FXCollections.observableArrayList();
// Puts some test data in the lists
public Main() {
rightSideList.add(new Unit("Skeleton",5,4,9,4,1,6,9));
rightSideList.add(new Unit("Ghoul",6,4,010,4,1,6,7));
rightSideList.add(new Unit("Zombie",4,5,1,3,1,6,5));
rightSideList.add(new Unit("Wraith",4,5,19,3,1,6,5));
rightSideList.add(new Unit("Spectre",4,5,1,3,1,6,5));
leftSideList.add(new Unit("Skeleton",5,4,0,4,1,6,9));
}
//Creates the tables
final TableView<Unit> table1 = new TableView<>(
rightSideList
);
final TableView<Unit> table2 = new TableView<>(
leftSideList
);
//Defines the table columns
//Columns for table 1
TableColumn<Unit,String> unitNameCol = new TableColumn<>("Unit");
unitNameCol.setCellValueFactory(new PropertyValueFactory("unitName"));
unitNameCol.setMinWidth(100);
TableColumn<Unit,Integer> speedCol = new TableColumn<>("Spd");
speedCol.setCellValueFactory(new PropertyValueFactory("speed"));
TableColumn<Unit,Integer> meleeCol = new TableColumn<>("Me");
meleeCol.setCellValueFactory(new PropertyValueFactory("melee"));
TableColumn<Unit,Integer> rangedCol = new TableColumn<>("Ra");
rangedCol.setCellValueFactory(new PropertyValueFactory("ranged"));
TableColumn<Unit,Integer> defenseCol = new TableColumn<>("Def");
defenseCol.setCellValueFactory(new PropertyValueFactory("defense"));
TableColumn<Unit,Integer> attackCol = new TableColumn<>("Att");
attackCol.setCellValueFactory(new PropertyValueFactory("attack"));
TableColumn<Unit,Integer> toughnessCol = new TableColumn<>("To");
toughnessCol.setCellValueFactory(new PropertyValueFactory("toughness"));
TableColumn<Unit,Integer> costCol = new TableColumn<>("Cost");
costCol.setCellValueFactory(new PropertyValueFactory("cost"));
TableColumn<Unit, Boolean> actionCol = new TableColumn<>("Action");
actionCol.setSortable(false);
actionCol.setMinWidth(35);
// define a simple boolean cell value for the action column so that the column will only be shown for non-empty rows for table 1
actionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Unit, Boolean>, ObservableValue<Boolean>>() {
#Override public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<Unit, Boolean> features) {
return new SimpleBooleanProperty(features.getValue() != null);
}
});
// create a cell value factory with an add button for each row in the table for table 1
actionCol.setCellFactory(new Callback<TableColumn<Unit, Boolean>, TableCell<Unit, Boolean>>() {
#Override public TableCell<Unit, Boolean> call(TableColumn<Unit, Boolean> unitBooleanTableColumn) {
return new AddUnitCell(mainStage, table1);
}
});
And here is the button class
/** A table cell containing a button for adding a unit */
private class AddUnitCell extends TableCell<Unit, Boolean> {
// a button for adding a new Unit.
final Button addButton = new Button("Add");
// pads and centers the add button in the cell.
final StackPane paddedButton = new StackPane();
/**
* AddUnitCell constructor
* #param stage the stage in which the table is placed.
* #param table the table to which a unit can be added.
*/
AddUnitCell(final Stage stage, final TableView<Unit> table) {
paddedButton.setPadding(new Insets(3));
paddedButton.getChildren().add(addButton);
addButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
Unit selectedUnit = table.getSelectionModel().getSelectedItem();
leftSideList.add(selectedUnit);
}
});
}
/** places an add button in the row only if the row is not empty. */
#Override protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(paddedButton);
} else {
setGraphic(null);
}
}
}
If you need to check in detail here is a link to the projects repository
https://github.com/MagnusLindstrom88/Star-Struck-City-Warband-Calculator/blob/master/src/application/Main.java
Instead of
Unit selectedUnit = table.getSelectionModel().getSelectedItem();
in the button event handler's handle() method, do
Unit selectedUnit = table.getItems().get(getIndex());
Instead of relying on the selection model to get the item, you should get the item from the TableRow:
Unit selectedUnit = (Unit) getTableRow().getItem();

Bind column to data javafx

Let me first explain what I did.
I am having list of tables in the drop down
when someone select any one it will populate all the data to tableview with respect to column name
I am able to get the columns from the table and put it in the tableview but I am not able to bind the data with associated columns.
My code here is to look
this.tableName = clsComboData.getValue();
System.out.println(tableName); System.out.println("Button Pressed");
List<StaticColumnConfig> allColumns = null;
for(StaticDataTable dataTable : dropdown) {
if(dataTable.getTableName() != null && dataTable.getTableName().equalsIgnoreCase(tableName)) {
System.out.println(dataTable.getColumnConfig());
allColumns = dataTable.getColumnConfig();
}
}
switch (tableName) {
case "IOSwapCounterparties": SimpleDateFormat date = new SimpleDateFormat("2015-04-15");
System.out.println("Into the Switch");
data = FXCollections.observableArrayList(
new IOSwapCounterparties(1,"a","b","c","d","e",date),
new IOSwapCounterparties(1,"aa","bb","cv","dd","es",date),
new IOSwapCounterparties(1,"ad","bd","cd","dc","eb",date),
new IOSwapCounterparties(1,"aw","bw","cr","dt","ey",date),
new IOSwapCounterparties(1,"ag","bt","cy","du","ep",date)
);
break;
}
//System.out.println("Its in ELSE");
//"Invited" column
TableColumn checkboxCol = new TableColumn<Person, Boolean>();
checkboxCol.setText("");
checkboxCol.setMinWidth(50);
checkboxCol.setCellValueFactory(new PropertyValueFactory("checkbox"));
// Create checkboxes
checkboxCol.setCellFactory(new Callback<TableColumn<Person, Boolean>, TableCell<Person, Boolean>>() {
public TableCell<Person, Boolean> call(TableColumn<Person, Boolean> p) {
CheckBoxTableCell<Person, Boolean> checkBox = new CheckBoxTableCell();
return checkBox;
}
});
//Set cell factory for cells that allow editing
Callback<TableColumn, TableCell> cellFactory =
new Callback<TableColumn, TableCell>() {
public TableCell call(TableColumn p) {
return new EditingCell();
}
};
for(StaticColumnConfig column : allColumns){
TableColumn oneColumn = new TableColumn(column.getColumnName());
oneColumn.setCellFactory(cellFactory);
}
// Clear the tableview for next table
tblViewer.getColumns().clear();
// Push the data to the tableview
tblViewer.setItems(data);
tblViewer.setEditable(true);
tblViewer.getColumns().addAll(checkboxCol);
for(StaticColumnConfig column : allColumns){
System.out.println(column.getColumnName());
TableColumn oneColumn = new TableColumn(column.getColumnName());
tblViewer.getColumns().addAll(oneColumn);
}
// Add the columns
tblViewer.getSelectionModel().setCellSelectionEnabled(true);
tblViewer.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
There is been uppercase - lowercase mistake with my getter and setter method.

Javafx multiple calls to get method when using setCellValueFactory

Why does a
tableColumn.setCellValueFactory(new PropertyValueFactory<Object1, Integer>("Number"));
method call the getNumber method multiple times when I only add one row to a tableView?
The number field is in object2 that is used in object1. So here Object1 one provides a getNumber method that gets a number stored in an arraylist in object2.
Here is my getNumber method:
public double getNumber() {
setNumber++;
return sets.get(setNumber).getNumber();
}
The value is fetched when the TableView shows it. When you modify your value when it is retrieved, you are just doing that and you just count the fetches.
Imagine you have a TableView with 10 visible rows and 20 entries in the list. When you scroll down and up, the value is fetched for the rows which newly appear in the visible area.
If you try this:
public class SO extends Application {
public static class Item {
final int v;
Item(int v) { this.v = v; }
public int getV() {
System.out.println(v);
return v;
}
}
#Override
public void start(Stage primaryStage) throws Exception {
StackPane pane = new StackPane();
Scene scene = new Scene(pane, 300, 100);
primaryStage.setScene(scene);
ObservableList<Item> list = FXCollections.observableArrayList();
for (int i=0; i<20; i++) {
Item item = new Item(i);
list.add(item);
}
TableView<Item> tv = new TableView<>(list);
TableColumn<Item, String> col = new TableColumn<>("Column");
col.setCellValueFactory(new PropertyValueFactory<Item,String>("v"));
tv.getColumns().add(col);
pane.getChildren().add(tv);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
When scrolling the TableView to the end, it will print increasing numbers in the console. When scrolling back, it prints decreasing numbers as it displays values from the beginning of the list. Part of the output:
5
6
7
8
9
9
2
2
1
1
0

Resources